diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp index 8e85c9410..6688debba 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.cpp @@ -12,6 +12,81 @@ using namespace Analyser::Dynamic; +namespace { + +class MultiStruct: public Reflection::Struct { + public: + MultiStruct(const std::vector &devices) : devices_(devices) { + for(auto device: devices) { + options_.emplace_back(device->get_options()); + } + } + + void apply() { + auto options = options_.begin(); + for(auto device: devices_) { + device->set_options(*options); + ++options; + } + } + + std::vector all_keys() final { + std::set keys; + for(auto &options: options_) { + const auto new_keys = options->all_keys(); + keys.insert(new_keys.begin(), new_keys.end()); + } + return std::vector(keys.begin(), keys.end()); + } + + std::vector values_for(const std::string &name) final { + std::set values; + for(auto &options: options_) { + const auto new_values = options->values_for(name); + values.insert(new_values.begin(), new_values.end()); + } + return std::vector(values.begin(), values.end()); + } + + const std::type_info *type_of(const std::string &name) final { + for(auto &options: options_) { + auto info = options->type_of(name); + if(info) return info; + } + return nullptr; + } + + const void *get(const std::string &name) final { + for(auto &options: options_) { + auto value = options->get(name); + if(value) return value; + } + return nullptr; + } + + void set(const std::string &name, const void *value) final { + const auto safe_type = type_of(name); + if(!safe_type) return; + + // Set this property only where the child's type is the same as that + // which was returned from here for type_of. + for(auto &options: options_) { + const auto type = options->type_of(name); + if(!type) continue; + + if(*type == *safe_type) { + options->set(name, value); + } + } + } + + private: + const std::vector &devices_; + std::vector> options_; +}; + +} + MultiConfigurable::MultiConfigurable(const std::vector> &machines) { for(const auto &machine: machines) { Configurable::Device *device = machine->configurable_device(); @@ -19,46 +94,11 @@ MultiConfigurable::MultiConfigurable(const std::vector> MultiConfigurable::get_options() { - std::vector> options; - - // Produce the list of unique options. - for(const auto &device : devices_) { - std::vector> device_options = device->get_options(); - for(auto &option : device_options) { - if(std::find(options.begin(), options.end(), option) == options.end()) { - options.push_back(std::move(option)); - } - } - } - - return options; +void MultiConfigurable::set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + options->apply(); } -void MultiConfigurable::set_selections(const Configurable::SelectionSet &selection_by_option) { - for(const auto &device : devices_) { - device->set_selections(selection_by_option); - } -} - -Configurable::SelectionSet MultiConfigurable::get_accurate_selections() { - Configurable::SelectionSet set; - for(const auto &device : devices_) { - Configurable::SelectionSet device_set = device->get_accurate_selections(); - for(auto &selection : device_set) { - set.insert(std::move(selection)); - } - } - return set; -} - -Configurable::SelectionSet MultiConfigurable::get_user_friendly_selections() { - Configurable::SelectionSet set; - for(const auto &device : devices_) { - Configurable::SelectionSet device_set = device->get_user_friendly_selections(); - for(auto &selection : device_set) { - set.insert(std::move(selection)); - } - } - return set; +std::unique_ptr MultiConfigurable::get_options() { + return std::make_unique(devices_); } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp index ac4aecae9..2595fc69c 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiConfigurable.hpp @@ -10,6 +10,7 @@ #define MultiConfigurable_hpp #include "../../../../Machines/DynamicMachine.hpp" +#include "../../../../Configurable/Configurable.hpp" #include #include @@ -28,10 +29,8 @@ class MultiConfigurable: public Configurable::Device { MultiConfigurable(const std::vector> &machines); // Below is the standard Configurable::Device interface; see there for documentation. - std::vector> get_options() final; - void set_selections(const Configurable::SelectionSet &selection_by_option) final; - Configurable::SelectionSet get_accurate_selections() final; - Configurable::SelectionSet get_user_friendly_selections() final; + void set_options(const std::unique_ptr &options) final; + std::unique_ptr get_options() final; private: std::vector devices_; diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 252176f56..75cafb509 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -59,7 +59,6 @@ static std::vector> Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { auto target = std::make_unique(); - target->machine = Machine::Electron; target->confidence = 0.5; // TODO: a proper estimation target->has_dfs = false; target->has_adfs = false; diff --git a/Analyser/Static/Acorn/Target.hpp b/Analyser/Static/Acorn/Target.hpp index e2bfcb779..1ef46c291 100644 --- a/Analyser/Static/Acorn/Target.hpp +++ b/Analyser/Static/Acorn/Target.hpp @@ -9,6 +9,7 @@ #ifndef Analyser_Static_Acorn_Target_h #define Analyser_Static_Acorn_Target_h +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,11 +17,18 @@ namespace Analyser { namespace Static { namespace Acorn { -struct Target: public ::Analyser::Static::Target { +struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { bool has_adfs = false; bool has_dfs = false; bool should_shift_restart = false; std::string loading_command; + + Target() : Analyser::Static::Target(Machine::Electron) { + if(needs_declare()) { + DeclareField(has_adfs); + DeclareField(has_dfs); + } + } }; } diff --git a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp index 7efadf16d..c45adf2b2 100644 --- a/Analyser/Static/AmstradCPC/StaticAnalyser.cpp +++ b/Analyser/Static/AmstradCPC/StaticAnalyser.cpp @@ -182,7 +182,6 @@ static bool CheckBootSector(const std::shared_ptr &disk, co Analyser::Static::TargetList Analyser::Static::AmstradCPC::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { TargetList destination; auto target = std::make_unique(); - target->machine = Machine::AmstradCPC; target->confidence = 0.5; target->model = Target::Model::CPC6128; diff --git a/Analyser/Static/AmstradCPC/Target.hpp b/Analyser/Static/AmstradCPC/Target.hpp index e30600d24..6f708d4b0 100644 --- a/Analyser/Static/AmstradCPC/Target.hpp +++ b/Analyser/Static/AmstradCPC/Target.hpp @@ -9,6 +9,8 @@ #ifndef Analyser_Static_AmstradCPC_Target_h #define Analyser_Static_AmstradCPC_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,15 +18,17 @@ namespace Analyser { namespace Static { namespace AmstradCPC { -struct Target: public ::Analyser::Static::Target { - enum class Model { - CPC464, - CPC664, - CPC6128 - }; - +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(Model, CPC464, CPC664, CPC6128); Model model = Model::CPC464; std::string loading_command; + + Target() : Analyser::Static::Target(Machine::AmstradCPC) { + if(needs_declare()) { + DeclareField(model); + AnnounceEnum(Model); + } + } }; } diff --git a/Analyser/Static/AppleII/StaticAnalyser.cpp b/Analyser/Static/AppleII/StaticAnalyser.cpp index b0b280fb1..71791a743 100644 --- a/Analyser/Static/AppleII/StaticAnalyser.cpp +++ b/Analyser/Static/AppleII/StaticAnalyser.cpp @@ -11,7 +11,6 @@ Analyser::Static::TargetList Analyser::Static::AppleII::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { auto target = std::make_unique(); - target->machine = Machine::AppleII; target->media = media; if(!target->media.disks.empty()) diff --git a/Analyser/Static/AppleII/Target.hpp b/Analyser/Static/AppleII/Target.hpp index 3c97abfcb..988e7d4ee 100644 --- a/Analyser/Static/AppleII/Target.hpp +++ b/Analyser/Static/AppleII/Target.hpp @@ -9,27 +9,38 @@ #ifndef Target_h #define Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" namespace Analyser { namespace Static { namespace AppleII { -struct Target: public ::Analyser::Static::Target { - enum class Model { +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(Model, II, IIplus, IIe, EnhancedIIe - }; - enum class DiskController { + ); + ReflectableEnum(DiskController, None, SixteenSector, ThirteenSector - }; + ); Model model = Model::IIe; DiskController disk_controller = DiskController::None; + + Target() : Analyser::Static::Target(Machine::AppleII) { + if(needs_declare()) { + DeclareField(model); + DeclareField(disk_controller); + AnnounceEnum(Model); + AnnounceEnum(DiskController); + } + } }; } diff --git a/Analyser/Static/Atari2600/StaticAnalyser.cpp b/Analyser/Static/Atari2600/StaticAnalyser.cpp index cd756e59e..9b627de38 100644 --- a/Analyser/Static/Atari2600/StaticAnalyser.cpp +++ b/Analyser/Static/Atari2600/StaticAnalyser.cpp @@ -184,7 +184,6 @@ static void DeterminePagingForCartridge(Target &target, const Storage::Cartridge Analyser::Static::TargetList Analyser::Static::Atari2600::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { // TODO: sanity checking; is this image really for an Atari 2600? auto target = std::make_unique(); - target->machine = Machine::Atari2600; target->confidence = 0.5; target->media.cartridges = media.cartridges; target->paging_model = Target::PagingModel::None; diff --git a/Analyser/Static/Atari2600/Target.hpp b/Analyser/Static/Atari2600/Target.hpp index 4bd4c5384..c372656ff 100644 --- a/Analyser/Static/Atari2600/Target.hpp +++ b/Analyser/Static/Atari2600/Target.hpp @@ -34,6 +34,8 @@ struct Target: public ::Analyser::Static::Target { // TODO: shouldn't these be properties of the cartridge? PagingModel paging_model = PagingModel::None; bool uses_superchip = false; + + Target() : Analyser::Static::Target(Machine::Atari2600) {} }; } diff --git a/Analyser/Static/AtariST/StaticAnalyser.cpp b/Analyser/Static/AtariST/StaticAnalyser.cpp index b46d0e17d..e2896aa6d 100644 --- a/Analyser/Static/AtariST/StaticAnalyser.cpp +++ b/Analyser/Static/AtariST/StaticAnalyser.cpp @@ -16,9 +16,8 @@ Analyser::Static::TargetList Analyser::Static::AtariST::GetTargets(const Media & // As there is at least one usable media image, wave it through. Analyser::Static::TargetList targets; - using Target = Analyser::Static::Target; - auto *target = new Target; - target->machine = Analyser::Machine::AtariST; + using Target = Analyser::Static::AtariST::Target; + auto *target = new Target(); target->media = media; targets.push_back(std::unique_ptr(target)); diff --git a/Analyser/Static/AtariST/Target.hpp b/Analyser/Static/AtariST/Target.hpp index f9c6f2828..fd025baf8 100644 --- a/Analyser/Static/AtariST/Target.hpp +++ b/Analyser/Static/AtariST/Target.hpp @@ -9,11 +9,15 @@ #ifndef Analyser_Static_AtariST_Target_h #define Analyser_Static_AtariST_Target_h +#include "../../../Reflection/Struct.hpp" +#include "../StaticAnalyser.hpp" + namespace Analyser { namespace Static { namespace AtariST { -struct Target: public ::Analyser::Static::Target { +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + Target() : Analyser::Static::Target(Machine::AtariST) {} }; } diff --git a/Analyser/Static/Coleco/StaticAnalyser.cpp b/Analyser/Static/Coleco/StaticAnalyser.cpp index 25f69e926..34abed780 100644 --- a/Analyser/Static/Coleco/StaticAnalyser.cpp +++ b/Analyser/Static/Coleco/StaticAnalyser.cpp @@ -54,8 +54,7 @@ static std::vector> Analyser::Static::TargetList Analyser::Static::Coleco::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { TargetList targets; - auto target = std::make_unique(); - target->machine = Machine::ColecoVision; + auto target = std::make_unique(Machine::ColecoVision); target->confidence = 1.0f - 1.0f / 32768.0f; target->media.cartridges = ColecoCartridgesFrom(media.cartridges); if(!target->media.empty()) diff --git a/Analyser/Static/Commodore/Target.hpp b/Analyser/Static/Commodore/Target.hpp index 89ad4538e..9da8506ec 100644 --- a/Analyser/Static/Commodore/Target.hpp +++ b/Analyser/Static/Commodore/Target.hpp @@ -9,6 +9,8 @@ #ifndef Analyser_Static_Commodore_Target_h #define Analyser_Static_Commodore_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,20 +18,20 @@ namespace Analyser { namespace Static { namespace Commodore { -struct Target: public ::Analyser::Static::Target { +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { enum class MemoryModel { Unexpanded, EightKB, ThirtyTwoKB }; - enum class Region { + ReflectableEnum(Region, American, Danish, Japanese, European, Swedish - }; + ); /// Maps from a named memory model to a bank enabled/disabled set. void set_memory_model(MemoryModel memory_model) { @@ -54,6 +56,19 @@ struct Target: public ::Analyser::Static::Target { Region region = Region::European; bool has_c1540 = false; std::string loading_command; + + Target() : Analyser::Static::Target(Machine::Vic20) { + if(needs_declare()) { + DeclareField(enabled_ram.bank0); + DeclareField(enabled_ram.bank1); + DeclareField(enabled_ram.bank2); + DeclareField(enabled_ram.bank3); + DeclareField(enabled_ram.bank5); + DeclareField(region); + DeclareField(has_c1540); + AnnounceEnum(Region); + } + } }; } diff --git a/Analyser/Static/DiskII/StaticAnalyser.cpp b/Analyser/Static/DiskII/StaticAnalyser.cpp index 5e8824ac2..56c7c5d3f 100644 --- a/Analyser/Static/DiskII/StaticAnalyser.cpp +++ b/Analyser/Static/DiskII/StaticAnalyser.cpp @@ -21,7 +21,6 @@ namespace { Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) { using Target = Analyser::Static::AppleII::Target; auto *target = new Target; - target->machine = Analyser::Machine::AppleII; if(sector_zero && sector_zero->encoding == Storage::Encodings::AppleGCR::Sector::Encoding::FiveAndThree) { target->disk_controller = Target::DiskController::ThirteenSector; @@ -35,7 +34,6 @@ Analyser::Static::Target *AppleTarget(const Storage::Encodings::AppleGCR::Sector Analyser::Static::Target *OricTarget(const Storage::Encodings::AppleGCR::Sector *sector_zero) { using Target = Analyser::Static::Oric::Target; auto *target = new Target; - target->machine = Analyser::Machine::Oric; target->rom = Target::ROM::Pravetz; target->disk_interface = Target::DiskInterface::Pravetz; target->loading_command = "CALL 800\n"; diff --git a/Analyser/Static/MSX/StaticAnalyser.cpp b/Analyser/Static/MSX/StaticAnalyser.cpp index a20350ca9..7342a793c 100644 --- a/Analyser/Static/MSX/StaticAnalyser.cpp +++ b/Analyser/Static/MSX/StaticAnalyser.cpp @@ -35,7 +35,6 @@ static std::unique_ptr CartridgeTarget( } auto target = std::make_unique(); - target->machine = Analyser::Machine::MSX; target->confidence = confidence; if(type == Analyser::Static::MSX::Cartridge::Type::None) { @@ -295,7 +294,6 @@ Analyser::Static::TargetList Analyser::Static::MSX::GetTargets(const Media &medi target->has_disk_drive = !media.disks.empty(); if(!target->media.empty()) { - target->machine = Machine::MSX; target->confidence = 0.5; destination.push_back(std::move(target)); } diff --git a/Analyser/Static/MSX/Target.hpp b/Analyser/Static/MSX/Target.hpp index fa4e379bc..f0d7a9f6a 100644 --- a/Analyser/Static/MSX/Target.hpp +++ b/Analyser/Static/MSX/Target.hpp @@ -9,6 +9,8 @@ #ifndef Analyser_Static_MSX_Target_h #define Analyser_Static_MSX_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,15 +18,24 @@ namespace Analyser { namespace Static { namespace MSX { -struct Target: public ::Analyser::Static::Target { +struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { bool has_disk_drive = false; std::string loading_command; - enum class Region { + ReflectableEnum(Region, Japan, USA, Europe - } region = Region::USA; + ); + Region region = Region::USA; + + Target(): Analyser::Static::Target(Machine::MSX) { + if(needs_declare()) { + DeclareField(has_disk_drive); + DeclareField(region); + AnnounceEnum(Region); + } + } }; } diff --git a/Analyser/Static/Macintosh/StaticAnalyser.cpp b/Analyser/Static/Macintosh/StaticAnalyser.cpp index 1dc76db84..d14a69554 100644 --- a/Analyser/Static/Macintosh/StaticAnalyser.cpp +++ b/Analyser/Static/Macintosh/StaticAnalyser.cpp @@ -18,7 +18,6 @@ Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media using Target = Analyser::Static::Macintosh::Target; auto *target = new Target; - target->machine = Analyser::Machine::Macintosh; target->media = media; targets.push_back(std::unique_ptr(target)); diff --git a/Analyser/Static/Macintosh/Target.hpp b/Analyser/Static/Macintosh/Target.hpp index 5d70748f0..f00cb4a24 100644 --- a/Analyser/Static/Macintosh/Target.hpp +++ b/Analyser/Static/Macintosh/Target.hpp @@ -9,19 +9,25 @@ #ifndef Analyser_Static_Macintosh_Target_h #define Analyser_Static_Macintosh_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" +#include "../StaticAnalyser.hpp" + namespace Analyser { namespace Static { namespace Macintosh { -struct Target: public ::Analyser::Static::Target { - enum class Model { - Mac128k, - Mac512k, - Mac512ke, - MacPlus - }; - +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(Model, Mac128k, Mac512k, Mac512ke, MacPlus); Model model = Model::MacPlus; + + Target() : Analyser::Static::Target(Machine::Macintosh) { + // Boilerplate for declaring fields and potential values. + if(needs_declare()) { + DeclareField(model); + AnnounceEnum(Model); + } + } }; } diff --git a/Analyser/Static/Oric/StaticAnalyser.cpp b/Analyser/Static/Oric/StaticAnalyser.cpp index 3a39c4eb4..22d607f29 100644 --- a/Analyser/Static/Oric/StaticAnalyser.cpp +++ b/Analyser/Static/Oric/StaticAnalyser.cpp @@ -147,7 +147,6 @@ bool is_bd500(Storage::Encodings::MFM::Parser &parser) { Analyser::Static::TargetList Analyser::Static::Oric::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) { auto target = std::make_unique(); - target->machine = Machine::Oric; target->confidence = 0.5; int basic10_votes = 0; diff --git a/Analyser/Static/Oric/Target.hpp b/Analyser/Static/Oric/Target.hpp index 24a5d1371..66aead3dd 100644 --- a/Analyser/Static/Oric/Target.hpp +++ b/Analyser/Static/Oric/Target.hpp @@ -9,6 +9,8 @@ #ifndef Analyser_Static_Oric_Target_h #define Analyser_Static_Oric_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,25 +18,34 @@ namespace Analyser { namespace Static { namespace Oric { -struct Target: public ::Analyser::Static::Target { - enum class ROM { +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(ROM, BASIC10, BASIC11, Pravetz - }; + ); - enum class DiskInterface { + ReflectableEnum(DiskInterface, + None, Microdisc, Pravetz, Jasmin, - BD500, - None - }; + BD500 + ); ROM rom = ROM::BASIC11; DiskInterface disk_interface = DiskInterface::None; std::string loading_command; bool should_start_jasmin = false; + + Target(): Analyser::Static::Target(Machine::Oric) { + if(needs_declare()) { + DeclareField(rom); + DeclareField(disk_interface); + AnnounceEnum(ROM); + AnnounceEnum(DiskInterface); + } + } }; } diff --git a/Analyser/Static/Sega/StaticAnalyser.cpp b/Analyser/Static/Sega/StaticAnalyser.cpp index 37b9d54d4..7e0db0c84 100644 --- a/Analyser/Static/Sega/StaticAnalyser.cpp +++ b/Analyser/Static/Sega/StaticAnalyser.cpp @@ -20,8 +20,6 @@ Analyser::Static::TargetList Analyser::Static::Sega::GetTargets(const Media &med TargetList targets; auto target = std::make_unique(); - target->machine = Machine::MasterSystem; - // Files named .sg are treated as for the SG1000; otherwise assume a Master System. if(file_name.size() >= 2 && *(file_name.end() - 2) == 's' && *(file_name.end() - 1) == 'g') { target->model = Target::Model::SG1000; diff --git a/Analyser/Static/Sega/Target.hpp b/Analyser/Static/Sega/Target.hpp index f95766ab6..3eceb774d 100644 --- a/Analyser/Static/Sega/Target.hpp +++ b/Analyser/Static/Sega/Target.hpp @@ -9,23 +9,27 @@ #ifndef Analyser_Static_Sega_Target_h #define Analyser_Static_Sega_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" +#include "../StaticAnalyser.hpp" + namespace Analyser { namespace Static { namespace Sega { -struct Target: public ::Analyser::Static::Target { +struct Target: public Analyser::Static::Target, public Reflection::StructImpl { enum class Model { SG1000, MasterSystem, MasterSystem2, }; - enum class Region { + ReflectableEnum(Region, Japan, USA, Europe, Brazil - }; + ); enum class PagingScheme { Sega, @@ -35,6 +39,13 @@ struct Target: public ::Analyser::Static::Target { Model model = Model::MasterSystem; Region region = Region::Japan; PagingScheme paging_scheme = PagingScheme::Sega; + + Target() : Analyser::Static::Target(Machine::MasterSystem) { + if(needs_declare()) { + DeclareField(region); + AnnounceEnum(Region); + } + } }; #define is_master_system(v) v >= Analyser::Static::Sega::Target::Model::MasterSystem diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index 5981fc435..e806ae042 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -35,6 +35,16 @@ struct Media { bool empty() const { return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty(); } + + Media &operator +=(const Media &rhs) { +#define append(name) name.insert(name.end(), rhs.name.begin(), rhs.name.end()); + append(disks); + append(tapes); + append(cartridges); + append(mass_storage_devices); +#undef append + return *this; + } }; /*! @@ -42,11 +52,12 @@ struct Media { and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness. */ struct Target { + Target(Machine machine) : machine(machine) {} virtual ~Target() {} Machine machine; Media media; - float confidence; + float confidence = 0.0f; }; typedef std::vector> TargetList; diff --git a/Analyser/Static/ZX8081/Target.hpp b/Analyser/Static/ZX8081/Target.hpp index 5671ab1d6..c7dd7e60d 100644 --- a/Analyser/Static/ZX8081/Target.hpp +++ b/Analyser/Static/ZX8081/Target.hpp @@ -9,6 +9,8 @@ #ifndef Analyser_Static_ZX8081_Target_h #define Analyser_Static_ZX8081_Target_h +#include "../../../Reflection/Enum.hpp" +#include "../../../Reflection/Struct.hpp" #include "../StaticAnalyser.hpp" #include @@ -16,17 +18,26 @@ namespace Analyser { namespace Static { namespace ZX8081 { -struct Target: public ::Analyser::Static::Target { - enum class MemoryModel { +struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { + ReflectableEnum(MemoryModel, Unexpanded, SixteenKB, SixtyFourKB - }; + ); MemoryModel memory_model = MemoryModel::Unexpanded; bool is_ZX81 = false; bool ZX80_uses_ZX81_ROM = false; std::string loading_command; + + Target(): Analyser::Static::Target(Machine::ZX8081) { + if(needs_declare()) { + DeclareField(memory_model); + DeclareField(is_ZX81); + DeclareField(ZX80_uses_ZX81_ROM); + AnnounceEnum(MemoryModel); + } + } }; } diff --git a/Components/6560/6560.hpp b/Components/6560/6560.hpp index 05e1fb2e0..0f7013dc9 100644 --- a/Components/6560/6560.hpp +++ b/Components/6560/6560.hpp @@ -87,6 +87,7 @@ template class MOS6560 { void set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } Outputs::Display::ScanStatus get_scaled_scan_status() const { return crt_.get_scaled_scan_status() / 4.0f; } void set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() { return crt_.get_display_type(); } Outputs::Speaker::Speaker *get_speaker() { return &speaker_; } void set_high_frequency_cutoff(float cutoff) { diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b6d672fc0..9ed2fbfb5 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -129,6 +129,10 @@ void TMS9918::set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } +Outputs::Display::DisplayType TMS9918::get_display_type() { + return crt_.get_display_type(); +} + void Base::LineBuffer::reset_sprite_collection() { sprites_stopped = false; active_sprite_slot = 0; diff --git a/Components/9918/9918.hpp b/Components/9918/9918.hpp index 2cca01693..4f2530bf3 100644 --- a/Components/9918/9918.hpp +++ b/Components/9918/9918.hpp @@ -50,6 +50,9 @@ class TMS9918: public Base { /*! Sets the type of display the CRT will request. */ void set_display_type(Outputs::Display::DisplayType); + /*! Gets the type of display the CRT will request. */ + Outputs::Display::DisplayType get_display_type(); + /*! Runs the VCP for the number of cycles indicate; it is an implicit assumption of the code that the input clock rate is 3579545 Hz, the NTSC colour clock rate. diff --git a/Configurable/Configurable.cpp b/Configurable/Configurable.cpp deleted file mode 100644 index 1834d3eb4..000000000 --- a/Configurable/Configurable.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// Configurable.cpp -// Clock Signal -// -// Created by Thomas Harte on 18/11/2017. -// Copyright 2017 Thomas Harte. All rights reserved. -// - -#include "Configurable.hpp" - -using namespace Configurable; - -ListSelection *BooleanSelection::list_selection() { - return new ListSelection(value ? "yes" : "no"); -} - -ListSelection *ListSelection::list_selection() { - return new ListSelection(value); -} - -BooleanSelection *ListSelection::boolean_selection() { - return new BooleanSelection(value != "no" && value != "n" && value != "false" && value != "f"); -} - -BooleanSelection *BooleanSelection::boolean_selection() { - return new BooleanSelection(value); -} diff --git a/Configurable/Configurable.hpp b/Configurable/Configurable.hpp index 833989951..d1aebbdfa 100644 --- a/Configurable/Configurable.hpp +++ b/Configurable/Configurable.hpp @@ -9,89 +9,37 @@ #ifndef Configurable_h #define Configurable_h -#include +#include "../Reflection/Struct.hpp" + #include -#include -#include 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) {} - - virtual bool operator==(const Option &rhs) { - return long_name == rhs.long_name && short_name == rhs.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 options; - ListOption(const std::string &long_name, const std::string &short_name, const std::vector &options) : Option(long_name, short_name), options(options) {} - - virtual bool operator==(const Option &rhs) { - const ListOption *list_rhs = dynamic_cast(&rhs); - if(!list_rhs) return false; - return long_name == rhs.long_name && short_name == rhs.short_name && options == list_rhs->options; - } -}; - -struct BooleanSelection; -struct ListSelection; - -/*! - Selections are responses to Options. -*/ -struct Selection { - virtual ~Selection() {} - virtual ListSelection *list_selection() = 0; - virtual BooleanSelection *boolean_selection() = 0; -}; - -struct BooleanSelection: public Selection { - bool value; - - ListSelection *list_selection(); - BooleanSelection *boolean_selection(); - BooleanSelection(bool value) : value(value) {} -}; - -struct ListSelection: public Selection { - std::string value; - - ListSelection *list_selection(); - BooleanSelection *boolean_selection(); - ListSelection(const std::string value) : value(value) {} -}; - -using SelectionSet = std::map>; - -/*! - A Configuratble provides the options that it responds to and allows selections to be set. + A Configurable::Device provides a reflective struct listing the available runtime options for this machine. + You can ordinarily either get or set a machine's current options, or else construct a new instance of + its options with one of the OptionsTypes defined below. */ struct Device { - virtual std::vector> 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; + /// Sets the current options. The caller must ensure that the object passed in is either an instance of the machine's + /// Options struct, or else was previously returned by get_options. + virtual void set_options(const std::unique_ptr &options) = 0; + + /// @returns An options object + virtual std::unique_ptr get_options() = 0; }; -template 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(selection->second.get()); -} +/*! + 'Accurate' options should correspond to the way that this device was usually used during its lifespan. + E.g. a ColecoVision might accurately be given composite output. + + 'User-friendly' options should be more like those that a user today might most expect from an emulator. + E.g. the ColecoVision might bump itself up to S-Video output. +*/ +enum class OptionsType { + Accurate, + UserFriendly +}; } diff --git a/Configurable/StandardOptions.cpp b/Configurable/StandardOptions.cpp index 1cb0badf2..1dce510b7 100644 --- a/Configurable/StandardOptions.cpp +++ b/Configurable/StandardOptions.cpp @@ -8,27 +8,7 @@ #include "StandardOptions.hpp" -namespace { - -/*! - Appends a Boolean selection of @c selection for option @c name to @c selection_set. -*/ -void append_bool(Configurable::SelectionSet &selection_set, const std::string &name, bool selection) { - selection_set[name] = std::make_unique(selection); -} - -/*! - Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found. -*/ -bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) { - auto selection = Configurable::selection(selections_by_option, name); - if(!selection) return false; - result = selection->value; - return true; -} - -} - +/* // MARK: - Standard option list builder std::vector> Configurable::standard_options(Configurable::StandardOptions mask) { std::vector> options; @@ -105,4 +85,4 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) { return get_bool(selections_by_option, "quickboot", result); -} +}*/ diff --git a/Configurable/StandardOptions.hpp b/Configurable/StandardOptions.hpp index fa042ee65..abc7c284c 100644 --- a/Configurable/StandardOptions.hpp +++ b/Configurable/StandardOptions.hpp @@ -9,87 +9,56 @@ #ifndef StandardOptions_hpp #define StandardOptions_hpp -#include "Configurable.hpp" +#include "../Reflection/Enum.hpp" namespace Configurable { -enum StandardOptions { - DisplayRGB = (1 << 0), - DisplaySVideo = (1 << 1), - DisplayCompositeColour = (1 << 2), - DisplayCompositeMonochrome = (1 << 3), - QuickLoadTape = (1 << 4), - AutomaticTapeMotorControl = (1 << 5), - QuickBoot = (1 << 6), -}; - -enum class Display { +ReflectableEnum(Display, RGB, SVideo, CompositeColour, CompositeMonochrome +); + +//=== +// From here downward are a bunch of templates for individual option flags. +// Using them saves you marginally in syntax, but the primary gain is to +// ensure unified property naming. +//=== + +template class DisplayOption { + public: + Configurable::Display output; + DisplayOption(Configurable::Display output) : output(output) {} + + protected: + void declare_display_option() { + static_cast(this)->declare(&output, "output"); + AnnounceEnumNS(Configurable, Display); + } }; -/*! - @returns An option list comprised of the standard names for all the options indicated by @c mask. -*/ -std::vector> standard_options(StandardOptions mask); +template class QuickloadOption { + public: + bool quickload; + QuickloadOption(bool quickload) : quickload(quickload) {} -/*! - Appends to @c selection_set a selection of @c selection for QuickLoadTape. -*/ -void append_quick_load_tape_selection(SelectionSet &selection_set, bool selection); + protected: + void declare_quickload_option() { + static_cast(this)->declare(&quickload, "quickload"); + } +}; -/*! - Appends to @c selection_set a selection of @c selection for AutomaticTapeMotorControl. -*/ -void append_automatic_tape_motor_control_selection(SelectionSet &selection_set, bool selection); +template class QuickbootOption { + public: + bool quickboot; + QuickbootOption(bool quickboot) : quickboot(quickboot) {} -/*! - Appends to @c selection_set a selection of @c selection for DisplayRGBComposite. -*/ -void append_display_selection(SelectionSet &selection_set, Display selection); - -/*! - Appends to @c selection_set a selection of @c selection for QuickBoot. -*/ -void append_quick_boot_selection(SelectionSet &selection_set, bool selection); - -/*! - Attempts to discern a QuickLoadTape selection from @c selections_by_option. - - @param selections_by_option The user selections. - @param result The location to which the selection will be stored if found. - @returns @c true if a selection is found; @c false otherwise. -*/ -bool get_quick_load_tape(const SelectionSet &selections_by_option, bool &result); - -/*! - Attempts to discern an AutomaticTapeMotorControl selection from @c selections_by_option. - - @param selections_by_option The user selections. - @param result The location to which the selection will be stored if found. - @returns @c true if a selection is found; @c false otherwise. -*/ -bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_by_option, bool &result); - -/*! - Attempts to discern a display RGB/composite selection from @c selections_by_option. - - @param selections_by_option The user selections. - @param result The location to which the selection will be stored if found. - @returns @c true if a selection is found; @c false otherwise. -*/ -bool get_display(const SelectionSet &selections_by_option, Display &result); - -/*! - Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option. - - @param selections_by_option The user selections. - @param result The location to which the selection will be stored if found. - @returns @c true if a selection is found; @c false otherwise. -*/ -bool get_quick_boot(const SelectionSet &selections_by_option, bool &result); + protected: + void declare_quickboot_option() { + static_cast(this)->declare(&quickboot, "quickboot"); + } +}; } diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index ec6565d74..85910387b 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -40,12 +40,6 @@ namespace AmstradCPC { -std::vector> get_options() { - return Configurable::standard_options( - Configurable::StandardOptions(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) - ); -} - /*! Models the CPC's interrupt timer. Inputs are vsync, hsync, interrupt acknowledge and reset, and its output is simply yes or no on whether an interupt is currently requested. Internally it uses a counter with a period @@ -354,6 +348,11 @@ class CRTCBusHandler { crt_.set_display_type(display_type); } + /// Gets the type of display. + Outputs::Display::DisplayType get_display_type() { + return crt_.get_display_type(); + } + /*! Sets the next video mode. Per the documentation, mode changes take effect only at the end of line, not immediately. So next means "as of the end of this line". @@ -1045,6 +1044,11 @@ template class ConcreteMachine: crtc_bus_handler_.set_display_type(display_type); } + /// A CRTMachine function; gets the output display type. + Outputs::Display::DisplayType get_display_type() { + return crtc_bus_handler_.get_display_type(); + } + /// @returns the speaker in use. Outputs::Speaker::Speaker *get_speaker() final { return ay_.get_speaker(); @@ -1114,27 +1118,15 @@ template class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return AmstradCPC::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + void set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); } // MARK: - Joysticks diff --git a/Machines/AmstradCPC/AmstradCPC.hpp b/Machines/AmstradCPC/AmstradCPC.hpp index e523a69c0..56c1186a2 100644 --- a/Machines/AmstradCPC/AmstradCPC.hpp +++ b/Machines/AmstradCPC/AmstradCPC.hpp @@ -10,17 +10,14 @@ #define AmstradCPC_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" #include -#include namespace AmstradCPC { -/// @returns The options available for an Amstrad CPC. -std::vector> get_options(); - /*! Models an Amstrad CPC. */ @@ -30,6 +27,18 @@ class Machine { /// Creates and returns an Amstrad CPC. static Machine *AmstradCPC(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + /// Defines the runtime options available for an Amstrad CPC. + class Options: public Reflection::StructImpl, public Configurable::DisplayOption { + friend Configurable::DisplayOption; + public: + Options(Configurable::OptionsType type) : Configurable::DisplayOption(Configurable::Display::RGB) { + if(needs_declare()) { + declare_display_option(); + limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1); + } + } + }; }; } diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp index 3c6c25c44..7cff092cc 100644 --- a/Machines/Apple/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -37,12 +37,6 @@ namespace Apple { namespace II { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplayCompositeMonochrome | Configurable::DisplayCompositeColour) - ); -} - #define is_iie() ((model == Analyser::Static::AppleII::Target::Model::IIe) || (model == Analyser::Static::AppleII::Target::Model::EnhancedIIe)) template class ConcreteMachine: @@ -430,6 +424,10 @@ template class ConcreteMachine: video_.set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return video_.get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -866,25 +864,15 @@ template class ConcreteMachine: } // MARK:: Configuration options. - std::vector> get_options() final { - return Apple::II::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - return get_accurate_selections(); + void set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); } // MARK: MediaTarget diff --git a/Machines/Apple/AppleII/AppleII.hpp b/Machines/Apple/AppleII/AppleII.hpp index ad4fededd..1e1434f63 100644 --- a/Machines/Apple/AppleII/AppleII.hpp +++ b/Machines/Apple/AppleII/AppleII.hpp @@ -10,24 +10,33 @@ #define AppleII_hpp #include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" #include "../../../Analyser/Static/StaticAnalyser.hpp" #include "../../ROMMachine.hpp" #include -#include namespace Apple { namespace II { -/// @returns The options available for an Apple II. -std::vector> get_options(); - class Machine { public: virtual ~Machine(); /// Creates and returns an AppleII. static Machine *AppleII(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + /// Defines the runtime options available for an Apple II. + class Options: public Reflection::StructImpl, public Configurable::DisplayOption { + friend Configurable::DisplayOption; + public: + Options(Configurable::OptionsType type) : Configurable::DisplayOption(Configurable::Display::CompositeColour) { + if(needs_declare()) { + declare_display_option(); + limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1); + } + } + }; }; } diff --git a/Machines/Apple/AppleII/Video.cpp b/Machines/Apple/AppleII/Video.cpp index a5b0b38b2..398eb91c9 100644 --- a/Machines/Apple/AppleII/Video.cpp +++ b/Machines/Apple/AppleII/Video.cpp @@ -55,6 +55,10 @@ void VideoBase::set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } +Outputs::Display::DisplayType VideoBase::get_display_type() { + return crt_.get_display_type(); +} + /* Rote setters and getters. */ diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index e227bc656..cd005742f 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -46,6 +46,9 @@ class VideoBase { /// Sets the type of output. void set_display_type(Outputs::Display::DisplayType); + /// Gets the type of output. + Outputs::Display::DisplayType get_display_type(); + /* Descriptions for the setters below are taken verbatim from the Apple IIe Technical Reference. Addresses are the conventional diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index b9ceedf4c..890db6619 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -21,6 +21,7 @@ #include "../../KeyboardMachine.hpp" #include "../../MediaTarget.hpp" #include "../../MouseMachine.hpp" +#include "../../../Configurable/Configurable.hpp" #include "../../../Inputs/QuadratureMouse/QuadratureMouse.hpp" #include "../../../Outputs/Log.hpp" @@ -56,12 +57,6 @@ constexpr int CLOCK_RATE = 7833600; namespace Apple { namespace Macintosh { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::QuickBoot) - ); -} - template class ConcreteMachine: public Machine, public CRTMachine::Machine, @@ -522,37 +517,31 @@ template class ConcreteMachin } // MARK: - Configuration options. - std::vector> get_options() final { - return Apple::Macintosh::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->quickboot = quickboot_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quick_boot; - if(Configurable::get_quick_boot(selections_by_option, quick_boot)) { - if(quick_boot) { - // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the - // test at $E00. TODO: adapt as(/if?) necessary for other Macs. - ram_[0x02ae] = 0x40; - ram_[0x02af] = 0x00; - ram_[0x02b0] = 0x00; - ram_[0x02b1] = 0x00; - } + void set_options(const std::unique_ptr &str) final { + // TODO: should this really be a runtime option? + // It should probably be a construction option. + + const auto options = dynamic_cast(str.get()); + quickboot_ = options->quickboot; + if(quickboot_) { + // Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the + // test at $E00. TODO: adapt as(/if?) necessary for other Macs. + ram_[0x02ae] = 0x40; + ram_[0x02af] = 0x00; + ram_[0x02b0] = 0x00; + ram_[0x02b1] = 0x00; } } - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_boot_selection(selection_set, false); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_boot_selection(selection_set, true); - return selection_set; - } - private: + bool quickboot_ = false; + void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final { scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None; } diff --git a/Machines/Apple/Macintosh/Macintosh.hpp b/Machines/Apple/Macintosh/Macintosh.hpp index 58623ea7c..5c42b555f 100644 --- a/Machines/Apple/Macintosh/Macintosh.hpp +++ b/Machines/Apple/Macintosh/Macintosh.hpp @@ -10,20 +10,30 @@ #define Macintosh_hpp #include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" #include "../../../Analyser/Static/StaticAnalyser.hpp" #include "../../ROMMachine.hpp" namespace Apple { namespace Macintosh { -std::vector> get_options(); - class Machine { public: virtual ~Machine(); /// Creates and returns a Macintosh. static Machine *Macintosh(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::QuickbootOption { + friend Configurable::QuickbootOption; + public: + Options(Configurable::OptionsType type) : + Configurable::QuickbootOption(type == Configurable::OptionsType::UserFriendly) { + if(needs_declare()) { + declare_quickboot_option(); + } + } + }; }; diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 7be6a7747..504e43400 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -42,12 +42,6 @@ namespace Atari { namespace ST { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) - ); -} - constexpr int CLOCK_RATE = 8021247; using Target = Analyser::Static::Target; @@ -149,6 +143,10 @@ class ConcreteMachine: video_->set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return video_->get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -678,27 +676,15 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return Atari::ST::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); } }; diff --git a/Machines/Atari/ST/AtariST.hpp b/Machines/Atari/ST/AtariST.hpp index 6345620cd..8f3274780 100644 --- a/Machines/Atari/ST/AtariST.hpp +++ b/Machines/Atari/ST/AtariST.hpp @@ -10,20 +10,32 @@ #define AtariST_hpp #include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" #include "../../../Analyser/Static/StaticAnalyser.hpp" #include "../../ROMMachine.hpp" +#include + namespace Atari { namespace ST { -/// @returns The options available for an Atari ST. -std::vector> get_options(); - class Machine { public: virtual ~Machine(); static Machine *AtariST(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption { + friend Configurable::DisplayOption; + public: + Options(Configurable::OptionsType type) : Configurable::DisplayOption( + type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour) { + if(needs_declare()) { + declare_display_option(); + limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1); + } + } + }; }; } diff --git a/Machines/Atari/ST/Video.cpp b/Machines/Atari/ST/Video.cpp index 134f5ed87..5c3ba851f 100644 --- a/Machines/Atari/ST/Video.cpp +++ b/Machines/Atari/ST/Video.cpp @@ -146,6 +146,10 @@ void Video::set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } +Outputs::Display::DisplayType Video::get_display_type() { + return crt_.get_display_type(); +} + void Video::run_for(HalfCycles duration) { int integer_duration = int(duration.as_integral()); assert(integer_duration >= 0); diff --git a/Machines/Atari/ST/Video.hpp b/Machines/Atari/ST/Video.hpp index 34a0f94c0..851d8674c 100644 --- a/Machines/Atari/ST/Video.hpp +++ b/Machines/Atari/ST/Video.hpp @@ -54,6 +54,11 @@ class Video { */ void set_display_type(Outputs::Display::DisplayType); + /*! + Gets the type of output. + */ + Outputs::Display::DisplayType get_display_type(); + /*! Produces the next @c duration period of pixels. */ diff --git a/Machines/CRTMachine.hpp b/Machines/CRTMachine.hpp index 1e57575c1..fc86a7379 100644 --- a/Machines/CRTMachine.hpp +++ b/Machines/CRTMachine.hpp @@ -181,10 +181,28 @@ class Machine { } /*! - Forwards the video signal to the target returned by get_crt(). + Maps back from Outputs::Display::VideoSignal to Configurable::Display, + calling @c get_display_type for the input. + */ + Configurable::Display get_video_signal_configurable() { + switch(get_display_type()) { + default: + case Outputs::Display::DisplayType::RGB: return Configurable::Display::RGB; + case Outputs::Display::DisplayType::SVideo: return Configurable::Display::SVideo; + case Outputs::Display::DisplayType::CompositeColour: return Configurable::Display::CompositeColour; + case Outputs::Display::DisplayType::CompositeMonochrome: return Configurable::Display::CompositeMonochrome; + } + } + + /*! + Sets the display type. */ virtual void set_display_type(Outputs::Display::DisplayType display_type) {} + /*! + Gets the display type. + */ + virtual Outputs::Display::DisplayType get_display_type() { return Outputs::Display::DisplayType::RGB; } private: double clock_rate_ = 1.0; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 02aacc6df..5a773f883 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -16,6 +16,7 @@ #include "../CRTMachine.hpp" #include "../JoystickMachine.hpp" +#include "../../Configurable/Configurable.hpp" #include "../../Configurable/StandardOptions.hpp" #include "../../ClockReceiver/ForceInline.hpp" @@ -33,12 +34,6 @@ constexpr int sn76489_divider = 2; namespace Coleco { namespace Vision { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplaySVideo | Configurable::DisplayCompositeColour) - ); -} - class Joystick: public Inputs::ConcreteJoystick { public: Joystick() : @@ -193,6 +188,10 @@ class ConcreteMachine: vdp_->set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return vdp_->get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -368,27 +367,15 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return Coleco::Vision::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::SVideo); - return selection_set; + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); } private: diff --git a/Machines/ColecoVision/ColecoVision.hpp b/Machines/ColecoVision/ColecoVision.hpp index 750e5bcbe..4e5f0ce2c 100644 --- a/Machines/ColecoVision/ColecoVision.hpp +++ b/Machines/ColecoVision/ColecoVision.hpp @@ -10,18 +10,29 @@ #define ColecoVision_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" namespace Coleco { namespace Vision { -std::vector> get_options(); - class Machine { public: virtual ~Machine(); static Machine *ColecoVision(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption { + friend Configurable::DisplayOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::SVideo : Configurable::Display::CompositeColour) { + if(needs_declare()) { + declare_display_option(); + limit_enum(&output, Configurable::Display::SVideo, Configurable::Display::CompositeColour, -1); + } + } + }; }; } diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 94e328c00..4595f3ef3 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -49,12 +49,6 @@ enum ROMSlot { Drive }; -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplaySVideo | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) - ); -} - enum JoystickInput { Up = 0x04, Down = 0x08, @@ -653,6 +647,10 @@ class ConcreteMachine: mos6560_.set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return mos6560_.get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return mos6560_.get_speaker(); } @@ -679,35 +677,19 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return Commodore::Vic20::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = allow_fast_tape_hack_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - allow_fast_tape_hack_ = quickload; - set_use_fast_tape(); - } + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - Configurable::append_display_selection(selection_set, Configurable::Display::SVideo); - return selection_set; + set_video_signal_configurable(options->output); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape(); } void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) final { diff --git a/Machines/Commodore/Vic-20/Vic20.hpp b/Machines/Commodore/Vic-20/Vic20.hpp index ea5e7d0b7..c9583bfa4 100644 --- a/Machines/Commodore/Vic-20/Vic20.hpp +++ b/Machines/Commodore/Vic-20/Vic20.hpp @@ -10,17 +10,17 @@ #define Vic20_hpp #include "../../../Configurable/Configurable.hpp" +#include "../../../Configurable/StandardOptions.hpp" #include "../../../Analyser/Static/StaticAnalyser.hpp" #include "../../ROMMachine.hpp" #include -#include namespace Commodore { namespace Vic20 { /// @returns The options available for a Vic-20. -std::vector> get_options(); +std::unique_ptr get_options(); class Machine { public: @@ -28,6 +28,21 @@ class Machine { /// Creates and returns a Vic-20. static Machine *Vic20(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption, public Configurable::QuickloadOption { + friend Configurable::DisplayOption; + friend Configurable::QuickloadOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::SVideo : Configurable::Display::CompositeColour), + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) { + if(needs_declare()) { + declare_display_option(); + declare_quickload_option(); + limit_enum(&output, Configurable::Display::SVideo, Configurable::Display::CompositeColour, -1); + } + } + }; }; } diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 2c1fc3ac5..b65ec7da9 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -12,6 +12,7 @@ #include "../MediaTarget.hpp" #include "../CRTMachine.hpp" #include "../KeyboardMachine.hpp" +#include "../../Configurable/Configurable.hpp" #include "../../ClockReceiver/ClockReceiver.hpp" #include "../../ClockReceiver/ForceInline.hpp" @@ -32,12 +33,6 @@ namespace Electron { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) - ); -} - class ConcreteMachine: public Machine, public CRTMachine::Machine, @@ -414,6 +409,10 @@ class ConcreteMachine: video_output_.set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return video_output_.get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -448,35 +447,19 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return Electron::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = allow_fast_tape_hack_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - allow_fast_tape_hack_ = quickload; - set_use_fast_tape_hack(); - } + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + set_video_signal_configurable(options->output); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape_hack(); } // MARK: - Activity Source diff --git a/Machines/Electron/Electron.hpp b/Machines/Electron/Electron.hpp index 90cf12993..5fd5daaa6 100644 --- a/Machines/Electron/Electron.hpp +++ b/Machines/Electron/Electron.hpp @@ -10,18 +10,14 @@ #define Electron_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" -#include #include -#include namespace Electron { -/// @returns The options available for an Electron. -std::vector> get_options(); - /*! @abstract Represents an Acorn Electron. @@ -34,6 +30,22 @@ class Machine { /// Creates and returns an Electron. static Machine *Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + /// Defines the runtime options available for an Electron. + class Options: public Reflection::StructImpl, public Configurable::DisplayOption, public Configurable::QuickloadOption { + friend Configurable::DisplayOption; + friend Configurable::QuickloadOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour), + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) { + if(needs_declare()) { + declare_display_option(); + declare_quickload_option(); + limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, Configurable::Display::CompositeMonochrome, -1); + } + } + }; }; } diff --git a/Machines/Electron/Video.cpp b/Machines/Electron/Video.cpp index 88de46806..292e799f1 100644 --- a/Machines/Electron/Video.cpp +++ b/Machines/Electron/Video.cpp @@ -64,6 +64,10 @@ void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) { crt_.set_display_type(display_type); } +Outputs::Display::DisplayType VideoOutput::get_display_type() { + return crt_.get_display_type(); +} + // MARK: - Display update methods void VideoOutput::start_pixel_line() { diff --git a/Machines/Electron/Video.hpp b/Machines/Electron/Video.hpp index af8e57111..aa2b28dec 100644 --- a/Machines/Electron/Video.hpp +++ b/Machines/Electron/Video.hpp @@ -45,6 +45,9 @@ class VideoOutput { /// Sets the type of output. void set_display_type(Outputs::Display::DisplayType); + /// Gets the type of output. + Outputs::Display::DisplayType get_display_type(); + /*! Writes @c value to the register at @c address. May mutate the results of @c get_next_interrupt, @c get_cycles_until_next_ram_availability and @c get_memory_access_range. diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 9f8fccb0d..7fd483d8a 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -37,6 +37,7 @@ #include "../JoystickMachine.hpp" #include "../MediaTarget.hpp" #include "../KeyboardMachine.hpp" +#include "../../Configurable/Configurable.hpp" #include "../../Outputs/Log.hpp" #include "../../Outputs/Speaker/Implementation/CompoundSource.hpp" @@ -51,12 +52,6 @@ namespace MSX { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplaySVideo | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) - ); -} - class AYPortHandler: public GI::AY38910::PortHandler { public: AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) { @@ -290,6 +285,10 @@ class ConcreteMachine: vdp_->set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return vdp_->get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -648,35 +647,19 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return MSX::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = allow_fast_tape_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - allow_fast_tape_ = quickload; - set_use_fast_tape(); - } + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + set_video_signal_configurable(options->output); + allow_fast_tape_ = options->quickload; + set_use_fast_tape(); } // MARK: - Sleeper diff --git a/Machines/MSX/MSX.hpp b/Machines/MSX/MSX.hpp index 1088786e2..902ad5a78 100644 --- a/Machines/MSX/MSX.hpp +++ b/Machines/MSX/MSX.hpp @@ -10,20 +10,32 @@ #define MSX_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" #include -#include namespace MSX { -std::vector> get_options(); - class Machine { public: virtual ~Machine(); static Machine *MSX(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption, public Configurable::QuickloadOption { + friend Configurable::DisplayOption; + friend Configurable::QuickloadOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour), + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) { + if(needs_declare()) { + declare_display_option(); + declare_quickload_option(); + } + } + }; }; } diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index ea4d46cfb..06859d6bd 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -16,6 +16,7 @@ #include "../CRTMachine.hpp" #include "../JoystickMachine.hpp" #include "../KeyboardMachine.hpp" +#include "../../Configurable/Configurable.hpp" #include "../../ClockReceiver/ForceInline.hpp" #include "../../ClockReceiver/JustInTime.hpp" @@ -37,12 +38,6 @@ constexpr int sn76489_divider = 2; namespace Sega { namespace MasterSystem { -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) - ); -} - class Joystick: public Inputs::ConcreteJoystick { public: Joystick() : @@ -187,6 +182,10 @@ class ConcreteMachine: vdp_->set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return vdp_->get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -373,27 +372,15 @@ class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return Sega::MasterSystem::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); } private: diff --git a/Machines/MasterSystem/MasterSystem.hpp b/Machines/MasterSystem/MasterSystem.hpp index b192dfae7..348842be9 100644 --- a/Machines/MasterSystem/MasterSystem.hpp +++ b/Machines/MasterSystem/MasterSystem.hpp @@ -10,18 +10,30 @@ #define MasterSystem_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" +#include + namespace Sega { namespace MasterSystem { -std::vector> get_options(); - class Machine { public: virtual ~Machine(); static Machine *MasterSystem(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption { + friend Configurable::DisplayOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour) { + if(needs_declare()) { + declare_display_option(); + } + } + }; }; } diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 21d54f189..74d92136f 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -50,16 +50,6 @@ enum ROM { BASIC10 = 0, BASIC11, Microdisc, Colour }; -std::vector> get_options() { - return Configurable::standard_options( - static_cast( - Configurable::DisplayRGB | - Configurable::DisplayCompositeColour | - Configurable::DisplayCompositeMonochrome | - Configurable::QuickLoadTape) - ); -} - /*! Models the Oric's keyboard: eight key rows, containing a bitfield of keys set. @@ -565,6 +555,10 @@ template class Co video_output_.set_display_type(display_type); } + Outputs::Display::DisplayType get_display_type() final { + return video_output_.get_display_type(); + } + Outputs::Speaker::Speaker *get_speaker() final { return &speaker_; } @@ -623,34 +617,17 @@ template class Co } // MARK: - Configuration options. - std::vector> get_options() final { - return Oric::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); + options->output = get_video_signal_configurable(); + options->quickload = use_fast_tape_hack_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - set_use_fast_tape_hack(quickload); - } - - Configurable::Display display; - if(Configurable::get_display(selections_by_option, display)) { - set_video_signal_configurable(display); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_display_selection(selection_set, Configurable::Display::CompositeColour); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - Configurable::append_display_selection(selection_set, Configurable::Display::RGB); - return selection_set; + void set_options(const std::unique_ptr &str) final { + const auto options = dynamic_cast(str.get()); + set_video_signal_configurable(options->output); + set_use_fast_tape_hack(options->quickload); } void set_activity_observer(Activity::Observer *observer) final { diff --git a/Machines/Oric/Oric.hpp b/Machines/Oric/Oric.hpp index 8e3977bdf..44167d77d 100644 --- a/Machines/Oric/Oric.hpp +++ b/Machines/Oric/Oric.hpp @@ -10,13 +10,13 @@ #define Oric_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" -namespace Oric { +#include -/// @returns The options available for an Oric. -std::vector> get_options(); +namespace Oric { /*! Models an Oric 1/Atmos with or without a Microdisc. @@ -27,6 +27,20 @@ class Machine { /// Creates and returns an Oric. static Machine *Oric(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher); + + class Options: public Reflection::StructImpl, public Configurable::DisplayOption, public Configurable::QuickloadOption { + friend Configurable::DisplayOption; + friend Configurable::QuickloadOption; + public: + Options(Configurable::OptionsType type) : + Configurable::DisplayOption(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour), + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly) { + if(needs_declare()) { + declare_display_option(); + declare_quickload_option(); + } + } + }; }; } diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 7e228c38e..8fb8926a0 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -67,6 +67,10 @@ void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) { } } +Outputs::Display::DisplayType VideoOutput::get_display_type() { + return crt_.get_display_type(); +} + void VideoOutput::set_scan_target(Outputs::Display::ScanTarget *scan_target) { crt_.set_scan_target(scan_target); } diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index 9d066a808..fe3001efe 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -27,6 +27,7 @@ class VideoOutput { void set_scan_target(Outputs::Display::ScanTarget *scan_target); void set_display_type(Outputs::Display::DisplayType display_type); + Outputs::Display::DisplayType get_display_type(); Outputs::Display::ScanStatus get_scaled_scan_status() const; void register_crt_frequency_mismatch(); diff --git a/Machines/Utility/MachineForTarget.cpp b/Machines/Utility/MachineForTarget.cpp index ece5a9f02..2d721a4dc 100644 --- a/Machines/Utility/MachineForTarget.cpp +++ b/Machines/Utility/MachineForTarget.cpp @@ -8,6 +8,9 @@ #include "MachineForTarget.hpp" +#include + +// Sources for runtime options. #include "../AmstradCPC/AmstradCPC.hpp" #include "../Apple/AppleII/AppleII.hpp" #include "../Apple/Macintosh/Macintosh.hpp" @@ -21,12 +24,23 @@ #include "../Oric/Oric.hpp" #include "../ZX8081/ZX8081.hpp" +// Sources for construction options. +#include "../../Analyser/Static/Acorn/Target.hpp" +#include "../../Analyser/Static/AmstradCPC/Target.hpp" +#include "../../Analyser/Static/AppleII/Target.hpp" +#include "../../Analyser/Static/Atari2600/Target.hpp" +#include "../../Analyser/Static/AtariST/Target.hpp" +#include "../../Analyser/Static/Commodore/Target.hpp" +#include "../../Analyser/Static/Macintosh/Target.hpp" +#include "../../Analyser/Static/MSX/Target.hpp" +#include "../../Analyser/Static/Oric/Target.hpp" +#include "../../Analyser/Static/Sega/Target.hpp" +#include "../../Analyser/Static/ZX8081/Target.hpp" + #include "../../Analyser/Dynamic/MultiMachine/MultiMachine.hpp" #include "TypedDynamicMachine.hpp" -namespace { - -::Machine::DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { +Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error) { error = Machine::Error::None; Machine::DynamicMachine *machine = nullptr; @@ -66,9 +80,7 @@ namespace { return machine; } -} - -::Machine::DynamicMachine *::Machine::MachineForTargets(const Analyser::Static::TargetList &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) { +Machine::DynamicMachine *Machine::MachineForTargets(const Analyser::Static::TargetList &targets, const ROMMachine::ROMFetcher &rom_fetcher, Error &error) { // Zero targets implies no machine. if(targets.empty()) { error = Error::NoTargets; @@ -138,20 +150,82 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) { } } -std::map>> Machine::AllOptionsByMachineName() { - std::map>> options; +std::vector Machine::AllMachines(Type type, bool long_names) { + std::vector result; - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AmstradCPC), AmstradCPC::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AtariST), Atari::ST::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MasterSystem), Sega::MasterSystem::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options())); - options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ZX8081), ZX8081::get_options())); +#define AddName(x) result.push_back(long_names ? LongNameForTargetMachine(Analyser::Machine::x) : ShortNameForTargetMachine(Analyser::Machine::x)) + + if(type == Type::Any || type == Type::RequiresMedia) { + AddName(Atari2600); + AddName(ColecoVision); + AddName(MasterSystem); + } + + if(type == Type::Any || type == Type::DoesntRequireMedia) { + AddName(AmstradCPC); + AddName(AppleII); + AddName(AtariST); + AddName(Electron); + AddName(Macintosh); + AddName(MSX); + AddName(Oric); + AddName(Vic20); + AddName(ZX8081); + } + +#undef AddName + + return result; +} + +std::map> Machine::AllOptionsByMachineName() { + std::map> options; + +#define Emplace(machine, class) \ + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::machine), std::make_unique(Configurable::OptionsType::UserFriendly))); + + Emplace(AmstradCPC, AmstradCPC::Machine); + Emplace(AppleII, Apple::II::Machine); + Emplace(AtariST, Atari::ST::Machine); + Emplace(ColecoVision, Coleco::Vision::Machine); + Emplace(Electron, Electron::Machine); + Emplace(Macintosh, Apple::Macintosh::Machine); + Emplace(MasterSystem, Sega::MasterSystem::Machine); + Emplace(MSX, MSX::Machine); + Emplace(Oric, Oric::Machine); + Emplace(Vic20, Commodore::Vic20::Machine); + Emplace(ZX8081, ZX8081::Machine); + +#undef Emplace + + return options; +} + +std::map> Machine::TargetsByMachineName(bool meaningful_without_media_only) { + std::map> options; + +#define AddMapped(Name, TargetNamespace) \ + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Name), new Analyser::Static::TargetNamespace::Target)); +#define Add(Name) AddMapped(Name, Name) + + Add(AmstradCPC); + Add(AppleII); + Add(AtariST); + AddMapped(Electron, Acorn); + Add(Macintosh); + Add(MSX); + Add(Oric); + AddMapped(Vic20, Commodore); + Add(ZX8081); + + if(!meaningful_without_media_only) { + Add(Atari2600); + options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), new Analyser::Static::Target(Analyser::Machine::ColecoVision))); + AddMapped(MasterSystem, Sega); + } + +#undef Add +#undef AddTwo return options; } diff --git a/Machines/Utility/MachineForTarget.hpp b/Machines/Utility/MachineForTarget.hpp index 0b156c7c7..066ef51b7 100644 --- a/Machines/Utility/MachineForTarget.hpp +++ b/Machines/Utility/MachineForTarget.hpp @@ -10,13 +10,25 @@ #define MachineForTarget_hpp #include "../../Analyser/Static/StaticAnalyser.hpp" +#include "../../Reflection/Struct.hpp" #include "../DynamicMachine.hpp" #include +#include #include #include +/*! + This namespace acts as a grab-bag of functions that allow a client to: + + (i) discover the total list of implemented machines; + (ii) discover the construction and runtime options available for controlling them; and + (iii) create any implemented machine via its construction options. + + See Reflection::Struct and Reflection::Enum for getting dynamic information from the + Targets that this namespace deals in. +*/ namespace Machine { enum class Error { @@ -34,6 +46,12 @@ enum class Error { */ DynamicMachine *MachineForTargets(const Analyser::Static::TargetList &targets, const ::ROMMachine::ROMFetcher &rom_fetcher, Error &error); +/*! + Allocates an instance of DynamicMaachine holding the machine described + by @c target. It is the caller's responsibility to delete the class when finished. +*/ +DynamicMachine *MachineForTarget(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher, Machine::Error &error); + /*! Returns a short string name for the machine identified by the target, which is guaranteed not to have any spaces or other potentially @@ -47,11 +65,31 @@ std::string ShortNameForTargetMachine(const Analyser::Machine target); */ std::string LongNameForTargetMachine(const Analyser::Machine target); +enum class Type { + RequiresMedia, + DoesntRequireMedia, + Any +}; + /*! - Returns a map from machine name to the list of options that machine - exposes, for all machines. + @param type the type of machines to include. + @param long_names If this is @c true then long names will be returned; otherwise short names will be returned. + + @returns A list of all available machines. Names are always guaranteed to be in the same order. */ -std::map>> AllOptionsByMachineName(); +std::vector AllMachines(Type type, bool long_names); + +/*! + Returns a map from long machine name to the list of options that machine exposes, for all machines. +*/ +std::map> AllOptionsByMachineName(); + +/*! + Returns a map from long machine name to appropriate instances of Target for the machine. + + NB: Usually the instances of Target can be dynamic_casted to Reflection::Struct in order to determine available properties. +*/ +std::map> TargetsByMachineName(bool meaningful_without_media_only); } diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 9a5be9e08..6030b2a08 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -18,7 +18,6 @@ #include "../../Storage/Tape/Parsers/ZX8081.hpp" #include "../../ClockReceiver/ForceInline.hpp" -#include "../../Configurable/StandardOptions.hpp" #include "../Utility/MemoryFuzzer.hpp" #include "../Utility/Typer.hpp" @@ -51,12 +50,6 @@ enum ROMType: uint8_t { ZX80 = 0, ZX81 }; -std::vector> get_options() { - return Configurable::standard_options( - static_cast(Configurable::AutomaticTapeMotorControl | Configurable::QuickLoadTape) - ); -} - template class ConcreteMachine: public CRTMachine::Machine, public MediaTarget::Machine, @@ -413,35 +406,18 @@ template class ConcreteMachine: } // MARK: - Configuration options. - std::vector> get_options() final { - return ZX8081::get_options(); + std::unique_ptr get_options() final { + auto options = std::make_unique(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional. + options->automatic_tape_motor_control = use_automatic_tape_motor_control_; + options->quickload = allow_fast_tape_hack_; + return options; } - void set_selections(const Configurable::SelectionSet &selections_by_option) final { - bool quickload; - if(Configurable::get_quick_load_tape(selections_by_option, quickload)) { - allow_fast_tape_hack_ = quickload; - set_use_fast_tape(); - } - - bool autotapemotor; - if(Configurable::get_automatic_tape_motor_control_selection(selections_by_option, autotapemotor)) { - set_use_automatic_tape_motor_control(autotapemotor); - } - } - - Configurable::SelectionSet get_accurate_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, false); - Configurable::append_automatic_tape_motor_control_selection(selection_set, false); - return selection_set; - } - - Configurable::SelectionSet get_user_friendly_selections() final { - Configurable::SelectionSet selection_set; - Configurable::append_quick_load_tape_selection(selection_set, true); - Configurable::append_automatic_tape_motor_control_selection(selection_set, true); - return selection_set; + void set_options(const std::unique_ptr &str) { + const auto options = dynamic_cast(str.get()); + set_use_automatic_tape_motor_control(options->automatic_tape_motor_control); + allow_fast_tape_hack_ = options->quickload; + set_use_fast_tape(); } private: diff --git a/Machines/ZX8081/ZX8081.hpp b/Machines/ZX8081/ZX8081.hpp index 301d8529a..5fff0ffe8 100644 --- a/Machines/ZX8081/ZX8081.hpp +++ b/Machines/ZX8081/ZX8081.hpp @@ -10,14 +10,15 @@ #define ZX8081_hpp #include "../../Configurable/Configurable.hpp" +#include "../../Configurable/StandardOptions.hpp" #include "../../Analyser/Static/StaticAnalyser.hpp" #include "../ROMMachine.hpp" +#include + namespace ZX8081 { -/// @returns The options available for a ZX80 or ZX81. -std::vector> get_options(); - +/// The ZX80/81 machine. class Machine { public: virtual ~Machine(); @@ -26,6 +27,24 @@ class Machine { virtual void set_tape_is_playing(bool is_playing) = 0; virtual bool get_tape_is_playing() = 0; + + /// Defines the runtime options available for a ZX80/81. + class Options: public Reflection::StructImpl, public Configurable::QuickloadOption { + friend Configurable::QuickloadOption; + public: + bool automatic_tape_motor_control; + + Options(Configurable::OptionsType type): + Configurable::QuickloadOption(type == Configurable::OptionsType::UserFriendly), + automatic_tape_motor_control(type == Configurable::OptionsType::UserFriendly) { + + // Declare fields if necessary. + if(needs_declare()) { + DeclareField(automatic_tape_motor_control); + declare_quickload_option(); + } + } + }; }; } diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ed74f9c91..881f5f442 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -101,8 +101,6 @@ 4B055AEF1FAE9BF00060FFFF /* Typer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B3A471F9B8FA70062DABF /* Typer.cpp */; }; 4B055AF11FAE9C160060FFFF /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; 4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055AF01FAE9C080060FFFF /* OpenGL.framework */; }; - 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; - 4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; 4B08A2751EE35D56008B7065 /* Z80InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */; }; 4B08A2781EE39306008B7065 /* TestMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B08A2771EE39306008B7065 /* TestMachine.mm */; }; 4B08A56920D72BEF0016CE5A /* Activity.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B08A56720D72BEF0016CE5A /* Activity.xib */; }; @@ -198,6 +196,8 @@ 4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; }; 4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; }; 4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; }; + 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; + 4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B47F6C4241C87A100ED06F7 /* Struct.cpp */; }; 4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; }; 4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; }; 4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; }; @@ -362,7 +362,6 @@ 4B778F5C23A5F3070000D260 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; }; 4B778F5D23A5F3230000D260 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; }; 4B778F5E23A5F3230000D260 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F91DCFF807003085B1 /* Oric.cpp */; }; - 4B778F5F23A5F3300000D260 /* Configurable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0783591FC11D10001D12BB /* Configurable.cpp */; }; 4B778F6023A5F3460000D260 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944EC201967B4007DE474 /* Disk.cpp */; }; 4B778F6123A5F3560000D260 /* Disk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8944FC201967B4007DE474 /* Disk.cpp */; }; 4B778F6223A5F35F0000D260 /* File.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B894500201967B4007DE474 /* File.cpp */; }; @@ -914,7 +913,6 @@ 4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MachineForTarget.cpp; sourceTree = ""; }; 4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineForTarget.hpp; sourceTree = ""; }; 4B055AF01FAE9C080060FFFF /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; - 4B0783591FC11D10001D12BB /* Configurable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Configurable.cpp; sourceTree = ""; }; 4B08A2741EE35D56008B7065 /* Z80InterruptTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80InterruptTests.swift; sourceTree = ""; }; 4B08A2761EE39306008B7065 /* TestMachine.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TestMachine.h; sourceTree = ""; }; 4B08A2771EE39306008B7065 /* TestMachine.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine.mm; sourceTree = ""; }; @@ -1040,6 +1038,8 @@ 4B38F3471F2EC11D00D9235D /* AmstradCPC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AmstradCPC.hpp; path = AmstradCPC/AmstradCPC.hpp; sourceTree = ""; }; 4B3940E51DA83C8300427841 /* AsyncTaskQueue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AsyncTaskQueue.cpp; path = ../../Concurrency/AsyncTaskQueue.cpp; sourceTree = ""; }; 4B3940E61DA83C8300427841 /* AsyncTaskQueue.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AsyncTaskQueue.hpp; path = ../../Concurrency/AsyncTaskQueue.hpp; sourceTree = ""; }; + 4B3AF7D02413470E00873C0B /* Enum.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Enum.hpp; sourceTree = ""; }; + 4B3AF7D12413472200873C0B /* Struct.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Struct.hpp; sourceTree = ""; }; 4B3BA0C21D318AEB005DD7A7 /* C1540Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = C1540Tests.swift; sourceTree = ""; }; 4B3BA0C51D318B44005DD7A7 /* C1540Bridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = C1540Bridge.h; sourceTree = ""; }; 4B3BA0C61D318B44005DD7A7 /* C1540Bridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = C1540Bridge.mm; sourceTree = ""; }; @@ -1093,6 +1093,7 @@ 4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = ""; }; 4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = ""; }; 4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = ""; }; + 4B47F6C4241C87A100ED06F7 /* Struct.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Struct.cpp; sourceTree = ""; }; 4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; }; 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = ""; }; 4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = ""; }; @@ -2126,7 +2127,6 @@ isa = PBXGroup; children = ( 4B31B88F1FBFBCD800C140D5 /* Configurable.hpp */, - 4B0783591FC11D10001D12BB /* Configurable.cpp */, 4BFE7B851FC39BF100160B38 /* StandardOptions.cpp */, 4BFE7B861FC39BF100160B38 /* StandardOptions.hpp */, ); @@ -2194,6 +2194,17 @@ name = Concurrency; sourceTree = ""; }; + 4B3AF7CF2413470E00873C0B /* Reflection */ = { + isa = PBXGroup; + children = ( + 4B3AF7D02413470E00873C0B /* Enum.hpp */, + 4B3AF7D12413472200873C0B /* Struct.hpp */, + 4B47F6C4241C87A100ED06F7 /* Struct.cpp */, + ); + name = Reflection; + path = ../../Reflection; + sourceTree = ""; + }; 4B3BA0C41D318B44005DD7A7 /* Bridges */ = { isa = PBXGroup; children = ( @@ -3263,6 +3274,7 @@ 4B366DFD1B5C165F0026627B /* Outputs */, 4BB73EDD1B587CA500552FC2 /* Processors */, 4BB73E9F1B587A5100552FC2 /* Products */, + 4B3AF7CF2413470E00873C0B /* Reflection */, 4B055A7B1FAE84A50060FFFF /* SDL */, 4B2409591C45DF85004DA684 /* SignalProcessing */, 4B69FB391C4D908A00B5F0AA /* Storage */, @@ -4408,6 +4420,7 @@ 4B0ACC3323775819008902D0 /* Atari2600.cpp in Sources */, 4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */, 4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */, + 4B47F6C6241C87A100ED06F7 /* Struct.cpp in Sources */, 4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */, 4B894527201967B4007DE474 /* StaticAnalyser.cpp in Sources */, 4BB244D622AABAF600BE20E5 /* z8530.cpp in Sources */, @@ -4425,7 +4438,6 @@ 4B055A7E1FAE84AA0060FFFF /* main.cpp in Sources */, 4B894537201967B4007DE474 /* Z80.cpp in Sources */, 4B055A9F1FAE85DA0060FFFF /* HFE.cpp in Sources */, - 4B07835B1FC11D42001D12BB /* Configurable.cpp in Sources */, 4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */, 4B055AEC1FAE9BA20060FFFF /* Z80Base.cpp in Sources */, 4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */, @@ -4484,7 +4496,6 @@ 4B894538201967B4007DE474 /* Tape.cpp in Sources */, 4B54C0CB1F8D92590050900F /* Keyboard.cpp in Sources */, 4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */, - 4B07835A1FC11D10001D12BB /* Configurable.cpp in Sources */, 4B2BF19623E10F0100C3AD60 /* CSHighPrecisionTimer.m in Sources */, 4B8334951F5E25B60097E338 /* C1540.cpp in Sources */, 4B89453C201967B4007DE474 /* StaticAnalyser.cpp in Sources */, @@ -4567,6 +4578,7 @@ 4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */, 4BD468F71D8DF41D0084958B /* 1770.cpp in Sources */, 4B7F1897215486A200388727 /* StaticAnalyser.cpp in Sources */, + 4B47F6C5241C87A100ED06F7 /* Struct.cpp in Sources */, 4BD3A30B1EE755C800B5B501 /* Video.cpp in Sources */, 4B5FADBA1DE3151600AEC565 /* FileHolder.cpp in Sources */, 4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */, @@ -4758,7 +4770,6 @@ 4B778F5623A5F2AF0000D260 /* CPM.cpp in Sources */, 4B778F1C23A5ED3F0000D260 /* TimedEventLoop.cpp in Sources */, 4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */, - 4B778F5F23A5F3300000D260 /* Configurable.cpp in Sources */, 4B778F3823A5F11C0000D260 /* SegmentParser.cpp in Sources */, 4B778F0723A5EC150000D260 /* CommodoreTAP.cpp in Sources */, 4B778F4123A5F19A0000D260 /* MemoryPacker.cpp in Sources */, @@ -4962,7 +4973,7 @@ "$(USER_LIBRARY_DIR)/Frameworks", ); GCC_C_LANGUAGE_STANDARD = gnu11; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -4984,7 +4995,7 @@ ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_OPTIMIZATION_LEVEL = 2; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; @@ -5104,7 +5115,6 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; @@ -5151,7 +5161,6 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 6993d95b7..66ee95aed 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -31,7 +31,7 @@ + + + isEnabled = "YES"> + + + isEnabled = "NO"> + + + + set_selections(selection_set); + auto options = configurable_device->get_options(); + Reflection::set(*options, "quickload", useFastLoadingHack ? true : false); + configurable_device->set_options(options); } } @@ -599,7 +599,6 @@ struct ActivityObserver: public Activity::Observer { @synchronized(self) { _videoSignal = videoSignal; - Configurable::SelectionSet selection_set; Configurable::Display display; switch(videoSignal) { case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break; @@ -607,8 +606,10 @@ struct ActivityObserver: public Activity::Observer { case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break; case CSMachineVideoSignalMonochromeComposite: display = Configurable::Display::CompositeMonochrome; break; } - append_display_selection(selection_set, display); - configurable_device->set_selections(selection_set); + + auto options = configurable_device->get_options(); + Reflection::set(*options, "output", int(display)); + configurable_device->set_options(options); } } @@ -617,35 +618,23 @@ struct ActivityObserver: public Activity::Observer { if(!configurable_device) return NO; // Get the options this machine provides. - std::vector> options; @synchronized(self) { - options = configurable_device->get_options(); - } + auto options = configurable_device->get_options(); - // Get the standard option for this video signal. - Configurable::StandardOptions option; - switch(videoSignal) { - case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break; - case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break; - case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break; - case CSMachineVideoSignalMonochromeComposite: option = Configurable::DisplayCompositeMonochrome; break; - } - std::unique_ptr display_option = std::move(standard_options(option).front()); - Configurable::ListOption *display_list_option = dynamic_cast(display_option.get()); - NSAssert(display_list_option, @"Expected display option to be a list"); + // Get the standard option for this video signal. + Configurable::Display option; + switch(videoSignal) { + case CSMachineVideoSignalRGB: option = Configurable::Display::RGB; break; + case CSMachineVideoSignalSVideo: option = Configurable::Display::SVideo; break; + case CSMachineVideoSignalComposite: option = Configurable::Display::CompositeColour; break; + case CSMachineVideoSignalMonochromeComposite: option = Configurable::Display::CompositeMonochrome; break; + } - // See whether the video signal is included in the machine options. - for(auto &candidate: options) { - Configurable::ListOption *list_option = dynamic_cast(candidate.get()); + // Map to a string and check against returned options for the 'output' field. + const auto string_option = Reflection::Enum::to_string(option); + const auto all_values = options->values_for("output"); - // Both should be list options - if(!list_option) continue; - - // Check for same name of option. - if(candidate->short_name != display_option->short_name) continue; - - // Check that the video signal option is included. - return std::find(list_option->options.begin(), list_option->options.end(), display_list_option->options.front()) != list_option->options.end(); + return std::find(all_values.begin(), all_values.end(), string_option) != all_values.end(); } return NO; @@ -658,9 +647,9 @@ struct ActivityObserver: public Activity::Observer { @synchronized(self) { _useAutomaticTapeMotorControl = useAutomaticTapeMotorControl; - Configurable::SelectionSet selection_set; - append_automatic_tape_motor_control_selection(selection_set, useAutomaticTapeMotorControl ? true : false); - configurable_device->set_selections(selection_set); + auto options = configurable_device->get_options(); + Reflection::set(*options, "automatic_tape_motor_control", useAutomaticTapeMotorControl ? true : false); + configurable_device->set_options(options); } } @@ -671,9 +660,9 @@ struct ActivityObserver: public Activity::Observer { @synchronized(self) { _useQuickBootingHack = useQuickBootingHack; - Configurable::SelectionSet selection_set; - append_quick_boot_selection(selection_set, useQuickBootingHack ? true : false); - configurable_device->set_selections(selection_set); + auto options = configurable_device->get_options(); + Reflection::set(*options, "quickboot", useQuickBootingHack ? true : false); + configurable_device->set_options(options); } } diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 160ae61fd..ee88ba3c5 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -16,6 +16,7 @@ #include "../../../../../Analyser/Static/Acorn/Target.hpp" #include "../../../../../Analyser/Static/AmstradCPC/Target.hpp" #include "../../../../../Analyser/Static/AppleII/Target.hpp" +#include "../../../../../Analyser/Static/AtariST/Target.hpp" #include "../../../../../Analyser/Static/Commodore/Target.hpp" #include "../../../../../Analyser/Static/Macintosh/Target.hpp" #include "../../../../../Analyser/Static/MSX/Target.hpp" @@ -46,7 +47,6 @@ if(self) { using Target = Analyser::Static::Acorn::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::Electron; target->has_dfs = !!dfs; target->has_adfs = !!adfs; _targets.push_back(std::move(target)); @@ -59,7 +59,6 @@ if(self) { using Target = Analyser::Static::AmstradCPC::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::AmstradCPC; switch(model) { case CSMachineCPCModel464: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC464; break; case CSMachineCPCModel664: target->model = Analyser::Static::AmstradCPC::Target::Model::CPC664; break; @@ -75,7 +74,6 @@ if(self) { using Target = Analyser::Static::MSX::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::MSX; target->has_disk_drive = !!hasDiskDrive; switch(region) { case CSMachineMSXRegionAmerican: target->region = Analyser::Static::MSX::Target::Region::USA; break; @@ -92,7 +90,6 @@ if(self) { using Target = Analyser::Static::Oric::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::Oric; switch(model) { case CSMachineOricModelOric1: target->rom = Target::ROM::BASIC10; break; case CSMachineOricModelOricAtmos: target->rom = Target::ROM::BASIC11; break; @@ -115,7 +112,6 @@ if(self) { using Target = Analyser::Static::Commodore::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::Vic20; switch(region) { case CSMachineVic20RegionDanish: target->region = Target::Region::Danish; break; case CSMachineVic20RegionSwedish: target->region = Target::Region::Swedish; break; @@ -150,7 +146,6 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K if(self) { using Target = Analyser::Static::ZX8081::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::ZX8081; target->is_ZX81 = false; target->ZX80_uses_ZX81_ROM = !!useZX81ROM; target->memory_model = ZX8081MemoryModelFromSize(memorySize); @@ -164,7 +159,6 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K if(self) { using Target = Analyser::Static::ZX8081::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::ZX8081; target->is_ZX81 = true; target->memory_model = ZX8081MemoryModelFromSize(memorySize); _targets.push_back(std::move(target)); @@ -177,7 +171,6 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K if(self) { using Target = Analyser::Static::AppleII::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::AppleII; switch(model) { default: target->model = Target::Model::II; break; case CSMachineAppleIIModelAppleIIPlus: target->model = Target::Model::IIplus; break; @@ -200,7 +193,6 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K if(self) { using Target = Analyser::Static::Macintosh::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::Macintosh; using Model = Target::Model; switch(model) { @@ -219,9 +211,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K - (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model { self = [super init]; if(self) { - using Target = Analyser::Static::Macintosh::Target; + using Target = Analyser::Static::AtariST::Target; auto target = std::make_unique(); - target->machine = Analyser::Machine::AtariST; _targets.push_back(std::move(target)); } return self; diff --git a/OSBindings/SDL/SConstruct b/OSBindings/SDL/SConstruct index 93d84ede7..3889c723d 100644 --- a/OSBindings/SDL/SConstruct +++ b/OSBindings/SDL/SConstruct @@ -85,6 +85,8 @@ SOURCES += glob.glob('../../Processors/6502/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/68000/Implementation/*.cpp') SOURCES += glob.glob('../../Processors/Z80/Implementation/*.cpp') +SOURCES += glob.glob('../../Reflection/*.cpp') + SOURCES += glob.glob('../../SignalProcessing/*.cpp') SOURCES += glob.glob('../../Storage/*.cpp') diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index 13a566feb..451f3c8d9 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -13,10 +13,10 @@ #include #include #include +#include #include #include #include -#include #include @@ -34,6 +34,9 @@ #include "../../Outputs/OpenGL/ScanTarget.hpp" #include "../../Outputs/OpenGL/Screenshot.hpp" +#include "../../Reflection/Enum.hpp" +#include "../../Reflection/Struct.hpp" + namespace { struct MachineRunner { @@ -356,8 +359,22 @@ bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key } struct ParsedArguments { - std::string file_name; - Configurable::SelectionSet selections; + std::vector file_names; + std::map selections; // The empty string will be inserted for arguments without an = suffix. + + void apply(Reflection::Struct *reflectable) const { + for(const auto &argument: selections) { + // Replace any dashes with underscores in the argument name. + std::string property; + std::transform(argument.first.begin(), argument.first.end(), std::back_inserter(property), [](char c) { return c == '-' ? '_' : c; }); + + if(argument.second.empty()) { + Reflection::set(*reflectable, property, true); + } else { + Reflection::fuzzy_set(*reflectable, property, argument.second); + } + } + } }; /*! Parses an argc/argv pair to discern program arguments. */ @@ -382,14 +399,14 @@ ParsedArguments parse_arguments(int argc, char *argv[]) { std::size_t split_index = argument.find("="); if(split_index == std::string::npos) { - arguments.selections[argument] = std::make_unique(true); + arguments.selections[argument]; // To create an entry with the default empty string. } else { - std::string name = argument.substr(0, split_index); + const std::string name = argument.substr(0, split_index); std::string value = argument.substr(split_index+1, std::string::npos); - arguments.selections[name] = std::make_unique(value); + arguments.selections[name] = value; } } else { - arguments.file_name = arg; + arguments.file_names.push_back(arg); } } @@ -467,52 +484,161 @@ int main(int argc, char *argv[]) { SDL_Window *window = nullptr; // Attempt to parse arguments. - ParsedArguments arguments = parse_arguments(argc, argv); + const ParsedArguments arguments = parse_arguments(argc, argv); // This may be printed either as - const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}]"; /* [--logical-keyboard] */ + const std::string usage_suffix = " [file or --new={machine}] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}] [--logical-keyboard]"; // Print a help message if requested. if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) { + const auto all_machines = Machine::AllMachines(Machine::Type::DoesntRequireMedia, false); + std::cout << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; std::cout << "Use alt+enter to toggle full screen display. Use control+shift+V to paste text." << std::endl; - std::cout << "Required machine type and configuration is determined from the file. Machines with further options:" << std::endl << std::endl; + std::cout << "Required machine type **and all options** are determined from the file if specified; otherwise use:" << std::endl << std::endl; + std::cout << "\t--new={"; + bool is_first = true; + for(const auto &name: all_machines) { + if(!is_first) std::cout << "|"; + std::cout << name; + is_first = false; + } + std::cout << "}" << std::endl << std::endl; - auto all_options = Machine::AllOptionsByMachineName(); - for(const auto &machine_options: all_options) { - std::cout << machine_options.first << ":" << std::endl; - for(const auto &option: machine_options.second) { - std::cout << '\t' << "--" << option->short_name; + std::cout << "Media is required to start the: "; + const auto other_machines = Machine::AllMachines(Machine::Type::RequiresMedia, true); + is_first = true; + for(const auto &name: other_machines) { + if(!is_first) std::cout << ", "; + std::cout << name; + is_first = false; + } + std::cout << "." << std::endl << std::endl; - Configurable::ListOption *list_option = dynamic_cast(option.get()); - if(list_option) { + std::cout << "Further machine options:" << std::endl << std::endl;; + + const auto targets = Machine::TargetsByMachineName(false); + const auto runtime_options = Machine::AllOptionsByMachineName(); + const auto machine_names = Machine::AllMachines(Machine::Type::Any, true); + for(const auto &machine: machine_names) { + const auto target = targets.find(machine); + const auto options = runtime_options.find(machine); + + const auto target_reflectable = dynamic_cast(target != targets.end() ? target->second.get() : nullptr); + const auto options_reflectable = dynamic_cast(options != runtime_options.end() ? options->second.get() : nullptr); + + // Don't print a section for this machine if it has no construction and no runtime options objects. + if(!target_reflectable && !options_reflectable) continue; + + const auto target_keys = target_reflectable ? target_reflectable->all_keys() : std::vector(); + const auto options_keys = options_reflectable ? options_reflectable->all_keys() : std::vector(); + + // Don't print a section for this machine if it doesn't actually have any options. + if(target_keys.empty() && options_keys.empty()) { + continue; + } + + std::cout << machine << ":" << std::endl; + + // Join the two lists of properties and sort the result. + std::vector all_options = options_keys; + all_options.insert(all_options.end(), target_keys.begin(), target_keys.end()); + std::sort(all_options.begin(), all_options.end()); + + for(const auto &option: all_options) { + // Replace any underscores with hyphens, better to conform to command-line norms. + std::string mapped_option; + std::transform(option.begin(), option.end(), std::back_inserter(mapped_option), [](char c) { return c == '_' ? '-' : c; }); + std::cout << '\t' << "--" << mapped_option; + + auto source = target_reflectable; + auto type = target_reflectable ? target_reflectable->type_of(option) : nullptr; + if(!type) { + source = options_reflectable; + type = options_reflectable->type_of(option); + } + + // Is this a registered enum? If so, list options. + if(!Reflection::Enum::name(*type).empty()) { std::cout << "={"; bool is_first = true; - for(const auto &option: list_option->options) { + for(const auto &value: source->values_for(option)) { if(!is_first) std::cout << '|'; is_first = false; - std::cout << option; + + std::cout << value; } std::cout << "}"; } + + // The above effectively assumes that every field is either a + // Boolean or an enum. This may need to be revisted. It also + // assumes no name collisions, but that's kind of unavoidable. + std::cout << std::endl; } + std::cout << std::endl; } return EXIT_SUCCESS; } - // Perform a sanity check on arguments. - if(arguments.file_name.empty()) { - std::cerr << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; - std::cerr << "Use --help to learn more about available options." << std::endl; - return EXIT_FAILURE; + // Determine the machine for the supplied file, if any, or from --new. + Analyser::Static::TargetList targets; + + const auto new_argument = arguments.selections.find("new"); + std::string long_machine_name; + if(new_argument != arguments.selections.end() && !new_argument->second.empty()) { + // Perform for a case-insensitive search against short names. + const auto short_names = Machine::AllMachines(Machine::Type::DoesntRequireMedia, false); + auto short_name = short_names.begin(); + while(short_name != short_names.end()) { + if(std::equal( + short_name->begin(), short_name->end(), + new_argument->second.begin(), new_argument->second.end(), + [](char a, char b) { return tolower(b) == tolower(a); })) { + break; + } + ++short_name; + } + + // If a match was found, use the corresponding long name to look up a suitable + // Analyser::Statuc::Target and move that to the targets list. + if(short_name != short_names.end()) { + long_machine_name = Machine::AllMachines(Machine::Type::DoesntRequireMedia, true)[short_name - short_names.begin()]; + auto targets_by_machine = Machine::TargetsByMachineName(false); + std::unique_ptr tgt = std::move(targets_by_machine[long_machine_name]); + targets.push_back(std::move(tgt)); + } + } else if(!arguments.file_names.empty()) { + // Take the first file name that actually implies a machine. + auto file_name = arguments.file_names.begin(); + while(file_name != arguments.file_names.end() && targets.empty()) { + targets = Analyser::Static::GetTargets(*file_name); + ++file_name; + } } - // Determine the machine for the supplied file. - const auto targets = Analyser::Static::GetTargets(arguments.file_name); if(targets.empty()) { - std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl; + if(!arguments.file_names.empty()) { + std::cerr << "Cannot open "; + bool is_first = true; + for(const auto &name: arguments.file_names) { + if(!is_first) std::cerr << ", "; + is_first = false; + std::cerr << name; + } + std::cerr << "; no target machine found" << std::endl; + return EXIT_FAILURE; + } + + if(!new_argument->second.empty()) { + std::cerr << "Unknown machine: " << new_argument->second << std::endl; + return EXIT_FAILURE; + } + + std::cerr << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl; + std::cerr << "Use --help to learn more about available options." << std::endl; return EXIT_FAILURE; } @@ -533,12 +659,13 @@ int main(int argc, char *argv[]) { "/usr/local/share/CLK/", "/usr/share/CLK/" }; - if(arguments.selections.find("rompath") != arguments.selections.end()) { - const std::string user_path = arguments.selections["rompath"]->list_selection()->value; - if(user_path.back() != '/') { - paths.push_back(user_path + "/"); + + const auto rompath = arguments.selections.find("rompath"); + if(rompath != arguments.selections.end()) { + if(rompath->second.back() != '/') { + paths.push_back(rompath->second + "/"); } else { - paths.push_back(user_path); + paths.push_back(rompath->second); } } @@ -573,6 +700,13 @@ int main(int argc, char *argv[]) { return results; }; + // Apply all command-line options to the targets. + for(auto &target: targets) { + auto reflectable_target = dynamic_cast(target.get()); + if(!reflectable_target) continue; + arguments.apply(reflectable_target); + } + // Create and configure a machine. ::Machine::Error error; std::mutex machine_mutex; @@ -596,9 +730,18 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // Apply all command-line options to the machines. + auto configurable = machine->configurable_device(); + if(configurable) { + const auto options = configurable->get_options(); + arguments.apply(options.get()); + configurable->set_options(options); + } + // Apply the speed multiplier, if one was requested. - if(arguments.selections.find("speed") != arguments.selections.end()) { - const char *speed_string = arguments.selections["speed"]->list_selection()->value.c_str(); + const auto speed_argument = arguments.selections.find("speed"); + if(speed_argument != arguments.selections.end()) { + const char *speed_string = speed_argument->second.c_str(); char *end; double speed = strtod(speed_string, &end); @@ -621,6 +764,18 @@ int main(int argc, char *argv[]) { machine_runner.machine = machine.get(); machine_runner.machine_mutex = &machine_mutex; + // Ensure all media is inserted, if this machine accepts it. + { + auto media_target = machine->media_target(); + if(media_target) { + Analyser::Static::Media media; + for(const auto &file_name: arguments.file_names) { + media += Analyser::Static::GetMedia(file_name); + } + media_target->insert_media(media); + } + } + // Attempt to set up video and audio. if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl; @@ -634,7 +789,7 @@ int main(int argc, char *argv[]) { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); SDL_GL_SetSwapInterval(1); - window = SDL_CreateWindow( final_path_component(arguments.file_name).c_str(), + window = SDL_CreateWindow( long_machine_name.empty() ? final_path_component(arguments.file_names.front()).c_str() : long_machine_name.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 400, 300, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); @@ -686,31 +841,6 @@ int main(int argc, char *argv[]) { int window_width, window_height; SDL_GetWindowSize(window, &window_width, &window_height); - Configurable::Device *const configurable_device = machine->configurable_device(); - if(configurable_device) { - // Establish user-friendly options by default. - configurable_device->set_selections(configurable_device->get_user_friendly_selections()); - - // Consider transcoding any list selections that map to Boolean options. - for(const auto &option: configurable_device->get_options()) { - // Check for a corresponding selection. - auto selection = arguments.selections.find(option->short_name); - if(selection != arguments.selections.end()) { - // Transcode selection if necessary. - if(dynamic_cast(option.get())) { - arguments.selections[selection->first] = std::unique_ptr(selection->second->boolean_selection()); - } - - if(dynamic_cast(option.get())) { - arguments.selections[selection->first] = std::unique_ptr(selection->second->list_selection()); - } - } - } - - // Apply the user's actual selections to final the defaults. - configurable_device->set_selections(arguments.selections); - } - // If this is a joystick machine, check for and open attached joysticks. /*! Provides a wrapper for SDL_Joystick pointers that can keep track diff --git a/Outputs/CRT/CRT.cpp b/Outputs/CRT/CRT.cpp index b624fa6cf..8f5b0321f 100644 --- a/Outputs/CRT/CRT.cpp +++ b/Outputs/CRT/CRT.cpp @@ -93,6 +93,10 @@ void CRT::set_display_type(Outputs::Display::DisplayType display_type) { scan_target_->set_modals(scan_target_modals_); } +Outputs::Display::DisplayType CRT::get_display_type() { + return scan_target_modals_.display_type; +} + void CRT::set_phase_linked_luminance_offset(float offset) { scan_target_modals_.input_data_tweaks.phase_linked_luminance_offset = offset; scan_target_->set_modals(scan_target_modals_); diff --git a/Outputs/CRT/CRT.hpp b/Outputs/CRT/CRT.hpp index be07a50ba..9988968f5 100644 --- a/Outputs/CRT/CRT.hpp +++ b/Outputs/CRT/CRT.hpp @@ -298,6 +298,9 @@ class CRT { /*! Sets the display type that will be nominated to the scan target. */ void set_display_type(Outputs::Display::DisplayType); + /*! Gets the last display type provided to set_display_type. */ + Outputs::Display::DisplayType get_display_type(); + /*! Sets the offset to apply to phase when using the PhaseLinkedLuminance8 input data type. */ void set_phase_linked_luminance_offset(float); diff --git a/Reflection/Enum.hpp b/Reflection/Enum.hpp new file mode 100644 index 000000000..09fd48719 --- /dev/null +++ b/Reflection/Enum.hpp @@ -0,0 +1,173 @@ +// +// Enum.hpp +// Clock Signal +// +// Created by Thomas Harte on 17/02/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef Enum_hpp +#define Enum_hpp + +#include +#include +#include +#include +#include +#include +#include + +namespace Reflection { + +#define ReflectableEnum(Name, ...) \ + enum class Name { __VA_ARGS__ }; \ + constexpr static const char *__declaration##Name = #__VA_ARGS__; + +#define EnumDeclaration(Name) #Name, __declaration##Name + +#define AnnounceEnum(Name) ::Reflection::Enum::declare(EnumDeclaration(Name)) +#define AnnounceEnumNS(Namespace, Name) ::Reflection::Enum::declare(#Name, Namespace::__declaration##Name) + +/*! + This provides a very slight version of enum reflection; you can introspect only: + + * enums have been registered, along with the text of their declarations; + * provided that those enums do not declare specific values for their members. + + The macros above help avoid duplication of the declaration, making this just mildly less + terrible than it might have been. + + No guarantees of speed or any other kind of efficiency are offered. +*/ +class Enum { + public: + /*! + Registers @c name and the entries within @c declaration for the enum type @c Type. + + Assuming the caller used the macros above, a standard pattern where both things can be placed in + the same namespace might look like: + + ReflectableEnum(MyEnum, int, A, B, C); + + ... + + AnnounceEnum(MyEnum) + + If AnnounceEnum cannot be placed into the same namespace as ReflectableEnum, see the + EnumDeclaration macro. + */ + template static void declare(const char *name, const char *declaration) { + const char *d_ptr = declaration; + + std::vector result; + while(true) { + // Skip non-alphas, and exit if the terminator is found. + while(*d_ptr && !isalpha(*d_ptr)) ++d_ptr; + if(!*d_ptr) break; + + // Note the current location and proceed for all alphas and digits. + const auto start = d_ptr; + while(isalpha(*d_ptr) || isdigit(*d_ptr)) ++d_ptr; + + // Add a string view. + result.emplace_back(std::string(start, size_t(d_ptr - start))); + } + + members_by_type_.emplace(std::make_pair(std::type_index(typeid(Type)), result)); + names_by_type_.emplace(std::make_pair(std::type_index(typeid(Type)), std::string(name))); + } + + /*! + @returns the declared name of the enum @c Type if it has been registered; the empty string otherwise. + */ + template static const std::string &name() { + return name(typeid(Type)); + } + + /*! + @returns the declared name of the enum with type_info @c type if it has been registered; the empty string otherwise. + */ + static const std::string &name(std::type_index type) { + const auto entry = names_by_type_.find(type); + if(entry == names_by_type_.end()) return empty_string_; + return entry->second; + } + + /*! + @returns the number of members of the enum @c Type if it has been registered; 0 otherwise. + */ + template static size_t size() { + return size(typeid(Type)); + } + + /*! + @returns the number of members of the enum with type_info @c type if it has been registered; @c std::string::npos otherwise. + */ + static size_t size(std::type_index type) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return std::string::npos; + return entry->second.size(); + } + + /*! + @returns A @c std::string name for the enum value @c e. + */ + template static const std::string &to_string(Type e) { + return to_string(typeid(Type), int(e)); + } + + /*! + @returns A @c std::string name for the enum value @c e from the enum with type_info @c type. + */ + static const std::string &to_string(std::type_index type, int e) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return empty_string_; + return entry->second[size_t(e)]; + } + + /*! + @returns a vector naming the members of the enum with type_info @c type if it has been registered; an empty vector otherwise. + */ + static const std::vector &all_values(std::type_index type) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return empty_vector_; + return entry->second; + } + + /*! + @returns a vector naming the members of the enum @c Type type if it has been registered; an empty vector otherwise. + */ + template static const std::vector &all_values() { + return all_values(typeid(Type)); + } + + /*! + @returns A value of @c Type for the name @c str, or @c EnumType(std::string::npos) if + the name is not found. + */ + template static Type from_string(const std::string &str) { + return Type(from_string(typeid(Type), str)); + } + + /*! + @returns A value for the name @c str in the enum with type_info @c type , or @c -1 if + the name is not found. + */ + static int from_string(std::type_index type, const std::string &str) { + const auto entry = members_by_type_.find(type); + if(entry == members_by_type_.end()) return -1; + const auto iterator = std::find(entry->second.begin(), entry->second.end(), str); + if(iterator == entry->second.end()) return -1; + return int(iterator - entry->second.begin()); + } + + private: + static inline std::unordered_map> members_by_type_; + static inline std::unordered_map names_by_type_; + static inline const std::string empty_string_; + static inline const std::vector empty_vector_; +}; + +} + +#endif /* Enum_hpp */ diff --git a/Reflection/Struct.cpp b/Reflection/Struct.cpp new file mode 100644 index 000000000..19096e0ad --- /dev/null +++ b/Reflection/Struct.cpp @@ -0,0 +1,124 @@ +// +// Struct.cpp +// Clock Signal +// +// Created by Thomas Harte on 13/03/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#include "Struct.hpp" + +#include + +// MARK: - Setters + +template <> bool Reflection::set(Struct &target, const std::string &name, int value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + // No need to convert an int or a registered enum. + if(*target_type == typeid(int) || !Reflection::Enum::name(*target_type).empty()) { + target.set(name, &value); + return true; + } + + // Promote to an int64_t. + if(*target_type == typeid(int64_t)) { + const auto int64 = int64_t(value); + target.set(name, &int64); + return true; + } + + return false; +} + +template <> bool Reflection::set(Struct &target, const std::string &name, const std::string &value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + if(Reflection::Enum::name(*target_type).empty()) { + return false; + } + + const int enum_value = Reflection::Enum::from_string(*target_type, value); + if(enum_value < 0) { + return false; + } + target.set(name, &enum_value); + + return true; +} + +template <> bool Reflection::set(Struct &target, const std::string &name, const char *value) { + const std::string string(value); + return set(target, name, string); +} + +template <> bool Reflection::set(Struct &target, const std::string &name, bool value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + if(*target_type == typeid(bool)) { + target.set(name, &value);; + } + + return false; +} + +// MARK: - Fuzzy setter + +bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::string &value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + // If the target is a registered enum, ttry to convert the value. Failing that, + // try to match without case sensitivity. + if(Reflection::Enum::size(*target_type)) { + const int from_string = Reflection::Enum::from_string(*target_type, value); + if(from_string >= 0) { + target.set(name, &from_string); + return true; + } + + const auto all_values = Reflection::Enum::all_values(*target_type); + const auto value_location = std::find_if(all_values.begin(), all_values.end(), + [&value] (const auto &entry) { + if(value.size() != entry.size()) return false; + const char *v = value.c_str(); + const char *e = entry.c_str(); + while(*v) { + if(tolower(*v) != tolower(*e)) return false; + ++v; + ++e; + } + return true; + }); + if(value_location != all_values.end()) { + const int offset = int(value_location - all_values.begin()); + target.set(name, &offset); + return true; + } + + return false; + } + + return false; +} + +// MARK: - Getters + +template bool Reflection::get(Struct &target, const std::string &name, Type &value) { + return false; +} + +template <> bool Reflection::get(Struct &target, const std::string &name, bool &value) { + const auto target_type = target.type_of(name); + if(!target_type) return false; + + if(*target_type == typeid(bool)) { + value = *reinterpret_cast(target.get(name)); + return true; + } + + return false; +} diff --git a/Reflection/Struct.hpp b/Reflection/Struct.hpp new file mode 100644 index 000000000..e6423deae --- /dev/null +++ b/Reflection/Struct.hpp @@ -0,0 +1,272 @@ +// +// Struct.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/03/2020. +// Copyright © 2020 Thomas Harte. All rights reserved. +// + +#ifndef Struct_hpp +#define Struct_hpp + +#include +#include +#include +#include +#include +#include +#include + +#include "Enum.hpp" + +namespace Reflection { + +#define DeclareField(Name) declare(&Name, #Name) + +struct Struct { + virtual std::vector all_keys() = 0; + virtual const std::type_info *type_of(const std::string &name) = 0; + virtual void set(const std::string &name, const void *value) = 0; + virtual const void *get(const std::string &name) = 0; + virtual std::vector values_for(const std::string &name) = 0; + virtual ~Struct() {} +}; + +/*! + Attempts to set the property @c name to @c value ; will perform limited type conversions. + + @returns @c true if the property was successfully set; @c false otherwise. +*/ +template bool set(Struct &target, const std::string &name, Type value); + +/*! + Setting an int: + + * to an int copies the int; + * to an int64_t promotes the int; and + * to a registered enum, copies the int. +*/ +template <> bool set(Struct &target, const std::string &name, int value); + +/*! + Setting a string: + + * to an enum, if the string names a member of the enum, sets the value. +*/ +template <> bool set(Struct &target, const std::string &name, const std::string &value); +template <> bool set(Struct &target, const std::string &name, const char *value); + +/*! + Setting a bool: + + * to a bool, copies the value. +*/ +template <> bool set(Struct &target, const std::string &name, bool value); + + +/*! + Fuzzy-set attempts to set any property based on a string value. This is intended to allow input provided by the user. + + Amongst other steps, it might: + * if the target is a bool, map true, false, yes, no, y, n, etc; + * if the target is an integer, parse like strtrol; + * if the target is a float, parse like strtod; or + * if the target is a reflective enum, attempt to match to enum members (possibly doing so in a case insensitive fashion). + + This method reserves the right to perform more or fewer attempted mappings, using any other logic it + decides is appropriate. + +@returns @c true if the property was successfully set; @c false otherwise. +*/ +bool fuzzy_set(Struct &target, const std::string &name, const std::string &value); + + +/*! + Attempts to get the property @c name to @c value ; will perform limited type conversions. + + @returns @c true if the property was successfully read; @c false otherwise. +*/ +template bool get(Struct &target, const std::string &name, Type &value); + +template <> bool get(Struct &target, const std::string &name, bool &value); + + +// TODO: move this elsewhere. It's just a sketch anyway. +struct Serialisable { + /// Serialises this object, appending it to @c target. + virtual void serialise(std::vector &target) = 0; + /// Deserialises this object from @c source. + /// @returns @c true if the deserialisation was successful; @c false otherwise. + virtual bool deserialise(const std::vector &source) = 0; +}; + +template class StructImpl: public Struct { + public: + /*! + @returns the value of type @c Type that is loaded from the offset registered for the field @c name. + It is the caller's responsibility to provide an appropriate type of data. + */ + const void *get(const std::string &name) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return nullptr; + return reinterpret_cast(this) + iterator->second.offset; + } + + /*! + Stores the @c value of type @c Type to the offset registered for the field @c name. + + It is the caller's responsibility to provide an appropriate type of data. + */ + void set(const std::string &name, const void *value) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return; + memcpy(reinterpret_cast(this) + iterator->second.offset, value, iterator->second.size); + } + + /*! + @returns @c type_info for the field @c name. + */ + const std::type_info *type_of(const std::string &name) final { + const auto iterator = contents_.find(name); + if(iterator == contents_.end()) return nullptr; + return iterator->second.type; + } + + /*! + @returns a list of the valid enum value names for field @c name if it is a declared enum field of this struct; + the empty list otherwise. + */ + std::vector values_for(const std::string &name) final { + std::vector result; + + // Return an empty vector if this field isn't declared. + const auto type = type_of(name); + if(!type) return result; + + // Also return an empty vector if this field isn't a registered enum. + const auto all_values = Enum::all_values(*type); + if(all_values.empty()) return result; + + // If no restriction is stored, return all values. + const auto permitted_values = permitted_enum_values_.find(name); + if(permitted_values == permitted_enum_values_.end()) return all_values; + + // Compile a vector of only those values the stored set indicates. + auto value = all_values.begin(); + auto flag = permitted_values->second.begin(); + while(value != all_values.end() && flag != permitted_values->second.end()) { + if(*flag) { + result.push_back(*value); + } + ++flag; + ++value; + } + + return result; + } + + /*! + @returns A vector of all declared fields for this struct. + */ + std::vector all_keys() final { + std::vector keys; + for(const auto &pair: contents_) { + keys.push_back(pair.first); + } + return keys; + } + + protected: + /* + This interface requires reflective structs to declare all fields; + specifically they should call: + + declare_field(&field1, "field1"); + declare_field(&field2, "field2"); + + Fields are registered in class storage. So callers can use needs_declare() + to determine whether a class of this type has already established the + reflective fields. + */ + + /*! + Exposes the field pointed to by @c t for reflection as @c name. + */ + template void declare(Type *t, const std::string &name) { + contents_.emplace( + std::make_pair( + name, + Field(typeid(Type), reinterpret_cast(t) - reinterpret_cast(this), sizeof(Type)) + )); + } + + /*! + If @c t is a previously-declared field that links to a declared enum then the variable + arguments provide a list of the acceptable values for that field. The list should be terminated + with a value of -1. + */ + template void limit_enum(Type *t, ...) { + const auto name = name_of(t); + if(name.empty()) return; + + // The default vector size of '8' isn't especially scientific, + // but I feel like it's a good choice. + std::vector permitted_values(8); + + va_list list; + va_start(list, t); + while(true) { + const int next = va_arg(list, int); + if(next < 0) break; + + if(permitted_values.size() <= size_t(next)) { + permitted_values.resize(permitted_values.size() << 1); + } + permitted_values[size_t(next)] = true; + } + va_end(list); + + permitted_enum_values_.emplace(std::make_pair(name, permitted_values)); + } + + /*! + @returns @c true if this subclass of @c Struct has not yet declared any fields. + */ + bool needs_declare() { + return !contents_.size(); + } + + /*! + Performs a reverse lookup from field to name. + */ + std::string name_of(void *field) { + const ssize_t offset = reinterpret_cast(field) - reinterpret_cast(this); + + auto iterator = contents_.begin(); + while(iterator != contents_.end()) { + if(iterator->second.offset == offset) break; + ++iterator; + } + + if(iterator != contents_.end()) { + return iterator->first; + } else { + return ""; + } + } + + private: + struct Field { + const std::type_info *type; + ssize_t offset; + size_t size; + Field(const std::type_info &type, ssize_t offset, size_t size) : + type(&type), offset(offset), size(size) {} + }; + static inline std::unordered_map contents_; + static inline std::unordered_map> permitted_enum_values_; +}; + +} + +#endif /* Struct_hpp */