1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #281 from TomHarte/MachineOptions

Introduces reflective machine options and a command-line parser for them.
This commit is contained in:
Thomas Harte 2017-11-18 17:03:53 -08:00 committed by GitHub
commit 8dcac6561e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 295 additions and 29 deletions

View File

@ -0,0 +1,77 @@
//
// Configurable.h
// Clock Signal
//
// Created by Thomas Harte on 17/11/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef Configurable_h
#define Configurable_h
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace Configurable {
/*!
The Option class hierarchy provides a way for components, machines, etc, to provide a named
list of typed options to which they can respond.
*/
struct Option {
std::string long_name;
std::string short_name;
virtual ~Option() {}
Option(const std::string &long_name, const std::string &short_name) : long_name(long_name), short_name(short_name) {}
};
struct BooleanOption: public Option {
BooleanOption(const std::string &long_name, const std::string &short_name) : Option(long_name, short_name) {}
};
struct ListOption: public Option {
std::vector<std::string> options;
ListOption(const std::string &long_name, const std::string &short_name, const std::vector<std::string> &options) : Option(long_name, short_name), options(options) {}
};
/*!
Selections are responses to Options.
*/
struct Selection {
virtual ~Selection() {}
};
struct BooleanSelection: public Selection {
bool value;
BooleanSelection(bool value) : value(value) {}
};
struct ListSelection: public Selection {
std::string value;
ListSelection(const std::string value) : value(value) {}
};
using SelectionSet = std::map<std::string, std::unique_ptr<Selection>>;
/*!
A Configuratble provides the options that it responds to and allows selections to be set.
*/
struct Device {
virtual std::vector<std::unique_ptr<Option>> get_options() = 0;
virtual void set_selections(const SelectionSet &selection_by_option) = 0;
virtual SelectionSet get_accurate_selections() = 0;
virtual SelectionSet get_user_friendly_selections() = 0;
};
template <typename T> T *selection(const Configurable::SelectionSet &selections_by_option, const std::string &name) {
auto selection = selections_by_option.find(name);
if(selection == selections_by_option.end()) return nullptr;
return dynamic_cast<T *>(selection->second.get());
}
}
#endif /* Configurable_h */

View File

@ -636,6 +636,30 @@ class ConcreteMachine:
return keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
return options;
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(quickload) set_use_fast_tape_hack(quickload->value);
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(false));
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
return selection_set;
}
private:
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;

View File

@ -9,6 +9,7 @@
#ifndef Vic20_hpp
#define Vic20_hpp
#include "../../../Configurable/Configurable.hpp"
#include "../../ConfigurationTarget.hpp"
#include "../../CRTMachine.hpp"
#include "../../KeyboardMachine.hpp"
@ -44,7 +45,8 @@ class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine,
public JoystickMachine::Machine {
public JoystickMachine::Machine,
public Configurable::Device {
public:
virtual ~Machine();

View File

@ -10,6 +10,9 @@
#define ConfigurationTarget_hpp
#include "../StaticAnalyser/StaticAnalyser.hpp"
#include "../Configurable/Configurable.hpp"
#include <string>
namespace ConfigurationTarget {

View File

@ -416,7 +416,38 @@ class ConcreteMachine:
return keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
return options;
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(quickload) set_use_fast_tape_hack(quickload->value);
auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display");
if(display) get_crt()->set_output_device((display->value == "rgb") ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(false));
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection("composite"));
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection("rgb"));
return selection_set;
}
private:
// MARK: - Work deferral updates.
inline void update_display() {
if(cycles_since_display_update_ > 0) {
video_output_->run_for(cycles_since_display_update_.flush());

View File

@ -9,6 +9,7 @@
#ifndef Electron_hpp
#define Electron_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@ -41,7 +42,8 @@ enum ROMSlot: uint8_t {
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public KeyboardMachine::Machine,
public Configurable::Device {
public:
virtual ~Machine();

View File

@ -42,7 +42,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
BIND(Hyphen, KeyMinus);
BIND(Delete, KeyDelete);
BIND(Delete, KeyDelete); BIND(BackSpace, KeyDelete);
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);

View File

@ -427,6 +427,36 @@ class ConcreteMachine:
return keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
options.emplace_back(new Configurable::ListOption("Display", "display", {"composite", "rgb"}));
return options;
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(quickload) set_use_fast_tape_hack(quickload->value);
auto display = Configurable::selection<Configurable::ListSelection>(selections_by_option, "display");
if(display) get_crt()->set_output_device((display->value == "rgb") ? Outputs::CRT::OutputDevice::Monitor : Outputs::CRT::OutputDevice::Television);
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(false));
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection("composite"));
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection("rgb"));
return selection_set;
}
private:
CPU::MOS6502::Processor<ConcreteMachine, false> m6502_;

View File

@ -9,6 +9,7 @@
#ifndef Oric_hpp
#define Oric_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@ -28,7 +29,8 @@ enum ROM {
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public KeyboardMachine::Machine,
public Configurable::Device {
public:
virtual ~Machine();

View File

@ -21,23 +21,30 @@ template<typename T> class TypedDynamicMachine: public ::Machine::DynamicMachine
public:
TypedDynamicMachine(T *machine) : machine_(machine) {}
ConfigurationTarget::Machine *configuration_target() {
return dynamic_cast<ConfigurationTarget::Machine *>(machine_.get());
ConfigurationTarget::Machine *configuration_target() override {
return get<ConfigurationTarget::Machine>();
}
CRTMachine::Machine *crt_machine() {
return dynamic_cast<CRTMachine::Machine *>(machine_.get());
CRTMachine::Machine *crt_machine() override {
return get<CRTMachine::Machine>();
}
JoystickMachine::Machine *joystick_machine() {
return dynamic_cast<JoystickMachine::Machine *>(machine_.get());
JoystickMachine::Machine *joystick_machine() override {
return get<JoystickMachine::Machine>();
}
KeyboardMachine::Machine *keyboard_machine() {
return dynamic_cast<KeyboardMachine::Machine *>(machine_.get());
KeyboardMachine::Machine *keyboard_machine() override {
return get<KeyboardMachine::Machine>();
}
Configurable::Device *configurable_device() override {
return get<Configurable::Device>();
}
private:
template <typename Class> Class *get() {
return dynamic_cast<Class *>(machine_.get());
}
std::unique_ptr<T> machine_;
};

View File

@ -11,6 +11,7 @@
#include "../../StaticAnalyser/StaticAnalyser.hpp"
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
@ -29,6 +30,7 @@ struct DynamicMachine {
virtual CRTMachine::Machine *crt_machine() = 0;
virtual JoystickMachine::Machine *joystick_machine() = 0;
virtual KeyboardMachine::Machine *keyboard_machine() = 0;
virtual Configurable::Device *configurable_device() = 0;
};
/*!

View File

@ -308,8 +308,7 @@ template<bool is_zx81> class ConcreteMachine:
return true;
}
// MARK: - Keyboard
// MARK: - Keyboard
void set_key_state(uint16_t key, bool isPressed) override final {
if(isPressed)
key_states_[key >> 8] &= static_cast<uint8_t>(~key);
@ -321,8 +320,7 @@ template<bool is_zx81> class ConcreteMachine:
memset(key_states_, 0xff, 8);
}
// MARK: - Tape control
// MARK: - Tape control
void set_use_fast_tape_hack(bool activate) override final {
use_fast_tape_hack_ = activate;
}
@ -337,8 +335,7 @@ template<bool is_zx81> class ConcreteMachine:
tape_player_.set_motor_control(is_playing);
}
// MARK: - Typer timing
// MARK: - Typer timing
HalfCycles get_typer_delay() override final { return Cycles(7000000); }
HalfCycles get_typer_frequency() override final { return Cycles(390000); }
@ -346,6 +343,36 @@ template<bool is_zx81> class ConcreteMachine:
return keyboard_mapper_;
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
std::vector<std::unique_ptr<Configurable::Option>> options;
options.emplace_back(new Configurable::BooleanOption("Load Tapes Quickly", "quickload"));
options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
return options;
}
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
if(quickload) set_use_fast_tape_hack(quickload->value);
auto autotapemotor = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "autotapemotor");
if(autotapemotor) set_use_automatic_tape_motor_control(autotapemotor->value);
}
Configurable::SelectionSet get_accurate_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(false));
selection_set["autotapemotor"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(false));
return selection_set;
}
Configurable::SelectionSet get_user_friendly_selections() override {
Configurable::SelectionSet selection_set;
selection_set["quickload"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
selection_set["autotapemotor"] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
return selection_set;
}
private:
CPU::Z80::Processor<ConcreteMachine, false, is_zx81> z80_;
@ -383,8 +410,7 @@ template<bool is_zx81> class ConcreteMachine:
bool use_automatic_tape_motor_control_;
HalfCycles tape_advance_delay_ = 0;
// MARK: - Video
// MARK: - Video
inline void set_vsync(bool sync) {
vsync_ = sync;
update_sync();

View File

@ -9,6 +9,7 @@
#ifndef ZX8081_hpp
#define ZX8081_hpp
#include "../../Configurable/Configurable.hpp"
#include "../ConfigurationTarget.hpp"
#include "../CRTMachine.hpp"
#include "../KeyboardMachine.hpp"
@ -25,7 +26,8 @@ enum ROMType: uint8_t {
class Machine:
public CRTMachine::Machine,
public ConfigurationTarget::Machine,
public KeyboardMachine::Machine {
public KeyboardMachine::Machine,
public Configurable::Device {
public:
static Machine *ZX8081(const StaticAnalyser::Target &target_hint);
virtual ~Machine();

View File

@ -680,6 +680,7 @@
4B30512C1D989E2200B4FED8 /* Drive.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Drive.hpp; sourceTree = "<group>"; };
4B30512E1D98ACC600B4FED8 /* Plus3.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Plus3.cpp; path = Electron/Plus3.cpp; sourceTree = "<group>"; };
4B30512F1D98ACC600B4FED8 /* Plus3.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Plus3.hpp; path = Electron/Plus3.hpp; sourceTree = "<group>"; };
4B31B88F1FBFBCD800C140D5 /* Configurable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Configurable.hpp; sourceTree = "<group>"; };
4B322DF31F5A26BF004EB04C /* 6502Implementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Implementation.hpp; sourceTree = "<group>"; };
4B322DF41F5A2714004EB04C /* 6502Storage.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = 6502Storage.hpp; sourceTree = "<group>"; };
4B322DFD1F5A2981004EB04C /* Z80AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Z80AllRAM.cpp; sourceTree = "<group>"; };
@ -1509,6 +1510,15 @@
name = Electron;
sourceTree = "<group>";
};
4B31B88E1FBFBCD800C140D5 /* Configurable */ = {
isa = PBXGroup;
children = (
4B31B88F1FBFBCD800C140D5 /* Configurable.hpp */,
);
name = Configurable;
path = ../../Configurable;
sourceTree = "<group>";
};
4B322DFC1F5A2981004EB04C /* AllRAM */ = {
isa = PBXGroup;
children = (
@ -2259,6 +2269,7 @@
4BF660691F281573002CB053 /* ClockReceiver */,
4BC9DF4A1D04691600F44158 /* Components */,
4B3940E81DA83C8700427841 /* Concurrency */,
4B31B88E1FBFBCD800C140D5 /* Configurable */,
4B055A761FAE78210060FFFF /* Frameworks */,
4B86E2581F8C628F006FAA45 /* Inputs */,
4BB73EDC1B587CA500552FC2 /* Machines */,

View File

@ -6,9 +6,9 @@
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include <cstdio>
#include <iostream>
#include <memory>
#include <cstdio>
#include <SDL2/SDL.h>
@ -117,19 +117,63 @@ bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key)
}
struct ParsedArguments {
std::string file_name;
Configurable::SelectionSet selections;
};
/*! Parses an argc/argv pair to discern program arguments. */
ParsedArguments parse_arguments(int argc, char *argv[]) {
ParsedArguments arguments;
for(int index = 1; index < argc; ++index) {
char *arg = argv[index];
// Accepted format is:
//
// --flag sets a Boolean option to true.
// --flag=value sets the value for a list option.
// name sets the file name to load.
// Anything starting with a dash always makes a selection; otherwise it's a file name.
if(arg[0] == '-') {
while(*arg == '-') arg++;
// Check for an equals sign, to discern a Boolean selection from a list selection.
std::string argument = arg;
std::size_t split_index = argument.find("=");
if(split_index == std::string::npos) {
arguments.selections[argument] = std::unique_ptr<Configurable::Selection>(new Configurable::BooleanSelection(true));
} else {
std::string name = argument.substr(0, split_index);
std::string value = argument.substr(split_index+1, std::string::npos);
arguments.selections[name] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(value));
}
} else {
arguments.file_name = arg;
}
}
return arguments;
}
int main(int argc, char *argv[]) {
SDL_Window *window = nullptr;
// Attempt to parse arguments.
ParsedArguments arguments = parse_arguments(argc, argv);
// Perform a sanity check on arguments.
if(argc < 2) {
if(arguments.file_name.empty()) {
std::cerr << "Usage: " << argv[0] << " [file]" << std::endl;
return -1;
}
// Determine the machine for the supplied file.
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets(argv[1]);
std::list<StaticAnalyser::Target> targets = StaticAnalyser::GetTargets(arguments.file_name.c_str());
if(targets.empty()) {
std::cerr << "Cannot open " << argv[1] << std::endl;
std::cerr << "Cannot open " << arguments.file_name << std::endl;
return -1;
}
@ -178,7 +222,6 @@ int main(int argc, char *argv[]) {
GLint target_framebuffer = 0;
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer);
std::cout << "Target framebuffer has ID " << target_framebuffer << std::endl;
// For vanilla SDL purposes, assume system ROMs can be found in one of:
//
@ -250,7 +293,13 @@ int main(int argc, char *argv[]) {
int window_width, window_height;
SDL_GetWindowSize(window, &window_width, &window_height);
std::cout << "Window size is " << window_width << ", " << window_height << std::endl;
// Establish user-friendly options by default.
Configurable::Device *configurable_device = machine->configurable_device();
if(configurable_device) {
configurable_device->set_selections(configurable_device->get_user_friendly_selections());
configurable_device->set_selections(arguments.selections);
}
// Run the main event loop until the OS tells us to quit.
bool should_quit = false;
@ -268,8 +317,6 @@ int main(int argc, char *argv[]) {
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer);
machine->crt_machine()->get_crt()->set_target_framebuffer(target_framebuffer);
SDL_GetWindowSize(window, &window_width, &window_height);
std::cout << "Resized; framebuffer is " << target_framebuffer << ", and window size is " << window_width << ", " << window_height << std::endl;
} break;
default: break;