mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-30 19:30:17 +00:00
Technically SDL users can now start a new machine.
Missing though: all the old per-machine command-line options, and any control over the new one.
This commit is contained in:
parent
fc3d3c76f8
commit
2031a33edf
@ -20,6 +20,14 @@
|
||||
#include <vector>
|
||||
|
||||
/*!
|
||||
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 {
|
||||
|
||||
@ -58,13 +66,16 @@ std::string ShortNameForTargetMachine(const Analyser::Machine target);
|
||||
std::string LongNameForTargetMachine(const Analyser::Machine target);
|
||||
|
||||
/*!
|
||||
@returns A list of all available machines.
|
||||
@param meaningful_without_media_only If this is @c true then only machines that it is meaningful to start without a piece of media will be listed;
|
||||
otherwise all supported machines will be listed.
|
||||
@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::vector<std::string> AllMachines(bool meaningful_without_media_only, bool long_names);
|
||||
|
||||
/*!
|
||||
Returns a map from long machine name to the list of options that machine
|
||||
exposes, for all machines.
|
||||
Returns a map from long machine name to the list of options that machine exposes, for all machines.
|
||||
*/
|
||||
std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> AllOptionsByMachineName();
|
||||
|
||||
|
@ -4981,7 +4981,7 @@
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
@ -5003,7 +5003,7 @@
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_OPTIMIZATION_LEVEL = 2;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.12;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
|
@ -34,7 +34,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableThreadSanitizer = "YES"
|
||||
disableMainThreadChecker = "YES"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
@ -57,6 +56,10 @@
|
||||
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=amstradcpc"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col""
|
||||
isEnabled = "NO">
|
||||
@ -87,7 +90,7 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--help"
|
||||
isEnabled = "YES">
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
|
@ -17,7 +17,6 @@
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <variant>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
@ -364,7 +363,7 @@ bool KeyboardKeyForSDLScancode(SDL_Scancode scancode, Inputs::Keyboard::Key &key
|
||||
|
||||
struct ParsedArguments {
|
||||
std::string file_name;
|
||||
std::map<std::string, std::variant<std::string, bool>> selections;
|
||||
std::map<std::string, std::string> selections; // The empty string will be inserted for arguments without an = suffix.
|
||||
};
|
||||
|
||||
/*! Parses an argc/argv pair to discern program arguments. */
|
||||
@ -389,7 +388,7 @@ ParsedArguments parse_arguments(int argc, char *argv[]) {
|
||||
std::size_t split_index = argument.find("=");
|
||||
|
||||
if(split_index == std::string::npos) {
|
||||
arguments.selections[argument] = true;
|
||||
arguments.selections[argument]; // To create an entry with the default empty string.
|
||||
} else {
|
||||
std::string name = argument.substr(0, split_index);
|
||||
std::string value = argument.substr(split_index+1, std::string::npos);
|
||||
@ -477,39 +476,55 @@ int main(int argc, char *argv[]) {
|
||||
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(false, 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;
|
||||
|
||||
const 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;
|
||||
|
||||
Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(option.get());
|
||||
if(list_option) {
|
||||
std::cout << "={";
|
||||
bool is_first = true;
|
||||
for(const auto &option: list_option->options) {
|
||||
if(!is_first) std::cout << '|';
|
||||
is_first = false;
|
||||
std::cout << option;
|
||||
}
|
||||
std::cout << "}";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "Required machine type and configuration is 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;
|
||||
|
||||
std::cout << "Further machine options:" << std::endl;
|
||||
std::cout << "(* means: a selection will be made automatically based on the file selected, if any)" << std::endl << std::endl;
|
||||
|
||||
// const 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;
|
||||
//
|
||||
// Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(option.get());
|
||||
// if(list_option) {
|
||||
// std::cout << "={";
|
||||
// bool is_first = true;
|
||||
// for(const auto &option: list_option->options) {
|
||||
// if(!is_first) std::cout << '|';
|
||||
// is_first = false;
|
||||
// std::cout << option;
|
||||
// }
|
||||
// std::cout << "}";
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
|
||||
const auto targets = Machine::TargetsByMachineName(false);
|
||||
for(const auto &target: targets) {
|
||||
const auto reflectable = dynamic_cast<Reflection::Struct *>(target.second.get());
|
||||
if(!reflectable) continue;
|
||||
const auto all_keys = reflectable->all_keys();
|
||||
if(all_keys.empty()) continue;
|
||||
|
||||
std::cout << target.first << ":" << std::endl;
|
||||
for(const auto &option: reflectable->all_keys()) {
|
||||
@ -529,7 +544,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// TODO: if not a registered enum... then assume it was a Boolean?
|
||||
std::cout << std::endl;
|
||||
std::cout << "\t*" << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
@ -537,17 +552,50 @@ int main(int argc, char *argv[]) {
|
||||
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;
|
||||
if(!arguments.file_name.empty()) {
|
||||
targets = Analyser::Static::GetTargets(arguments.file_name);
|
||||
}
|
||||
|
||||
const auto new_argument = arguments.selections.find("new");
|
||||
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(false, 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()) {
|
||||
const auto long_name = Machine::AllMachines(false, true)[short_name - short_names.begin()];
|
||||
auto targets_by_machine = Machine::TargetsByMachineName(false);
|
||||
std::unique_ptr<Analyser::Static::Target> tgt = std::move(targets_by_machine[long_name]);
|
||||
targets.push_back(std::move(tgt));
|
||||
}
|
||||
}
|
||||
|
||||
// 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_name.empty()) {
|
||||
std::cerr << "Cannot open " << arguments.file_name << "; 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;
|
||||
}
|
||||
|
||||
@ -568,14 +616,14 @@ 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 + "/");
|
||||
// } else {
|
||||
// paths.push_back(user_path);
|
||||
// }
|
||||
// }
|
||||
if(arguments.selections.find("rompath") != arguments.selections.end()) {
|
||||
const std::string user_path = arguments.selections["rompath"];
|
||||
if(user_path.back() != '/') {
|
||||
paths.push_back(user_path + "/");
|
||||
} else {
|
||||
paths.push_back(user_path);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> results;
|
||||
for(const auto &rom: roms) {
|
||||
|
@ -51,3 +51,7 @@ template <> bool Reflection::set(Struct &target, const std::string &name, const
|
||||
const std::string string(value);
|
||||
return set<const std::string &>(target, name, string);
|
||||
}
|
||||
|
||||
bool Reflection::fuzzy_set(Struct &target, const std::string &name, const std::string &value) {
|
||||
return false;
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ struct Struct {
|
||||
/*!
|
||||
Attempts to set the property @c name to @c value ; will perform limited type conversions.
|
||||
|
||||
@returns @c true if t
|
||||
@returns @c true if the property was successfully set; @c false otherwise.
|
||||
*/
|
||||
template <typename Type> bool set(Struct &target, const std::string &name, Type value);
|
||||
|
||||
@ -54,12 +54,30 @@ template <> bool set(Struct &target, const std::string &name, int 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);
|
||||
|
||||
|
||||
/*!
|
||||
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);
|
||||
|
||||
// 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<uint8_t> &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<uint8_t> &source) = 0;
|
||||
/// Serialises this object, appending it to @c target.
|
||||
virtual void serialise(std::vector<uint8_t> &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<uint8_t> &source) = 0;
|
||||
};
|
||||
|
||||
template <typename Owner> class StructImpl: public Struct {
|
||||
|
Loading…
Reference in New Issue
Block a user