diff options
| author | 2017-05-07 14:40:12 +1000 | |
|---|---|---|
| committer | 2017-05-07 14:40:12 +1000 | |
| commit | a400c011a96197281e205d265c267ecd915fb597 (patch) | |
| tree | 68bb8b132d9c8dea4e587c22db6b32c6ece08b73 /src | |
| parent | 1970bcfab8103d41ce65bffa7933d19eda1f1b19 (diff) | |
Revert "Overhaul to how MAME handles options (#2260)"
This reverts commit 536990e77b49ccc50ef275bfbf1018cc29c16154.
Conflicts:
src/frontend/mame/mame.cpp
Sorry, but this change was half-baked. It breaks a lot of existing
functionality and clearly hasn't been tested in more than a tiny subset
of use cases. Please play this work back onto your own branch, and test
it before submitting another PR.
Diffstat (limited to 'src')
34 files changed, 1523 insertions, 1846 deletions
diff --git a/src/devices/machine/ram.cpp b/src/devices/machine/ram.cpp index 1fb1be56043..49ff027ed0f 100644 --- a/src/devices/machine/ram.cpp +++ b/src/devices/machine/ram.cpp @@ -163,7 +163,9 @@ void ram_device::device_validity_check(validity_checker &valid) const osd_printf_error("%s", output.str().c_str()); osd_printf_warning("Setting value to default %s\n",m_default_size); - mconfig().options().set_value(OPTION_RAMSIZE, m_default_size, OPTION_PRIORITY_CMDLINE); + std::string error; + mconfig().options().set_value(OPTION_RAMSIZE, m_default_size, OPTION_PRIORITY_CMDLINE, error); + assert(error.empty()); } } diff --git a/src/emu/diimage.cpp b/src/emu/diimage.cpp index 009ea67e42b..1c256684821 100644 --- a/src/emu/diimage.cpp +++ b/src/emu/diimage.cpp @@ -1226,7 +1226,7 @@ void device_image_interface::reset_and_load(const std::string &path) device().machine().schedule_hard_reset(); // and record the new load - device().machine().options().image_option(instance_name()).specify(path); + device().machine().options().image_options()[instance_name()] = path; } @@ -1310,10 +1310,9 @@ void device_image_interface::update_names() if (brief_name == nullptr) brief_name = device_brieftypename(image_type()); - m_cannonical_instance_name = string_format("%s%d", inst_name, index + 1); if (count > 1) { - m_instance_name = m_cannonical_instance_name; + m_instance_name = string_format("%s%d", inst_name, index + 1); m_brief_instance_name = string_format("%s%d", brief_name, index + 1); } else @@ -1464,11 +1463,11 @@ std::string device_image_interface::software_get_default_slot(const char *defaul { std::string result; - const std::string &image_name(device().mconfig().options().image_option(instance_name()).value()); - if (!image_name.empty()) + auto iter = device().mconfig().options().image_options().find(instance_name()); + if (iter != device().mconfig().options().image_options().end() && !iter->second.empty()) { result.assign(default_card_slot); - const software_part *swpart = find_software_item(image_name, true); + const software_part *swpart = find_software_item(iter->second, true); if (swpart != nullptr) { const char *slot = swpart->feature("slot"); diff --git a/src/emu/diimage.h b/src/emu/diimage.h index e6fa3ea69e3..bf656b6a040 100644 --- a/src/emu/diimage.h +++ b/src/emu/diimage.h @@ -225,7 +225,6 @@ public: const std::string &instance_name() const { return m_instance_name; } const std::string &brief_instance_name() const { return m_brief_instance_name; } - const std::string &cannonical_instance_name() const { return m_cannonical_instance_name; } bool uses_file_extension(const char *file_extension) const; const formatlist_type &formatlist() const { return m_formatlist; } @@ -342,9 +341,8 @@ private: util::hash_collection m_hash; - std::string m_instance_name; // e.g. - "cartridge", "floppydisk2" - std::string m_brief_instance_name; // e.g. - "cart", "flop2" - std::string m_cannonical_instance_name; // e.g. - "cartridge1", "floppydisk2" - only used internally in emuopts.cpp + std::string m_brief_instance_name; + std::string m_instance_name; // in the case of arcade cabinet with fixed carts inserted, // we want to disable command line cart loading... diff --git a/src/emu/dislot.h b/src/emu/dislot.h index 8f36d70386c..a6722a50f70 100644 --- a/src/emu/dislot.h +++ b/src/emu/dislot.h @@ -145,7 +145,6 @@ public: virtual std::string get_default_card_software(get_default_card_software_hook &hook) const { return std::string(); } device_t *get_card_device() { return m_card_device; } void set_card_device(device_t *dev) { m_card_device = dev; } - const char *slot_name() const { return device().tag() + 1; } private: // internal state diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp index 823fb2e071f..2863ef35caf 100644 --- a/src/emu/emuopts.cpp +++ b/src/emu/emuopts.cpp @@ -10,11 +10,6 @@ #include "emu.h" #include "emuopts.h" -#include "drivenum.h" -#include "softlist_dev.h" -#include "hashfile.h" - -#include <stack> //************************************************************************** @@ -218,167 +213,6 @@ const options_entry emu_options::s_option_entries[] = //************************************************************************** -// CUSTOM OPTION ENTRIES AND SUPPORT CLASSES -//************************************************************************** - -namespace -{ - // custom option entry for the system name - class system_name_option_entry : public core_options::entry - { - public: - system_name_option_entry(emu_options &host) - : entry(OPTION_SYSTEMNAME) - , m_host(host) - { - } - - virtual const char *value() const override - { - return m_host.system() ? m_host.system()->name : ""; - } - - protected: - virtual void internal_set_value(std::string &&newvalue) override - { - m_host.set_system_name(newvalue); - } - - private: - emu_options &m_host; - }; - - // custom option entry for the software name - class software_name_option_entry : public core_options::entry - { - public: - software_name_option_entry(emu_options &host) - : entry(OPTION_SOFTWARENAME) - , m_host(host) - { - } - - protected: - virtual void internal_set_value(std::string &&newvalue) override - { - m_host.set_software(newvalue); - } - - private: - emu_options &m_host; - }; - - // custom option entry for slots - class slot_option_entry : public core_options::entry - { - public: - slot_option_entry(const char *name, slot_option &host) - : entry(name) - , m_host(host) - { - } - - virtual const char *value() const override - { - const char *result = nullptr; - if (m_host.specified()) - { - m_temp = m_host.specified_value(); - result = m_temp.c_str(); - } - return result; - } - - protected: - virtual void internal_set_value(std::string &&newvalue) override - { - m_host.specify(std::move(newvalue)); - } - - private: - slot_option & m_host; - mutable std::string m_temp; - }; - - // custom option entry for images - class image_option_entry : public core_options::entry - { - public: - image_option_entry(std::vector<std::string> &&names, image_option &host) - : entry(std::move(names)) - , m_host(host) - { - } - - virtual const char *value() const override - { - return m_host.value().c_str(); - } - - protected: - virtual void internal_set_value(std::string &&newvalue) override - { - m_host.specify(std::move(newvalue)); - } - - private: - image_option &m_host; - }; - - // existing option tracker class; used by slot/image calculus to identify existing - // options for later purging - template<typename T> - class existing_option_tracker - { - public: - existing_option_tracker(const std::unordered_map<std::string, T> &map) - { - m_vec.reserve(map.size()); - for (const auto &entry : map) - m_vec.push_back(&entry.first); - } - - template<typename TStr> - void remove(TStr str) - { - auto iter = std::find_if( - m_vec.begin(), - m_vec.end(), - [&str](const auto &x) { return *x == str; }); - if (iter != m_vec.end()) - m_vec.erase(iter); - } - - std::vector<const std::string *>::iterator begin() { return m_vec.begin(); } - std::vector<const std::string *>::iterator end() { return m_vec.end(); } - - private: - std::vector<const std::string *> m_vec; - }; - - - //------------------------------------------------- - // get_full_option_names - //------------------------------------------------- - - std::vector<std::string> get_full_option_names(const device_image_interface &image) - { - std::vector<std::string> result; - - result.push_back(image.instance_name()); - result.push_back(image.brief_instance_name()); - - if (strcmp(image.device_typename(image.image_type()), image.instance_name().c_str()) == 0) - { - result.push_back(image.instance_name() + "1"); - result.push_back(image.brief_instance_name() + "1"); - } - return result; - } -} - - -//************************************************************************** // EMU OPTIONS //************************************************************************** @@ -386,611 +220,125 @@ namespace // emu_options - constructor //------------------------------------------------- -emu_options::emu_options(bool general_only) - : m_system(nullptr) - , m_coin_impulse(0) - , m_joystick_contradictory(false) - , m_sleep(true) - , m_refresh_speed(false) - , m_ui(UI_CABINET) +emu_options::emu_options() +: core_options() +, m_coin_impulse(0) +, m_joystick_contradictory(false) +, m_sleep(true) +, m_refresh_speed(false) +, m_ui(UI_CABINET) { - // add entries - if (!general_only) - { - add_entry(std::make_shared<system_name_option_entry>(*this)); - add_entry(std::make_shared<software_name_option_entry>(*this)); - } add_entries(emu_options::s_option_entries); - - // adding handlers to keep copies of frequently requested options in member variables - set_value_changed_handler(OPTION_COIN_IMPULSE, [this](const char *value) { m_coin_impulse = int_value(OPTION_COIN_IMPULSE); }); - set_value_changed_handler(OPTION_JOYSTICK_CONTRADICTORY, [this](const char *value) { m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); }); - set_value_changed_handler(OPTION_SLEEP, [this](const char *value) { m_sleep = bool_value(OPTION_SLEEP); }); - set_value_changed_handler(OPTION_REFRESHSPEED, [this](const char *value) { m_sleep = bool_value(OPTION_REFRESHSPEED); }); - set_value_changed_handler(OPTION_UI, [this](const std::string &value) - { - if (value == "simple") - m_ui = UI_SIMPLE; - else - m_ui = UI_CABINET; - }); -} - - -//------------------------------------------------- -// emu_options - destructor -//------------------------------------------------- - -emu_options::~emu_options() -{ } //------------------------------------------------- -// system_name +// value_changed - to prevent tagmap +// lookups keep copies of frequently requested +// options in member variables. //------------------------------------------------- -const char *emu_options::system_name() const +void emu_options::value_changed(const std::string &name, const std::string &value) { - return m_system ? m_system->name : ""; -} - - -//------------------------------------------------- -// set_system_name - called to set the system -// name; will adjust slot/image options as appropriate -//------------------------------------------------- - -void emu_options::set_system_name(const std::string &new_system_name) -{ - const game_driver *new_system = nullptr; - - // was a system name specified? - if (!new_system_name.empty()) + if (name == OPTION_COIN_IMPULSE) { - // if so, first extract the base name (the reason for this is drag-and-drop on Windows; a side - // effect is a command line like 'mame pacman.foo' will work correctly, but so be it) - std::string new_system_base_name = core_filename_extract_base(new_system_name, true); - - // perform the lookup (and error if it cannot be found) - int index = driver_list::find(new_system_base_name.c_str()); - if (index < 0) - throw options_error_exception("Unknown system '%s'", new_system_name); - new_system = &driver_list::driver(index); + m_coin_impulse = int_value(OPTION_COIN_IMPULSE); } - - // did we change anything? - if (new_system != m_system) + else if (name == OPTION_JOYSTICK_CONTRADICTORY) { - // if so, specify the new system and update - m_system = new_system; - update_slot_and_image_options(); + m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); } -} - - -//------------------------------------------------- -// update_slot_and_image_options -//------------------------------------------------- - -void emu_options::update_slot_and_image_options() -{ - bool changed; - do + else if (name == OPTION_SLEEP) { - changed = false; - - // first we add and remove slot options depending on what has been configured in the - // device, bringing m_slot_options up to a state where it matches machine_config - if (add_and_remove_slot_options()) - changed = true; - - // second, we perform an analgous operation with m_image_options - if (add_and_remove_image_options()) - changed = true; - - // if we changed anything, we should reevaluate existing options - if (changed) - reevaluate_default_card_software(); - } while (changed); -} - - -//------------------------------------------------- -// add_and_remove_slot_options - add any missing -// and/or purge extraneous slot options -//------------------------------------------------- - -bool emu_options::add_and_remove_slot_options() -{ - bool changed = false; - - // first, create a list of existing slot options; this is so we can purge - // any stray slot options that are no longer pertinent when we're done - existing_option_tracker<::slot_option> existing(m_slot_options); - - // it is perfectly legal for this to be called without a system; we - // need to check for that condition! - if (m_system) - { - // create the configuration - machine_config config(*m_system, *this); - - for (const device_slot_interface &slot : slot_interface_iterator(config.root_device())) - { - // come up with the cannonical name of the slot - const char *slot_option_name = slot.slot_name(); - - // erase this option from existing (so we don't purge it later) - existing.remove(slot_option_name); - - // do we need to add this option? - if (!has_slot_option(slot_option_name)) - { - // we do - add it to m_slot_options - auto pair = std::make_pair(slot_option_name, ::slot_option(*this, slot.default_option())); - ::slot_option &new_option(m_slot_options.emplace(std::move(pair)).first->second); - changed = true; - - // for non-fixed slots, this slot needs representation in the options collection - if (!slot.fixed()) - { - // first device? add the header as to be pretty - const char *header = "SLOT DEVICES"; - if (!header_exists(header)) - add_header(header); - - // create a new entry in the options - auto new_entry = new_option.setup_option_entry(slot_option_name); - - // and add it - add_entry(std::move(new_entry), header); - } - } - - } + m_sleep = bool_value(OPTION_SLEEP); } - - // at this point we need to purge stray slot options that may no longer be pertinent - for (auto &opt_name : existing) - { - auto iter = m_slot_options.find(*opt_name); - assert(iter != m_slot_options.end()); - - // if this is represented in core_options, remove it - if (iter->second.option_entry()) - remove_entry(*iter->second.option_entry()); - - // remove this option - m_slot_options.erase(iter); - changed = true; - } - - return changed; -} - - -//------------------------------------------------- -// add_and_remove_slot_options - add any missing -// and/or purge extraneous slot options -//------------------------------------------------- - -bool emu_options::add_and_remove_image_options() -{ - // The logic for image options is superficially similar to the logic for slot options, but - // there is one larger piece of complexity. The image instance names (returned by the - // image_instance() call and surfaced in the UI) may change simply because we've added more - // devices. This is because the instance_name() for a singular cartridge device might be - // "cartridge" starting out, but become "cartridge1" when another cartridge device is added. - // - // To get around this behavior, our internal data structures work in terms of what is - // returned by cannonical_instance_name(), which will be something like "cartridge1" both - // for a singular cartridge device and the first cartridge in a multi cartridge system. - // - // The need for this behavior was identified by Tafoid when the following command line - // regressed: - // - // mame snes bsxsore -cart2 bszelda - // - // Before we were accounting for this behavior, 'bsxsore' got stored in "cartridge" and - // the association got lost when the second cartridge was added. - - bool changed = false; - - // first, create a list of existing image options; this is so we can purge - // any stray slot options that are no longer pertinent when we're done; we - // have to do this for both "flavors" of name - existing_option_tracker<::image_option> existing(m_image_options_cannonical); - - // wipe the non-cannonical image options; we're going to rebuild it - m_image_options.clear(); - - // it is perfectly legal for this to be called without a system; we - // need to check for that condition! - if (m_system) + else if (name == OPTION_REFRESHSPEED) { - // create the configuration - machine_config config(*m_system, *this); - - // iterate through all image devices - for (device_image_interface &image : image_interface_iterator(config.root_device())) - { - const std::string &cannonical_name(image.cannonical_instance_name()); - - // erase this option from existing (so we don't purge it later) - existing.remove(cannonical_name); - - // do we need to add this option? - auto iter = m_image_options_cannonical.find(cannonical_name); - ::image_option *this_option = iter != m_image_options_cannonical.end() ? &iter->second : nullptr; - if (!this_option) - { - // we do - add it to both m_image_options_cannonical and m_image_options - auto pair = std::make_pair(cannonical_name, ::image_option(*this, image.cannonical_instance_name())); - this_option = &m_image_options_cannonical.emplace(std::move(pair)).first->second; - changed = true; - - // if this image is user loadable, we have to surface it in the core_options - if (image.user_loadable()) - { - // first device? add the header as to be pretty - const char *header = "IMAGE DEVICES"; - if (!header_exists(header)) - add_header(header); - - // name this options - auto names = get_full_option_names(image); - - // create a new entry in the options - auto new_entry = this_option->setup_option_entry(std::move(names)); - - // and add it - add_entry(std::move(new_entry), header); - } - } - - // whether we added it or we didn't, we have to add it to the m_image_option map - m_image_options[image.instance_name()] = this_option; - } + m_refresh_speed = bool_value(OPTION_REFRESHSPEED); } - - // at this point we need to purge stray image options that may no longer be pertinent - for (auto &opt_name : existing) + else if (name == OPTION_UI) { - auto iter = m_image_options_cannonical.find(*opt_name); - assert(iter != m_image_options_cannonical.end()); - - // if this is represented in core_options, remove it - if (iter->second.option_entry()) - remove_entry(*iter->second.option_entry()); - - // remove this option - m_image_options_cannonical.erase(iter); - changed = true; + if (value == "simple") + m_ui = UI_SIMPLE; + else + m_ui = UI_CABINET; } - - return changed; } //------------------------------------------------- -// reevaluate_default_card_software - based on recent -// changes in what images are mounted, give drivers -// a chance to specify new default slot options +// override_get_value - when saving to an INI, we +// need to hook into that process so we can write +// out image/slot options //------------------------------------------------- -void emu_options::reevaluate_default_card_software() +core_options::override_get_value_result emu_options::override_get_value(const char *name, std::string &value) const { - if (m_system) + if (name) { - bool found; - do + auto slotiter = m_slot_options.find(name); + if (slotiter != m_slot_options.end()) { - // set up the machine_config - machine_config config(*m_system, *this); - found = false; - - // iterate through all slot devices - for (device_slot_interface &slot : slot_interface_iterator(config.root_device())) - { - // retrieve info about the device instance - auto &slot_opt(slot_option(slot.slot_name())); - - // device_slot_interface::get_default_card_software() is essentially a hook - // that lets devices provide a feedback loop to force a specified software - // list entry to be loaded - // - // In the repeated cycle of adding slots and slot devices, this gives a chance - // for devices to "plug in" default software list items. Of course, the fact - // that this is all shuffling options is brittle and roundabout, but such is - // the nature of software lists. - // - // In reality, having some sort of hook into the pipeline of slot/device evaluation - // makes sense, but the fact that it is joined at the hip to device_image_interface - // and device_slot_interface is unfortunate - std::string default_card_software = get_default_card_software(slot); - if (slot_opt.default_card_software() != default_card_software) - { - slot_opt.set_default_card_software(std::move(default_card_software)); - - // calling set_default_card_software() can cause a cascade of slot/image - // evaluations; we need to bail out of this loop because the iterator - // may be bad - found = true; - break; - } - } - } while (found); - } -} - - -//------------------------------------------------- -// get_default_card_software -//------------------------------------------------- - -std::string emu_options::get_default_card_software(device_slot_interface &slot) -{ - std::string image_path; - std::function<bool(util::core_file &, std::string&)> get_hashfile_extrainfo; - - // figure out if an image option has been specified, and if so, get the image path out of the options - device_image_interface *image = dynamic_cast<device_image_interface *>(&slot); - if (image) - { - image_path = image_option(image->instance_name()).value(); + value = slotiter->second.specified_value(); + return slotiter->second.specified() + ? override_get_value_result::OVERRIDE + : override_get_value_result::SKIP; + } - get_hashfile_extrainfo = [image, this](util::core_file &file, std::string &extrainfo) + auto imageiter = m_image_options.find(name); + if (imageiter != m_image_options.end()) { - util::hash_collection hashes = image->calculate_hash_on_file(file); - - return hashfile_extrainfo( - hash_path(), - image->device().mconfig().gamedrv(), - hashes, - extrainfo); - }; + value = imageiter->second; + return override_get_value_result::OVERRIDE; + } } - // create the hook - get_default_card_software_hook hook(image_path, std::move(get_hashfile_extrainfo)); - - // and invoke the slot's implementation of get_default_card_software() - return slot.get_default_card_software(hook); + return override_get_value_result::NONE; } //------------------------------------------------- -// set_software - called to load "unqualified" -// software out of a software list (e.g. - "mame nes 'zelda'") +// override_set_value - when parsing an INI, we +// need to hook into into it so we can do the same +// crazy slot logic done in mameopt //------------------------------------------------- -void emu_options::set_software(const std::string &new_software) +bool emu_options::override_set_value(const char *name, const std::string &value) { - // identify any options as a result of softlists - software_options softlist_opts = evaluate_initial_softlist_options(new_software); - - while (!softlist_opts.slot.empty() || !softlist_opts.image.empty()) + auto slotiter = m_slot_options.find(name); + if (slotiter != m_slot_options.end()) { - // track how many options we have - size_t before_size = softlist_opts.slot.size() + softlist_opts.image.size(); - - // keep a list of deferred options, in case anything is applied - // out of order - software_options deferred_opts; - - // distribute slot options - for (auto &slot_opt : softlist_opts.slot) - { - auto iter = m_slot_options.find(slot_opt.first); - if (iter != m_slot_options.end()) - iter->second.specify(std::move(slot_opt.second)); - else - deferred_opts.slot[slot_opt.first] = std::move(slot_opt.second); - } - - // distribute image options - for (auto &image_opt : softlist_opts.image) - { - auto iter = m_image_options.find(image_opt.first); - if (iter != m_image_options.end()) - iter->second->specify(std::move(image_opt.second)); - else - deferred_opts.image[image_opt.first] = std::move(image_opt.second); - } - - // keep any deferred options for the next round - softlist_opts = std::move(deferred_opts); - - // do we have any pending options after failing to distribute any? - size_t after_size = softlist_opts.slot.size() + softlist_opts.image.size(); - if ((after_size > 0) && after_size >= before_size) - throw options_error_exception("Could not assign software option"); + slotiter->second.specify(std::string(value)); + return true; } -} - - -//------------------------------------------------- -// evaluate_initial_softlist_options -//------------------------------------------------- - -emu_options::software_options emu_options::evaluate_initial_softlist_options(const std::string &software_identifier) -{ - software_options results; - // load software specified at the command line (if any of course) - if (!software_identifier.empty()) + auto imageiter = m_image_options.find(name); + if (imageiter != m_image_options.end()) { - // we have software; first identify the proper game_driver - if (!m_system) - throw options_error_exception("Cannot specify software without specifying system"); - - // and set up a configuration - machine_config config(*m_system, *this); - software_list_device_iterator iter(config.root_device()); - if (iter.count() == 0) - throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", software_identifier.c_str()); - - // and finally set up the stack - std::stack<std::string> software_identifier_stack; - software_identifier_stack.push(software_identifier); - - // we need to keep evaluating softlist identifiers until the stack is empty - while (!software_identifier_stack.empty()) - { - // pop the identifier - std::string current_software_identifier = std::move(software_identifier_stack.top()); - software_identifier_stack.pop(); - - // and parse it - std::string list_name, software_name; - auto colon_pos = current_software_identifier.find_first_of(':'); - if (colon_pos != std::string::npos) - { - list_name = current_software_identifier.substr(0, colon_pos); - software_name = current_software_identifier.substr(colon_pos + 1); - } - else - { - software_name = current_software_identifier; - } - - // loop through all softlist devices, and try to find one capable of handling the requested software - bool found = false; - bool compatible = false; - for (software_list_device &swlistdev : iter) - { - if (list_name.empty() || (list_name == swlistdev.list_name())) - { - const software_info *swinfo = swlistdev.find(software_name); - if (swinfo != nullptr) - { - // loop through all parts - for (const software_part &swpart : swinfo->parts()) - { - // only load compatible software this way - if (swlistdev.is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE) - { - // we need to find a mountable image slot, but we need to ensure it is a slot - // for which we have not already distributed a part to - device_image_interface *image = software_list_device::find_mountable_image( - config, - swpart, - [&results](const device_image_interface &candidate) { return results.image.count(candidate.instance_name()) == 0; }); - - // did we find a slot to put this part into? - if (image != nullptr) - { - // we've resolved this software - results.image[image->instance_name()] = string_format("%s:%s:%s", swlistdev.list_name(), software_name, swpart.name()); - - // does this software part have a requirement on another part? - const char *requirement = swpart.feature("requirement"); - if (requirement) - software_identifier_stack.push(requirement); - } - compatible = true; - } - found = true; - } - - // identify other shared features specified as '<<slot name>>_default' - // - // example from SMS: - // - // <software name = "alexbmx"> - // ... - // <sharedfeat name = "ctrl1_default" value = "paddle" /> - // </software> - for (const feature_list_item &fi : swinfo->shared_info()) - { - const std::string default_suffix = "_default"; - if (fi.name().size() > default_suffix.size() - && fi.name().compare(fi.name().size() - default_suffix.size(), default_suffix.size(), default_suffix) == 0) - { - std::string slot_name = fi.name().substr(0, fi.name().size() - default_suffix.size()); - results.slot[slot_name] = fi.value(); - } - } - } - } - if (compatible) - break; - } - - if (!compatible) - { - software_list_device::display_matches(config, nullptr, software_name); - if (!found) - throw options_error_exception(""); - else - throw options_error_exception("Software '%s' is incompatible with system '%s'\n", software_name, m_system->name); - } - } + // We've found a potential image slot for this value. However, we're only going to specify it + // if the current image option is empty. This is because if there is an image option already + // present, it is almost certain that this was because something was specified at the command + // line and we're parsing an INI. Because INIs have less priority than the command line, this + // should be ignored + // + // Obviously, this ignores that INIs themselves have their own prioritization, so this should be + // considered to be a hack. Instead of having image options being just a straight map of std::string + // it should really be a structure where the priority can be recorded + if (imageiter->second.empty()) + imageiter->second = value; + return true; } - return results; -} - - -//------------------------------------------------- -// slot_option -//------------------------------------------------- - -const slot_option &emu_options::slot_option(const std::string &device_name) const -{ - auto iter = m_slot_options.find(device_name); - assert(iter != m_slot_options.end() && "Attempt to access non-existent slot option"); - return iter->second; -} - -slot_option &emu_options::slot_option(const std::string &device_name) -{ - auto iter = m_slot_options.find(device_name); - assert(iter != m_slot_options.end() && "Attempt to access non-existent slot option"); - return iter->second; -} - - -//------------------------------------------------- -// has_slot_option -//------------------------------------------------- -bool emu_options::has_slot_option(const std::string &device_name) const -{ - return m_slot_options.find(device_name) != m_slot_options.end(); + return false; } //------------------------------------------------- -// image_option -//------------------------------------------------- - -const image_option &emu_options::image_option(const std::string &device_name) const -{ - auto iter = m_image_options.find(device_name); - assert(iter != m_image_options.end() && "Attempt to access non-existent image option"); - return *iter->second; -} - -image_option &emu_options::image_option(const std::string &device_name) -{ - auto iter = m_image_options.find(device_name); - assert(iter != m_image_options.end() && "Attempt to access non-existent image option"); - return *iter->second; -} - - -//************************************************************************** -// SLOT OPTIONS -//************************************************************************** - -//------------------------------------------------- // slot_option ctor //------------------------------------------------- -slot_option::slot_option(emu_options &host, const char *default_value) - : m_host(host) - , m_specified(false) +slot_option::slot_option(const char *default_value) + : m_specified(false) , m_default_value(default_value ? default_value : "") { } @@ -1048,12 +396,10 @@ std::string slot_option::specified_value() const void slot_option::specify(std::string &&text) { - // record the old value; we may need to trigger an update - const std::string old_value = value(); - // we need to do some elementary parsing here const char *bios_arg = ",bios="; - const size_t pos = text.find(bios_arg); + + size_t pos = text.find(bios_arg); if (pos != std::string::npos) { m_specified = true; @@ -1066,37 +412,6 @@ void slot_option::specify(std::string &&text) m_specified_value = std::move(text); m_specified_bios = ""; } - - // we may have changed - possibly_changed(old_value); -} - - -//------------------------------------------------- -// slot_option::set_default_card_software -//------------------------------------------------- - -void slot_option::set_default_card_software(std::string &&s) -{ - // record the old value; we may need to trigger an update - const std::string old_value = value(); - - // update the default card software - m_default_card_software = std::move(s); - - // we may have changed - possibly_changed(old_value); -} - - -//------------------------------------------------- -// slot_option::possibly_changed -//------------------------------------------------- - -void slot_option::possibly_changed(const std::string &old_value) -{ - if (value() != old_value) - m_host.update_slot_and_image_options(); } @@ -1113,73 +428,3 @@ void slot_option::set_bios(std::string &&text) } m_specified_bios = std::move(text); } - - -//------------------------------------------------- -// slot_option::setup_option_entry -//------------------------------------------------- - -core_options::entry::shared_ptr slot_option::setup_option_entry(const char *name) -{ - // this should only be called once - assert(m_entry.expired()); - - // create the entry and return it - core_options::entry::shared_ptr entry = std::make_shared<slot_option_entry>(name, *this); - m_entry = entry; - return entry; -} - - -//************************************************************************** -// IMAGE OPTIONS -//************************************************************************** - -//------------------------------------------------- -// image_option ctor -//------------------------------------------------- - -image_option::image_option(emu_options &host, const std::string &cannonical_instance_name) - : m_host(host) - , m_cannonical_instance_name(cannonical_instance_name) -{ -} - - -//------------------------------------------------- -// image_option::specify -//------------------------------------------------- - -void image_option::specify(const std::string &value) -{ - if (value != m_value) - { - m_value = value; - m_host.reevaluate_default_card_software(); - } -} - -void image_option::specify(std::string &&value) -{ - if (value != m_value) - { - m_value = std::move(value); - m_host.reevaluate_default_card_software(); - } -} - - -//------------------------------------------------- -// image_option::setup_option_entry -//------------------------------------------------- - -core_options::entry::shared_ptr image_option::setup_option_entry(std::vector<std::string> &&names) -{ - // this should only be called once - assert(m_entry.expired()); - - // create the entry and return it - core_options::entry::shared_ptr entry = std::make_shared<image_option_entry>(std::move(names), *this); - m_entry = entry; - return entry; -} diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index 44c636182ec..e592d04d6e3 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -195,77 +195,56 @@ // TYPE DEFINITIONS //************************************************************************** -struct game_driver; -class device_slot_interface; -class emu_options; - class slot_option { public: - slot_option(emu_options &host, const char *default_value); - slot_option(const slot_option &that) = delete; + slot_option(const char *default_value = nullptr); + slot_option(const slot_option &that) = default; slot_option(slot_option &&that) = default; + const slot_option &operator=(const slot_option &that) + { + m_specified = that.m_specified; + m_specified_value = that.m_specified_value; + m_specified_bios = that.m_specified_bios; + m_default_card_software = that.m_default_card_software; + m_default_value = that.m_default_value; + return *this; + } + + const slot_option &operator=(slot_option &&that) + { + m_specified = that.m_specified; + m_specified_value = std::move(that.m_specified_value); + m_specified_bios = std::move(that.m_specified_bios); + m_default_card_software = std::move(that.m_default_card_software); + m_default_value = std::move(that.m_default_value); + return *this; + } + // accessors const std::string &value() const; std::string specified_value() const; const std::string &bios() const { return m_specified_bios; } const std::string &default_card_software() const { return m_default_card_software; } bool specified() const { return m_specified; } - core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); } // seters void specify(std::string &&text); void set_bios(std::string &&text); - void set_default_card_software(std::string &&s); - - // instantiates an option entry (don't call outside of emuopts.cpp) - core_options::entry::shared_ptr setup_option_entry(const char *name); + void set_default_card_software(std::string &&s) { m_default_card_software = std::move(s); } private: - void possibly_changed(const std::string &old_value); - - emu_options & m_host; - bool m_specified; - std::string m_specified_value; - std::string m_specified_bios; - std::string m_default_card_software; - std::string m_default_value; - core_options::entry::weak_ptr m_entry; -}; - - -class image_option -{ -public: - image_option(emu_options &host, const std::string &cannonical_instance_name); - image_option(const image_option &that) = delete; - image_option(image_option &&that) = default; - - // accessors - const std::string &cannonical_instance_name() const { return m_cannonical_instance_name; } - const std::string &value() const { return m_value; } - core_options::entry::shared_ptr option_entry() const { return m_entry.lock(); } - - // mutators - void specify(const std::string &value); - void specify(std::string &&value); - - // instantiates an option entry (don't call outside of emuopts.cpp) - core_options::entry::shared_ptr setup_option_entry(std::vector<std::string> &&names); - -private: - emu_options & m_host; - std::string m_cannonical_instance_name; - std::string m_value; - core_options::entry::weak_ptr m_entry; + bool m_specified; + std::string m_specified_value; + std::string m_specified_bios; + std::string m_default_card_software; + std::string m_default_value; }; class emu_options : public core_options { - friend class slot_option; - friend class image_option; public: enum ui_option { @@ -274,16 +253,11 @@ public: }; // construction/destruction - emu_options(bool general_only = false); - ~emu_options(); - - // mutation - void set_system_name(const std::string &new_system_name); - void set_software(const std::string &new_software); + emu_options(); // core options - const game_driver *system() const { return m_system; } - const char *system_name() const; + const char *system_name() const { return value(OPTION_SYSTEMNAME); } + const char *software_name() const { return value(OPTION_SOFTWARENAME); } // core configuration options bool read_config() const { return bool_value(OPTION_READCONFIG); } @@ -453,51 +427,36 @@ public: const char *language() const { return value(OPTION_LANGUAGE); } - // Web server specific options + // Web server specific optopns bool http() const { return bool_value(OPTION_HTTP); } short http_port() const { return int_value(OPTION_HTTP_PORT); } const char *http_root() const { return value(OPTION_HTTP_ROOT); } // slots and devices - the values for these are stored outside of the core_options // structure - const ::slot_option &slot_option(const std::string &device_name) const; - ::slot_option &slot_option(const std::string &device_name); - bool has_slot_option(const std::string &device_name) const; - const ::image_option &image_option(const std::string &device_name) const; - ::image_option &image_option(const std::string &device_name); + std::map<std::string, slot_option> &slot_options() { return m_slot_options; } + const std::map<std::string, slot_option> &slot_options() const { return m_slot_options; } + std::map<std::string, std::string> &image_options() { return m_image_options; } + const std::map<std::string, std::string> &image_options() const { return m_image_options; } -private: - struct software_options - { - std::unordered_map<std::string, std::string> slot; - std::unordered_map<std::string, std::string> image; - }; - - // slot/image/softlist calculus - software_options evaluate_initial_softlist_options(const std::string &software_identifier); - void update_slot_and_image_options(); - bool add_and_remove_slot_options(); - bool add_and_remove_image_options(); - void reevaluate_default_card_software(); - std::string get_default_card_software(device_slot_interface &slot); +protected: + virtual void value_changed(const std::string &name, const std::string &value) override; + virtual override_get_value_result override_get_value(const char *name, std::string &value) const override; + virtual bool override_set_value(const char *name, const std::string &value) override; - // static list of options entries - static const options_entry s_option_entries[]; - - // the current driver - const game_driver * m_system; +private: + static const options_entry s_option_entries[]; // slots and devices - std::unordered_map<std::string, ::slot_option> m_slot_options; - std::unordered_map<std::string, ::image_option> m_image_options_cannonical; - std::unordered_map<std::string, ::image_option *> m_image_options; + std::map<std::string, slot_option> m_slot_options; + std::map<std::string, std::string> m_image_options; // cached options, for scenarios where parsing core_options is too slow - int m_coin_impulse; - bool m_joystick_contradictory; - bool m_sleep; - bool m_refresh_speed; - ui_option m_ui; + int m_coin_impulse; + bool m_joystick_contradictory; + bool m_sleep; + bool m_refresh_speed; + ui_option m_ui; }; #endif // MAME_EMU_EMUOPTS_H diff --git a/src/emu/image.cpp b/src/emu/image.cpp index 973c2b57db8..25167db54a2 100644 --- a/src/emu/image.cpp +++ b/src/emu/image.cpp @@ -38,12 +38,37 @@ image_manager::image_manager(running_machine &machine) continue; // find the image option in image_options() - const std::string &startup_image(machine.options().image_option(image.instance_name()).value()); + auto iter = machine.options().image_options().find(image.instance_name()); + + // GROSS HACK - if we began our journey with a single device configuration (e.g. - a single + // cartridge system) but later added a device of that type, image.instance_name() will become + // something different. We're going to try to accomodate that specific scenario here + // + // Specific example: 'mame snes -cart1 sufami -cart2 poipoi' - the instance_name() starts out + // as "cartridge" but at the end becomes "cartridge1" + if (iter == machine.options().image_options().end() + && (image.instance_name().rbegin() != image.instance_name().rend()) + && (*image.instance_name().rbegin() == '1')) + { + std::string alternate_instance_name = image.instance_name().substr(0, image.instance_name().size() - 1); + iter = machine.options().image_options().find(alternate_instance_name); + + // If we found something, we need to write it back (so later checks work). We also need to redo + // the find; the act of erasing the old value breaks the iterator + if (iter != machine.options().image_options().end()) + { + std::string temp = std::move(iter->second); + machine.options().image_options()[image.instance_name()] = std::move(temp); + machine.options().image_options().erase(alternate_instance_name); + iter = machine.options().image_options().find(image.instance_name()); + } + } // is an image specified for this image? - if (!startup_image.empty()) + if (iter != machine.options().image_options().end() && !iter->second.empty()) { // we do have a startup image specified - load it + const std::string &startup_image(iter->second); image_init_result result = image_init_result::FAIL; // try as a softlist @@ -193,7 +218,7 @@ void image_manager::options_extract() } // and set the option - machine().options().image_option(image.instance_name()).specify(std::move(image_opt)); + machine().options().image_options()[image.instance_name()] = std::move(image_opt); } } diff --git a/src/emu/mconfig.cpp b/src/emu/mconfig.cpp index ba52b986fa5..84fc5c70b67 100644 --- a/src/emu/mconfig.cpp +++ b/src/emu/mconfig.cpp @@ -39,20 +39,21 @@ machine_config::machine_config(const game_driver &gamedrv, emu_options &options) const char *slot_option_name = owner.tag() + 1; // figure out which device goes into this slot - bool has_option = options.has_slot_option(slot_option_name); + bool has_option = options.slot_options().count(slot_option_name); const char *selval; bool is_default; if (!has_option) { - // The only time we should be getting here is when emuopts.cpp is invoking - // us to evaluate slot/image options, and the internal state of emuopts.cpp has - // not caught up yet + // Theoretically we should never get here; in the long run the expectation is that + // options.slot_options() should be fully qualified and all options should be + // present. However, we're getting late in the MAME 0.185 development cycle and + // I don't want to rip this out (yet) selval = slot.default_option(); is_default = true; } else { - const slot_option &opt = options.slot_option(slot_option_name); + const slot_option &opt = options.slot_options()[slot_option_name]; selval = opt.value().c_str(); is_default = !opt.specified(); } diff --git a/src/emu/natkeyboard.cpp b/src/emu/natkeyboard.cpp index 1aa8a78ed75..e1921b7a21d 100644 --- a/src/emu/natkeyboard.cpp +++ b/src/emu/natkeyboard.cpp @@ -365,7 +365,9 @@ void natural_keyboard::set_in_use(bool usage) { // update active usage m_in_use = usage; - machine().options().set_value(OPTION_NATURAL_KEYBOARD, usage, OPTION_PRIORITY_CMDLINE); + std::string error; + machine().options().set_value(OPTION_NATURAL_KEYBOARD, usage, OPTION_PRIORITY_CMDLINE, error); + assert(error.empty()); // lock out (or unlock) all keyboard inputs for (auto &port : machine().ioport().ports()) diff --git a/src/emu/romload.cpp b/src/emu/romload.cpp index ab2feae3886..118c7fcc0fe 100644 --- a/src/emu/romload.cpp +++ b/src/emu/romload.cpp @@ -1091,9 +1091,10 @@ int open_disk_image(emu_options &options, const game_driver *gamedrv, const rom_ // will only find CHDs for the default configuration. I believe that this in practice will // be acceptable. emu_options driver_specific_options; - driver_specific_options.set_value(OPTION_SYSTEMNAME, options.system_name(), OPTION_PRIORITY_DEFAULT); - driver_specific_options.set_value(OPTION_MEDIAPATH, options.media_path(), OPTION_PRIORITY_DEFAULT); - driver_specific_options.set_value(OPTION_DIFF_DIRECTORY, options.diff_directory(), OPTION_PRIORITY_DEFAULT); + std::string error_string; + driver_specific_options.set_value(OPTION_SYSTEMNAME, options.system_name(), OPTION_PRIORITY_DEFAULT, error_string); + driver_specific_options.set_value(OPTION_MEDIAPATH, options.media_path(), OPTION_PRIORITY_DEFAULT, error_string); + driver_specific_options.set_value(OPTION_DIFF_DIRECTORY, options.diff_directory(), OPTION_PRIORITY_DEFAULT, error_string); // Now that we have an emu_options structure properly set up, we can create a machine_config machine_config config(current_driver, driver_specific_options); @@ -1495,13 +1496,12 @@ rom_load_manager::rom_load_manager(running_machine &machine) specbios.assign(machine.options().bios()); else { - const device_slot_interface *slot = dynamic_cast<const device_slot_interface *>(&device); - const slot_option *slot_opt = slot - ? &machine.options().slot_option(slot->slot_name()) + const char *slot_option_name = device.owner()->tag() + 1; + const slot_option *opt = machine.options().slot_options().count(slot_option_name) + ? &machine.options().slot_options()[slot_option_name] : nullptr; - - specbios = slot_opt && !slot_opt->bios().empty() - ? slot_opt->bios().c_str() + specbios = opt && !opt->bios().empty() + ? opt->bios().c_str() : device.default_bios_tag(); } determine_bios_rom(device, specbios.c_str()); diff --git a/src/frontend/mame/clifront.cpp b/src/frontend/mame/clifront.cpp index 8aa5e0c31c2..c7a01674f47 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -2,7 +2,7 @@ // copyright-holders:Aaron Giles /*************************************************************************** - clifront.cpp + clifront.c Command-line interface frontend for MAME. @@ -190,26 +190,26 @@ cli_frontend::cli_frontend(emu_options &options, osd_interface &osd) cli_frontend::~cli_frontend() { + // nuke any device options since they will leak memory + mame_options::remove_device_options(m_options); } void cli_frontend::start_execution(mame_machine_manager *manager, std::vector<std::string> &args) { - std::ostringstream option_errors; + std::string option_errors; // parse the command line, adding any system-specific options - try - { - m_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE); - } - catch (options_exception &ex) + if (!mame_options::parse_command_line(m_options, args, option_errors)) { // if we failed, check for no command and a system name first; in that case error on the name if (m_options.command().empty() && mame_options::system(m_options) == nullptr && *(m_options.system_name()) != 0) throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.system_name()); // otherwise, error on the options - throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", ex.message().c_str()); + throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", strtrimspace(option_errors).c_str()); } + if (!option_errors.empty()) + osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors).c_str()); // determine the base name of the EXE std::string exename = core_filename_extract_base(args[0], true); @@ -232,11 +232,8 @@ void cli_frontend::start_execution(mame_machine_manager *manager, std::vector<st manager->start_luaengine(); - if (option_errors.tellp() > 0) - { - std::string option_errors_string = option_errors.str(); - osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors_string).c_str()); - } + if (!option_errors.empty()) + osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors).c_str()); // if we can't find it, give an appropriate error const game_driver *system = mame_options::system(m_options); @@ -782,13 +779,8 @@ void cli_frontend::listmedia(const char *gamename) //------------------------------------------------- void cli_frontend::verifyroms(const char *gamename) { - // create our own copy of options for the purposes of ROM validation - // so we are not "polluted" with driver-specific slot/image options - emu_options options(true); - options.copy_from(m_options); - // determine which drivers to output; - driver_enumerator drivlist(options, gamename); + driver_enumerator drivlist(m_options, gamename); unsigned correct = 0; unsigned incorrect = 0; @@ -1364,10 +1356,10 @@ void cli_frontend::execute_commands(const char *exename) } // other commands need the INIs parsed - std::ostringstream option_errors; + std::string option_errors; mame_options::parse_standard_inis(m_options,option_errors); - if (option_errors.tellp() > 0) - osd_printf_error("%s\n", option_errors.str().c_str()); + if (!option_errors.empty()) + osd_printf_error("%s\n", option_errors.c_str()); // createconfig? if (m_options.command() == CLICOMMAND_CREATECONFIG) diff --git a/src/frontend/mame/info.cpp b/src/frontend/mame/info.cpp index b580bc76a7e..fa742dfe2f4 100644 --- a/src/frontend/mame/info.cpp +++ b/src/frontend/mame/info.cpp @@ -193,9 +193,9 @@ info_xml_creator::info_xml_creator(driver_enumerator &drivlist, bool filter_devi : m_output(nullptr) , m_drivlist(drivlist) , m_filter_devices(filter_devices) - , m_lookup_options(true) + , m_lookup_options(m_drivlist.options()) { - m_lookup_options.copy_from(m_drivlist.options()); + mame_options::remove_device_options(m_lookup_options); } diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp index add15da55f9..7e7d5d78572 100644 --- a/src/frontend/mame/luaengine.cpp +++ b/src/frontend/mame/luaengine.cpp @@ -721,6 +721,7 @@ void lua_engine::initialize() emu["app_version"] = &emulator_info::get_bare_build_version; emu["gamename"] = [this](){ return machine().system().description; }; emu["romname"] = [this](){ return machine().basename(); }; + emu["softname"] = [this](){ return machine().options().software_name(); }; emu["keypost"] = [this](const char *keys){ machine().ioport().natkeyboard().post_utf8(keys); }; emu["time"] = [this](){ return machine().time().as_double(); }; emu["start"] = [this](const char *driver) { @@ -921,11 +922,9 @@ void lua_engine::initialize() "entries", sol::property([this](core_options &options) { sol::table table = sol().create_table(); int unadorned_index = 0; - for (auto &curentry : options.entries()) + for(core_options::entry &curentry : options) { - const char *name = curentry->names().size() > 0 - ? curentry->name().c_str() - : nullptr; + const char *name = curentry.name(); bool is_unadorned = false; // check if it's unadorned if (name && strlen(name) && !strcmp(name, options.unadorned(unadorned_index))) @@ -933,8 +932,8 @@ void lua_engine::initialize() unadorned_index++; is_unadorned = true; } - if (curentry->type() != core_options::option_type::HEADER && curentry->type() != core_options::option_type::COMMAND && !is_unadorned) - table[name] = &*curentry; + if (!curentry.is_header() && !curentry.is_command() && !curentry.is_internal() && !is_unadorned) + table[name] = &curentry; } return table; })); @@ -975,19 +974,18 @@ void lua_engine::initialize() e.set_value(val, OPTION_PRIORITY_CMDLINE); }, [this](core_options::entry &e) -> sol::object { - if (e.type() == core_options::option_type::INVALID) + if(!e.type()) return sol::make_object(sol(), sol::nil); switch(e.type()) { - case core_options::option_type::BOOLEAN: + case OPTION_BOOLEAN: return sol::make_object(sol(), atoi(e.value()) != 0); - case core_options::option_type::INTEGER: + case OPTION_INTEGER: return sol::make_object(sol(), atoi(e.value())); - case core_options::option_type::FLOAT: + case OPTION_FLOAT: return sol::make_object(sol(), atof(e.value())); - default: - return sol::make_object(sol(), e.value()); } + return sol::make_object(sol(), e.value()); }), "description", &core_options::entry::description, "default_value", &core_options::entry::default_value, diff --git a/src/frontend/mame/mame.cpp b/src/frontend/mame/mame.cpp index 0046d4c421e..0a954906c35 100644 --- a/src/frontend/mame/mame.cpp +++ b/src/frontend/mame/mame.cpp @@ -124,38 +124,36 @@ void mame_machine_manager::start_luaengine() std::vector<std::string> exclude = split(options().no_plugin(),','); { // parse the file + std::string error; // attempt to open the output file emu_file file(options().ini_path(), OPEN_FLAG_READ); if (file.open("plugin.ini") == osd_file::error::NONE) { - try - { - m_plugins->parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, false); - } - catch (options_exception &) - { - osd_printf_error("**Error loading plugin.ini**\n"); - } + bool result = m_plugins->parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_DRIVER_INI, error); + if (!result) + osd_printf_error("**Error loading plugin.ini**"); } } - for (auto &curentry : m_plugins->entries()) + for (auto &curentry : *m_plugins) { - if (curentry->type() != core_options::option_type::HEADER) + if (!curentry.is_header()) { - if (std::find(include.begin(), include.end(), curentry->name()) != include.end()) + if (std::find(include.begin(), include.end(), curentry.name()) != include.end()) { - m_plugins->set_value(curentry->name(), "1", OPTION_PRIORITY_CMDLINE); + std::string error_string; + m_plugins->set_value(curentry.name(), "1", OPTION_PRIORITY_CMDLINE, error_string); } - if (std::find(exclude.begin(), exclude.end(), curentry->name()) != exclude.end()) + if (std::find(exclude.begin(), exclude.end(), curentry.name()) != exclude.end()) { - m_plugins->set_value(curentry->name(), "0", OPTION_PRIORITY_CMDLINE); + std::string error_string; + m_plugins->set_value(curentry.name(), "0", OPTION_PRIORITY_CMDLINE, error_string); } } } } - if (options().console()) - { - m_plugins->set_value("console", "1", OPTION_PRIORITY_CMDLINE); + if (options().console()) { + std::string error_string; + m_plugins->set_value("console", "1", OPTION_PRIORITY_CMDLINE, error_string); } m_lua->initialize(); @@ -204,8 +202,9 @@ int mame_machine_manager::execute() // parse any INI files as the first thing if (m_options.read_config()) { - std::ostringstream errors; - mame_options::parse_standard_inis(m_options, errors); + m_options.revert(OPTION_PRIORITY_INI); + std::string errors; + mame_options::parse_standard_inis(m_options,errors); } // otherwise, perform validity checks before anything else @@ -217,6 +216,11 @@ int mame_machine_manager::execute() valid.check_shared_source(*system); } + // reevaluate slot options until nothing changes + while (mame_options::reevaluate_slot_options(m_options)) + { + } + // create the machine configuration machine_config config(*system, m_options); @@ -233,13 +237,12 @@ int mame_machine_manager::execute() if (m_new_driver_pending) { // set up new system name and adjust device options accordingly - m_options.set_system_name(m_new_driver_pending->name); + mame_options::set_system_name(m_options,m_new_driver_pending->name); m_firstrun = true; } else { - if (machine.exit_pending()) - m_options.set_system_name(""); + if (machine.exit_pending()) mame_options::set_system_name(m_options,""); } if (machine.exit_pending() && (!started_empty || is_empty)) diff --git a/src/frontend/mame/mameopts.cpp b/src/frontend/mame/mameopts.cpp index 04025630a58..8cb8cff09fa 100644 --- a/src/frontend/mame/mameopts.cpp +++ b/src/frontend/mame/mameopts.cpp @@ -21,21 +21,491 @@ #include <stack> +int mame_options::m_slot_options = 0; +int mame_options::m_device_options = 0; + +//------------------------------------------------- +// add_slot_options - add all of the slot +// options for the configured system +//------------------------------------------------- + +bool mame_options::add_slot_options(emu_options &options, value_specifier_func value_specifier) +{ + // look up the system configured by name; if no match, do nothing + const game_driver *cursystem = system(options); + if (cursystem == nullptr) + return false; + + // create the configuration + machine_config config(*cursystem, options); + + // iterate through all slot devices + int starting_count = options.options_count(); + for (const device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + // skip fixed slots + if (slot.fixed()) + continue; + + // retrieve info about the device instance + const char *name = slot.device().tag() + 1; + if (!options.exists(name)) + { + // first device? add the header as to be pretty + if (m_slot_options++ == 0) + options.add_entry(nullptr, "SLOT DEVICES", OPTION_HEADER | OPTION_FLAG_DEVICE); + + // add the option + options.add_entry(name, nullptr, OPTION_STRING | OPTION_FLAG_DEVICE, slot.default_option(), true); + options.slot_options()[name] = slot_option(slot.default_option()); + + // allow opportunity to specify this value + if (value_specifier) + { + std::string specified_value = value_specifier(name); + if (specified_value != value_specifier_invalid_value()) + options.slot_options()[name].specify(std::move(specified_value)); + } + } + } + return (options.options_count() != starting_count); +} + + +//------------------------------------------------- +// update_slot_options - update slot values +// depending of image mounted +//------------------------------------------------- + +void mame_options::update_slot_options(emu_options &options, const software_part *swpart) +{ + // look up the system configured by name; if no match, do nothing + const game_driver *cursystem = system(options); + if (cursystem == nullptr) + return; + machine_config config(*cursystem, options); + + // iterate through all slot devices + for (device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + // retrieve info about the device instance + const char *name = slot.device().tag() + 1; + if (options.exists(name) && !slot.option_list().empty()) + { + std::string defvalue = get_default_card_software(slot, options); + if (defvalue.empty()) + { + // keep any non-default setting + if (options.priority(name) > OPTION_PRIORITY_DEFAULT) + continue; + + // reinstate the actual default value as configured + if (slot.default_option() != nullptr) + defvalue.assign(slot.default_option()); + } + + // set the value and hide the option if not selectable + options.set_default_value(name, defvalue.c_str()); + const device_slot_option *option = slot.option(defvalue.c_str()); + options.set_flag(name, ~OPTION_FLAG_INTERNAL, (option != nullptr && !option->selectable()) ? OPTION_FLAG_INTERNAL : 0); + } + } + add_device_options(options); +} + + +//------------------------------------------------- +// get_default_card_software +//------------------------------------------------- + +std::string mame_options::get_default_card_software(device_slot_interface &slot, const emu_options &options) +{ + std::string image_path; + std::function<bool(util::core_file &, std::string&)> get_hashfile_extrainfo; + + // figure out if an image option has been specified, and if so, get the image path out of the options + device_image_interface *image = dynamic_cast<device_image_interface *>(&slot); + if (image) + { + auto iter = options.image_options().find(image->instance_name()); + if (iter != options.image_options().end()) + image_path = iter->second; + + get_hashfile_extrainfo = [image, &options](util::core_file &file, std::string &extrainfo) + { + util::hash_collection hashes = image->calculate_hash_on_file(file); + + return hashfile_extrainfo( + options.hash_path(), + image->device().mconfig().gamedrv(), + hashes, + extrainfo); + }; + } + + // create the hook + get_default_card_software_hook hook(image_path, std::move(get_hashfile_extrainfo)); + + // and invoke the slot's implementation of get_default_card_software() + return slot.get_default_card_software(hook); +} + + +//------------------------------------------------- +// add_device_options - add all of the device +// options for the configured system +//------------------------------------------------- + +void mame_options::add_device_options(emu_options &options, value_specifier_func value_specifier) +{ + // look up the system configured by name; if no match, do nothing + const game_driver *cursystem = system(options); + if (cursystem == nullptr) + return; + machine_config config(*cursystem, options); + + // iterate through all image devices + for (device_image_interface &image : image_interface_iterator(config.root_device())) + { + if (!image.user_loadable()) + continue; + + // add the option + if (!options.exists(image.instance_name().c_str())) + { + // first device? add the header as to be pretty + if (m_device_options++ == 0) + options.add_entry(nullptr, "IMAGE DEVICES", OPTION_HEADER | OPTION_FLAG_DEVICE); + + // add the option + std::string option_name = get_full_option_name(image); + options.add_entry(option_name.c_str(), nullptr, OPTION_STRING | OPTION_FLAG_DEVICE, nullptr, true); + options.image_options()[image.instance_name()] = ""; + + // allow opportunity to specify this value + if (value_specifier) + { + std::string value = value_specifier(image.instance_name()); + if (value != value_specifier_invalid_value()) + options.image_options()[image.instance_name()] = std::move(value); + } + } + } +} + + +//------------------------------------------------- +// remove_device_options - remove device options +//------------------------------------------------- + +std::string mame_options::get_full_option_name(const device_image_interface &image) +{ + std::ostringstream option_name; + util::stream_format(option_name, "%s;%s", image.instance_name(), image.brief_instance_name()); + if (strcmp(image.device_typename(image.image_type()), image.instance_name().c_str()) == 0) + util::stream_format(option_name, ";%s1;%s1", image.instance_name(), image.brief_instance_name()); + return option_name.str(); +} + + +//------------------------------------------------- +// remove_device_options - remove device options +//------------------------------------------------- + +void mame_options::remove_device_options(emu_options &options) +{ + // iterate through options and remove interesting ones + core_options::entry *nextentry; + for (auto *curentry = options.first(); curentry != nullptr; curentry = nextentry) + { + // pre-fetch the next entry in case we delete this one + nextentry = curentry->next(); + + // if this is a device option, nuke it + if ((curentry->flags() & OPTION_FLAG_DEVICE) != 0) + options.remove_entry(*curentry); + } + + // take also care of ramsize options + options.set_default_value(OPTION_RAMSIZE, ""); + + // reset counters + m_slot_options = 0; + m_device_options = 0; +} + + +//------------------------------------------------- +// parse_slot_devices - parse the command line +// and update slot and image devices +//------------------------------------------------- + +void mame_options::parse_slot_devices(emu_options &options, value_specifier_func value_specifier) +{ + bool still_adding = true; + while (still_adding) + { + // keep adding slot options until we stop seeing new stuff + still_adding = false; + while (add_slot_options(options, value_specifier)) + still_adding = true; + + // add device options + add_device_options(options, value_specifier); + + if (reevaluate_slot_options(options)) + still_adding = true; + } +} + + +//------------------------------------------------- +// reevaluate_slot_options - based on recent changes +// in what images are mounted, give drivers a chance +// to specify new default slot options +//------------------------------------------------- + +bool mame_options::reevaluate_slot_options(emu_options &options) +{ + bool result = false; + + // look up the system configured by name; if no match, do nothing + const game_driver *cursystem = system(options); + if (cursystem == nullptr) + return result; + machine_config config(*cursystem, options); + + // iterate through all slot devices + for (device_slot_interface &slot : slot_interface_iterator(config.root_device())) + { + // retrieve info about the device instance + const char *name = slot.device().tag() + 1; + if (options.exists(name) && !slot.option_list().empty()) + { + // device_slot_interface::get_default_card_software() is essentially a hook + // that lets devices provide a feedback loop to force a specified software + // list entry to be loaded + // + // In the repeated cycle of adding slots and slot devices, this gives a chance + // for devices to "plug in" default software list items. Of course, the fact + // that this is all shuffling options is brittle and roundabout, but such is + // the nature of software lists. + // + // In reality, having some sort of hook into the pipeline of slot/device evaluation + // makes sense, but the fact that it is joined at the hip to device_image_interface + // and device_slot_interface is unfortunate + std::string default_card_software = get_default_card_software(slot, options); + if (!default_card_software.empty()) + { + // we have default card software - is this resulting in the slot option being mutated? + if (options.slot_options()[name].default_card_software() != default_card_software) + { + options.slot_options()[name].set_default_card_software(std::move(default_card_software)); + result = true; + } + } + } + } + return result; +} + + +//------------------------------------------------- +// parse_command_line - parse the command line +// and update the devices +//------------------------------------------------- + +bool mame_options::parse_command_line(emu_options &options, std::vector<std::string> &args, std::string &error_string) +{ + // parse the command line + if (!options.parse_command_line(args, OPTION_PRIORITY_CMDLINE, error_string)) + return false; + + // identify any options as a result of softlists + auto softlist_opts = evaluate_initial_softlist_options(options); + + // assemble a "value specifier" that will be used to specify options set up as a consequence + // of slot and device setup + auto value_specifier = [&options, &softlist_opts, &args, &error_string](const std::string &arg) + { + // first find within the command line + std::string arg_value; + bool success = options.pluck_from_command_line(args, arg, arg_value); + + // next try to find within softlist-specified options + if (!success) + { + auto iter = softlist_opts.find(arg); + if (iter != softlist_opts.end()) + { + arg_value = iter->second; + success = true; + } + } + + // did we find something? + return success + ? arg_value + : value_specifier_invalid_value(); + }; + + // parse the slot devices + parse_slot_devices(options, value_specifier); + + // at this point, we should have handled all arguments; the only argument that shouldn't have + // been handled is the file name + if (args.size() > 1) + { + error_string.append(string_format("Error: unknown option: %s\n", args[1])); + return false; + } + + return true; +} + + +//------------------------------------------------- +// evaluate_initial_softlist_options +//------------------------------------------------- + +std::map<std::string, std::string> mame_options::evaluate_initial_softlist_options(emu_options &options) +{ + std::map<std::string, std::string> results; + + // load software specified at the command line (if any of course) + std::string software_identifier = options.software_name(); + if (!software_identifier.empty()) + { + // we have software; first identify the proper game_driver + const game_driver *system = mame_options::system(options); + if (system == nullptr && *(options.system_name()) != 0) + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", options.system_name()); + + // and set up a configuration + machine_config config(*system, options); + software_list_device_iterator iter(config.root_device()); + if (iter.count() == 0) + throw emu_fatalerror(EMU_ERR_FATALERROR, "Error: unknown option: %s\n", options.software_name()); + + // and finally set up the stack + std::stack<std::string> software_identifier_stack; + software_identifier_stack.push(software_identifier); + + // we need to keep evaluating softlist identifiers until the stack is empty + while (!software_identifier_stack.empty()) + { + // pop the identifier + software_identifier = std::move(software_identifier_stack.top()); + software_identifier_stack.pop(); + + // and parse it + std::string list_name, software_name; + auto colon_pos = software_identifier.find_first_of(':'); + if (colon_pos != std::string::npos) + { + list_name = software_identifier.substr(0, colon_pos); + software_name = software_identifier.substr(colon_pos + 1); + } + else + { + software_name = software_identifier; + } + + // loop through all softlist devices, and try to find one capable of handling the requested software + bool found = false; + bool compatible = false; + for (software_list_device &swlistdev : iter) + { + if (list_name.empty() || (list_name == swlistdev.list_name())) + { + const software_info *swinfo = swlistdev.find(software_name); + if (swinfo != nullptr) + { + // loop through all parts + for (const software_part &swpart : swinfo->parts()) + { + // only load compatible software this way + if (swlistdev.is_compatible(swpart) == SOFTWARE_IS_COMPATIBLE) + { + // we need to find a mountable image slot, but we need to ensure it is a slot + // for which we have not already distributed a part to + device_image_interface *image = software_list_device::find_mountable_image( + config, + swpart, + [&results](const device_image_interface &candidate) { return results.count(candidate.instance_name()) == 0; }); + + // did we find a slot to put this part into? + if (image != nullptr) + { + // we've resolved this software + results[image->instance_name()] = string_format("%s:%s:%s", swlistdev.list_name(), software_name, swpart.name()); + + // does this software part have a requirement on another part? + const char *requirement = swpart.feature("requirement"); + if (requirement) + software_identifier_stack.push(requirement); + } + compatible = true; + } + found = true; + } + + // identify other shared features specified as '<<slot name>>_default' + // + // example from SMS: + // + // <software name = "alexbmx"> + // ... + // <sharedfeat name = "ctrl1_default" value = "paddle" /> + // </software> + for (const feature_list_item &fi : swinfo->shared_info()) + { + const std::string default_suffix = "_default"; + if (fi.name().size() > default_suffix.size() + && fi.name().compare(fi.name().size() - default_suffix.size(), default_suffix.size(), default_suffix) == 0) + { + std::string slot_name = fi.name().substr(0, fi.name().size() - default_suffix.size()); + results[slot_name] = fi.value(); + } + } + } + } + if (compatible) + break; + } + + if (!compatible) + { + software_list_device::display_matches(config, nullptr, software_name); + if (!found) + throw emu_fatalerror(EMU_ERR_FATALERROR, nullptr); + else + throw emu_fatalerror(EMU_ERR_FATALERROR, "Software '%s' is incompatible with system '%s'\n", software_name.c_str(), options.system_name()); + } + } + } + return results; +} + + //------------------------------------------------- // parse_standard_inis - parse the standard set // of INI files //------------------------------------------------- -void mame_options::parse_standard_inis(emu_options &options, std::ostream &error_stream, const game_driver *driver) +void mame_options::parse_standard_inis(emu_options &options, std::string &error_string, const game_driver *driver) { + // start with an empty string + error_string.clear(); + // parse the INI file defined by the platform (e.g., "mame.ini") // we do this twice so that the first file can change the INI path - parse_one_ini(options, emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI); - parse_one_ini(options, emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI, &error_stream); + parse_one_ini(options,emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI); + parse_one_ini(options,emulator_info::get_configname(), OPTION_PRIORITY_MAME_INI, &error_string); // debug mode: parse "debug.ini" as well if (options.debug()) - parse_one_ini(options, "debug", OPTION_PRIORITY_DEBUG_INI, &error_stream); + parse_one_ini(options,"debug", OPTION_PRIORITY_DEBUG_INI, &error_string); // if we have a valid system driver, parse system-specific INI files const game_driver *cursystem = (driver == nullptr) ? system(options) : driver; @@ -44,18 +514,18 @@ void mame_options::parse_standard_inis(emu_options &options, std::ostream &error // parse "vertical.ini" or "horizont.ini" if (cursystem->flags & ORIENTATION_SWAP_XY) - parse_one_ini(options, "vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); + parse_one_ini(options,"vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_string); else - parse_one_ini(options, "horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); + parse_one_ini(options,"horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_string); if (cursystem->flags & MACHINE_TYPE_ARCADE) - parse_one_ini(options, "arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); + parse_one_ini(options,"arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_string); else if (cursystem->flags & MACHINE_TYPE_CONSOLE) - parse_one_ini(options ,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); + parse_one_ini(options,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_string); else if (cursystem->flags & MACHINE_TYPE_COMPUTER) - parse_one_ini(options, "computer", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); + parse_one_ini(options,"computer", OPTION_PRIORITY_SYSTYPE_INI, &error_string); else if (cursystem->flags & MACHINE_TYPE_OTHER) - parse_one_ini(options, "othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); + parse_one_ini(options,"othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_string); machine_config config(*cursystem, options); for (const screen_device &device : screen_device_iterator(config.root_device())) @@ -63,35 +533,35 @@ void mame_options::parse_standard_inis(emu_options &options, std::ostream &error // parse "raster.ini" for raster games if (device.screen_type() == SCREEN_TYPE_RASTER) { - parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream); + parse_one_ini(options,"raster", OPTION_PRIORITY_SCREEN_INI, &error_string); break; } // parse "vector.ini" for vector games if (device.screen_type() == SCREEN_TYPE_VECTOR) { - parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream); + parse_one_ini(options,"vector", OPTION_PRIORITY_SCREEN_INI, &error_string); break; } // parse "lcd.ini" for lcd games if (device.screen_type() == SCREEN_TYPE_LCD) { - parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream); + parse_one_ini(options,"lcd", OPTION_PRIORITY_SCREEN_INI, &error_string); break; } } // next parse "source/<sourcefile>.ini" std::string sourcename = core_filename_extract_base(cursystem->source_file, true).insert(0, "source" PATH_SEPARATOR); - parse_one_ini(options, sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_stream); + parse_one_ini(options,sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_string); // then parse the grandparent, parent, and system-specific INIs int parent = driver_list::clone(*cursystem); int gparent = (parent != -1) ? driver_list::clone(parent) : -1; if (gparent != -1) - parse_one_ini(options, driver_list::driver(gparent).name, OPTION_PRIORITY_GPARENT_INI, &error_stream); + parse_one_ini(options,driver_list::driver(gparent).name, OPTION_PRIORITY_GPARENT_INI, &error_string); if (parent != -1) - parse_one_ini(options, driver_list::driver(parent).name, OPTION_PRIORITY_PARENT_INI, &error_stream); - parse_one_ini(options, cursystem->name, OPTION_PRIORITY_DRIVER_INI, &error_stream); + parse_one_ini(options,driver_list::driver(parent).name, OPTION_PRIORITY_PARENT_INI, &error_string); + parse_one_ini(options,cursystem->name, OPTION_PRIORITY_DRIVER_INI, &error_string); } @@ -108,33 +578,115 @@ const game_driver *mame_options::system(const emu_options &options) //------------------------------------------------- +// set_system_name - set a new system name +//------------------------------------------------- + +void mame_options::set_system_name(emu_options &options, const char *name) +{ + // remember the original system name + std::string old_system_name(options.system_name()); + bool new_system = old_system_name.compare(name) != 0; + + // if the system name changed, fix up the device options + if (new_system) + { + // first set the new name + std::string error; + options.set_value(OPTION_SYSTEMNAME, name, OPTION_PRIORITY_CMDLINE, error); + assert(error.empty()); + + // remove any existing device options + remove_device_options(options); + } + else + { + // revert device options set for the old software + options.revert(OPTION_PRIORITY_SUBCMD, OPTION_PRIORITY_SUBCMD); + } + + // get the new system + const game_driver *cursystem = system(options); + if (cursystem == nullptr) + return; + + if (*options.software_name() != 0) + { + std::string sw_load(options.software_name()); + std::string sw_list, sw_name, sw_part, sw_instance, error_string; + int left = sw_load.find_first_of(':'); + int middle = sw_load.find_first_of(':', left + 1); + int right = sw_load.find_last_of(':'); + + sw_list = sw_load.substr(0, left); + sw_name = sw_load.substr(left + 1, middle - left - 1); + sw_part = sw_load.substr(middle + 1, right - middle - 1); + sw_instance = sw_load.substr(right + 1); + sw_load.assign(sw_load.substr(0, right)); + + // look up the software part + machine_config config(*cursystem, options); + software_list_device *swlist = software_list_device::find_by_name(config, sw_list.c_str()); + const software_info *swinfo = swlist != nullptr ? swlist->find(sw_name.c_str()) : nullptr; + const software_part *swpart = swinfo != nullptr ? swinfo->find_part(sw_part.c_str()) : nullptr; + if (swpart == nullptr) + osd_printf_warning("Could not find %s in software list\n", options.software_name()); + + // then add the options + if (new_system) + { + while (add_slot_options(options)) {} + add_device_options(options); + } + + options.set_value(OPTION_SOFTWARENAME, sw_name.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + if (options.exists(sw_instance.c_str())) + options.set_value(sw_instance.c_str(), sw_load.c_str(), OPTION_PRIORITY_SUBCMD, error_string); + + int num; + do { + num = options.options_count(); + update_slot_options(options,swpart); + } while (num != options.options_count()); + } + else if (new_system) + { + // add the options afresh + while (add_slot_options(options)) {} + add_device_options(options); + int num; + do { + num = options.options_count(); + update_slot_options(options); + } while (num != options.options_count()); + } +} + +//------------------------------------------------- // parse_one_ini - parse a single INI file //------------------------------------------------- -void mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream) +bool mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::string *error_string) { // don't parse if it has been disabled if (!options.read_config()) - return; + return false; // open the file; if we fail, that's ok emu_file file(options.ini_path(), OPEN_FLAG_READ); osd_printf_verbose("Attempting load of %s.ini\n", basename); osd_file::error filerr = file.open(basename, ".ini"); if (filerr != osd_file::error::NONE) - return; + return false; // parse the file osd_printf_verbose("Parsing %s.ini\n", basename); - try - { - options.parse_ini_file((util::core_file&)file, priority, false); - } - catch (options_exception &ex) - { - if (error_stream) - util::stream_format(*error_stream, "While parsing %s:\n%s\n", ex.message(), file.fullpath(), ex.message()); - return; - } + std::string error; + bool result = options.parse_ini_file((util::core_file&)file, priority, OPTION_PRIORITY_DRIVER_INI, error); + + // append errors if requested + if (!error.empty() && error_string) + error_string->append(string_format("While parsing %s:\n%s\n", file.fullpath(), error)); + return result; } + diff --git a/src/frontend/mame/mameopts.h b/src/frontend/mame/mameopts.h index 68c0d683d0d..3c543e65c66 100644 --- a/src/frontend/mame/mameopts.h +++ b/src/frontend/mame/mameopts.h @@ -50,14 +50,42 @@ class software_part; class mame_options { + static const uint32_t OPTION_FLAG_DEVICE = 0x80000000; + public: + typedef std::function<std::string (const std::string &)> value_specifier_func; + // parsing wrappers - static void parse_standard_inis(emu_options &options, std::ostream &error_stream, const game_driver *driver = nullptr); + static bool parse_command_line(emu_options &options, std::vector<std::string> &args, std::string &error_string); + static void parse_standard_inis(emu_options &options, std::string &error_string, const game_driver *driver = nullptr); + // FIXME: Couriersud: This should be in image_device_exit + static void remove_device_options(emu_options &options); + static const game_driver *system(const emu_options &options); + static void set_system_name(emu_options &options, const char *name); + static bool add_slot_options(emu_options &options, value_specifier_func value_specifier = nullptr); + static bool reevaluate_slot_options(emu_options &options); private: + // device-specific option handling + static void add_device_options(emu_options &options, value_specifier_func value_specifier = nullptr); + static void update_slot_options(emu_options &options, const software_part *swpart = nullptr); + static void parse_slot_devices(emu_options &options, value_specifier_func value_specifier); + static std::string get_full_option_name(const device_image_interface &image); + static std::string get_default_card_software(device_slot_interface &slot, const emu_options &options); + // INI parsing helper - static void parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream = nullptr); + static bool parse_one_ini(emu_options &options, const char *basename, int priority, std::string *error_string = nullptr); + + // softlist handling + static std::map<std::string, std::string> evaluate_initial_softlist_options(emu_options &options); + + // represents an "invalid" value (an empty string is valid so we can't use that; what I + // really want to return is std::optional<std::string> but C++17 isn't here yet) + static std::string value_specifier_invalid_value() { return std::string("\x01\x02\x03"); } + + static int m_slot_options; + static int m_device_options; }; #endif /* __MAMEOPTS_H__ */ diff --git a/src/frontend/mame/pluginopts.cpp b/src/frontend/mame/pluginopts.cpp index b9a9e8d3c3b..779ac6ff709 100644 --- a/src/frontend/mame/pluginopts.cpp +++ b/src/frontend/mame/pluginopts.cpp @@ -75,7 +75,7 @@ void plugin_options::parse_json(std::string path) if (type=="plugin") { - add_entry({ std::move(plugin_name) }, core_strdup(description.c_str()), option_type::BOOLEAN, start ? "1" : "0"); + add_entry(core_strdup(plugin_name.c_str()),core_strdup(description.c_str()), OPTION_BOOLEAN, start ? "1" : "0"); } } diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp index 438840f0fc0..17a5c22539b 100644 --- a/src/frontend/mame/ui/custui.cpp +++ b/src/frontend/mame/ui/custui.cpp @@ -64,10 +64,12 @@ menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container menu_custom_ui::~menu_custom_ui() { - ui().options().set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE); + std::string error_string; + ui().options().set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE, error_string); if (!m_lang.empty()) { - machine().options().set_value(OPTION_LANGUAGE, m_lang[m_currlang].c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_LANGUAGE, m_lang[m_currlang].c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(OPTION_LANGUAGE); load_translation(machine().options()); } ui_globals::reset = true; @@ -213,10 +215,22 @@ menu_font_ui::menu_font_ui(mame_ui_manager &mui, render_container &container) : m_info_size = moptions.infos_size(); m_font_size = moptions.font_rows(); - m_info_max = atof(moptions.get_entry(OPTION_INFOS_SIZE)->maximum()); - m_info_min = atof(moptions.get_entry(OPTION_INFOS_SIZE)->minimum()); - m_font_max = atof(moptions.get_entry(OPTION_FONT_ROWS)->maximum()); - m_font_max = atof(moptions.get_entry(OPTION_FONT_ROWS)->minimum()); + + for (ui_options::entry &f_entry : moptions) + { + const char *entry_name = f_entry.name(); + if (entry_name && strlen(entry_name) && !strcmp(OPTION_INFOS_SIZE, f_entry.name())) + { + m_info_max = atof(f_entry.maximum()); + m_info_min = atof(f_entry.minimum()); + } + else if (entry_name && strlen(entry_name) && !strcmp(OPTION_FONT_ROWS, f_entry.name())) + { + m_font_max = atof(f_entry.maximum()); + m_font_min = atof(f_entry.minimum()); + } + } + } //------------------------------------------------- @@ -250,9 +264,11 @@ menu_font_ui::~menu_font_ui() name.insert(0, "[B]"); } #endif - machine().options().set_value(OPTION_UI_FONT, name, OPTION_PRIORITY_CMDLINE); - moptions.set_value(OPTION_INFOS_SIZE, m_info_size, OPTION_PRIORITY_CMDLINE); - moptions.set_value(OPTION_FONT_ROWS, m_font_size, OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_UI_FONT, name.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(OPTION_UI_FONT); + + moptions.set_value(OPTION_INFOS_SIZE, m_info_size, OPTION_PRIORITY_CMDLINE, error_string); + moptions.set_value(OPTION_FONT_ROWS, m_font_size, OPTION_PRIORITY_CMDLINE, error_string); } //------------------------------------------------- @@ -447,11 +463,11 @@ menu_colors_ui::menu_colors_ui(mame_ui_manager &mui, render_container &container menu_colors_ui::~menu_colors_ui() { - std::string dec_color; + std::string error_string, dec_color; for (int index = 1; index < MUI_RESTORE; index++) { dec_color = string_format("%x", (uint32_t)m_color_table[index].color); - ui().options().set_value(m_color_table[index].option, dec_color.c_str(), OPTION_PRIORITY_CMDLINE); + ui().options().set_value(m_color_table[index].option, dec_color.c_str(), OPTION_PRIORITY_CMDLINE, error_string); } } diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp index 2d0200ef099..1ef39925bfd 100644 --- a/src/frontend/mame/ui/dirmenu.cpp +++ b/src/frontend/mame/ui/dirmenu.cpp @@ -352,10 +352,11 @@ void menu_add_change_folder::handle() if (m_change) { if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); + ui().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE, error_string); else if (strcmp(machine().options().value(s_folders[m_ref].option), m_current_path.c_str()) != 0) { - machine().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(s_folders[m_ref].option); } } else @@ -370,10 +371,11 @@ void menu_add_change_folder::handle() } if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); + ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE, error_string); else if (strcmp(machine().options().value(s_folders[m_ref].option), tmppath.c_str()) != 0) { - machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(s_folders[m_ref].option); } } @@ -590,10 +592,11 @@ void menu_remove_folder::handle() } if (ui().options().exists(s_folders[m_ref].option)) - ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); + ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE, error_string); else if (strcmp(machine().options().value(s_folders[m_ref].option),tmppath.c_str())!=0) { - machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(s_folders[m_ref].option); } reset_parent(reset_options::REMEMBER_REF); diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index ab0e6d85cd3..8101faad3c6 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -326,7 +326,8 @@ std::string machine_info::mandatory_images() { if (image.must_be_loaded()) { - if (m_machine.options().image_option(image.instance_name()).value().empty()) + auto iter = m_machine.options().image_options().find(image.instance_name()); + if (iter == m_machine.options().image_options().end() || iter->second.empty()) { if (is_first) is_first = false; diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index 66201951a91..3ec2bf78cd3 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -127,10 +127,12 @@ void menu_bios_selection::handle() if (val>cnt) val=1; dev->set_system_bios(val); if (strcmp(dev->tag(),":")==0) { - machine().options().set_value("bios", val-1, OPTION_PRIORITY_CMDLINE); + std::string error; + machine().options().set_value("bios", val-1, OPTION_PRIORITY_CMDLINE, error); + assert(error.empty()); } else { const char *slot_option_name = dev->owner()->tag() + 1; - machine().options().slot_option(slot_option_name).set_bios(string_format("%d", val - 1)); + machine().options().slot_options()[slot_option_name].set_bios(string_format("%d", val - 1)); } reset(reset_options::REMEMBER_REF); } @@ -673,14 +675,15 @@ void menu_export::populate(float &customtop, float &custombottom) menu_machine_configure::menu_machine_configure(mame_ui_manager &mui, render_container &container, const game_driver *prev, float _x0, float _y0) : menu(mui, container) , m_drv(prev) + , m_opts(mui.machine().options()) , x0(_x0) , y0(_y0) , m_curbios(0) , m_fav_reset(false) { // parse the INI file - std::ostringstream error; - mame_options::parse_standard_inis(m_opts, error, m_drv); + std::string error; + mame_options::parse_standard_inis(m_opts,error, m_drv); setup_bios(); } @@ -751,7 +754,9 @@ void menu_machine_configure::handle() else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) { (menu_event->iptkey == IPT_UI_LEFT) ? --m_curbios : ++m_curbios; - m_opts.set_value(OPTION_BIOS, m_bios[m_curbios].second, OPTION_PRIORITY_CMDLINE); + std::string error; + m_opts.set_value(OPTION_BIOS, m_bios[m_curbios].second, OPTION_PRIORITY_CMDLINE, error); + m_opts.mark_changed(OPTION_BIOS); reset(reset_options::REMEMBER_POSITION); } } @@ -913,7 +918,8 @@ void menu_plugins_configure::handle() if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT || menu_event->iptkey == IPT_UI_SELECT) { int oldval = plugins.int_value((const char*)menu_event->itemref); - plugins.set_value((const char*)menu_event->itemref, oldval == 1 ? 0 : 1, OPTION_PRIORITY_CMDLINE); + std::string error_string; + plugins.set_value((const char*)menu_event->itemref, oldval == 1 ? 0 : 1, OPTION_PRIORITY_CMDLINE, error_string); changed = true; } } @@ -929,13 +935,13 @@ void menu_plugins_configure::populate(float &customtop, float &custombottom) { plugin_options& plugins = mame_machine_manager::instance()->plugins(); - for (auto &curentry : plugins.entries()) + for (auto &curentry : plugins) { - if (curentry->type() != OPTION_HEADER) + if (!curentry.is_header()) { - auto enabled = !strcmp(curentry->value(), "1"); - item_append(curentry->description(), enabled ? _("On") : _("Off"), - enabled ? FLAG_RIGHT_ARROW : FLAG_LEFT_ARROW, (void *)(uintptr_t)curentry->name().c_str()); + auto enabled = std::string(curentry.value()) == "1"; + item_append(curentry.description(), enabled ? _("On") : _("Off"), + enabled ? FLAG_RIGHT_ARROW : FLAG_LEFT_ARROW, (void *)(uintptr_t)curentry.name()); } } item_append(menu_item_type::SEPARATOR); diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index 04d1c2b2350..e2628f63a9b 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -116,7 +116,8 @@ menu_select_game::menu_select_game(mame_ui_manager &mui, render_container &conta if (!moptions.remember_last()) reselect_last::reset(); - mui.machine().options().set_value(OPTION_SNAPNAME, "%g/%i", OPTION_PRIORITY_CMDLINE); + mui.machine().options().set_value(OPTION_SNAPNAME, "%g/%i", OPTION_PRIORITY_CMDLINE, error_string); + mui.machine().options().set_value(OPTION_SOFTWARENAME, "", OPTION_PRIORITY_CMDLINE, error_string); ui_globals::curimage_view = FIRST_VIEW; ui_globals::curdats_view = 0; @@ -154,9 +155,9 @@ menu_select_game::~menu_select_game() filter.append(",").append(c_year::ui[c_year::actual]); ui_options &mopt = ui().options(); - mopt.set_value(OPTION_LAST_USED_FILTER, filter.c_str(), OPTION_PRIORITY_CMDLINE); - mopt.set_value(OPTION_LAST_USED_MACHINE, last_driver.c_str(), OPTION_PRIORITY_CMDLINE); - mopt.set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_LAST_USED_FILTER, filter.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + mopt.set_value(OPTION_LAST_USED_MACHINE, last_driver.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + mopt.set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE, error_string); ui().save_ui_options(); } @@ -929,10 +930,11 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) return; } + std::string error_string; std::string string_list = std::string(ui_swinfo->listname).append(":").append(ui_swinfo->shortname).append(":").append(ui_swinfo->part).append(":").append(ui_swinfo->instance); - mopt.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); std::string snap_list = std::string(ui_swinfo->listname).append(PATH_SEPARATOR).append(ui_swinfo->shortname); - mopt.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); + mopt.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); reselect_last::driver = drv.driver().name; reselect_last::software = ui_swinfo->shortname; reselect_last::swlist = ui_swinfo->listname; diff --git a/src/frontend/mame/ui/selsoft.cpp b/src/frontend/mame/ui/selsoft.cpp index 23107e4d992..b5eeb36b204 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -144,6 +144,9 @@ menu_select_software::menu_select_software(mame_ui_manager &mui, render_containe ui_globals::switch_image = true; ui_globals::cur_sw_dats_view = 0; ui_globals::cur_sw_dats_total = 1; + + std::string error_string; + mui.machine().options().set_value(OPTION_SOFTWARENAME, "", OPTION_PRIORITY_CMDLINE, error_string); } //------------------------------------------------- @@ -715,10 +718,11 @@ void menu_select_software::inkey_select(const event *menu_event) return; } - machine().options().set_system_name(m_driver->name); - machine().options().set_value(OPTION_SOFTWARENAME, ui_swinfo->shortname, OPTION_PRIORITY_CMDLINE); + std::string error_string; + std::string string_list = std::string(ui_swinfo->listname).append(":").append(ui_swinfo->shortname).append(":").append(ui_swinfo->part).append(":").append(ui_swinfo->instance); + machine().options().set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); std::string snap_list = std::string(ui_swinfo->listname).append(PATH_SEPARATOR).append(ui_swinfo->shortname); - machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); reselect_last::driver = drivlist.driver().name; reselect_last::software = ui_swinfo->shortname; reselect_last::swlist = ui_swinfo->listname; @@ -1284,8 +1288,9 @@ void software_parts::handle() { if ((void*)&elem == menu_event->itemref) { + std::string error_string; std::string string_list = std::string(m_uiinfo->listname).append(":").append(m_uiinfo->shortname).append(":").append(elem.first).append(":").append(m_uiinfo->instance); - machine().options().set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); reselect_last::driver = m_uiinfo->driver->name; reselect_last::software = m_uiinfo->shortname; @@ -1293,7 +1298,7 @@ void software_parts::handle() reselect_last::set(true); std::string snap_list = std::string(m_uiinfo->listname).append("/").append(m_uiinfo->shortname); - machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); + machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); mame_machine_manager::instance()->schedule_new_driver(*m_uiinfo->driver); machine().schedule_hard_reset(); @@ -1395,7 +1400,8 @@ void bios_selection::handle() reselect_last::set(true); } - moptions.set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE); + std::string error; + moptions.set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE, error); mame_machine_manager::instance()->schedule_new_driver(*s_driver); machine().schedule_hard_reset(); stack_reset(); @@ -1403,7 +1409,8 @@ void bios_selection::handle() else { ui_software_info *ui_swinfo = (ui_software_info *)m_driver; - machine().options().set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE); + std::string error; + machine().options().set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE, error); driver_enumerator drivlist(machine().options(), *ui_swinfo->driver); drivlist.next(); software_list_device *swlist = software_list_device::find_by_name(*drivlist.config(), ui_swinfo->listname.c_str()); @@ -1424,10 +1431,11 @@ void bios_selection::handle() menu::stack_push<software_parts>(ui(), container(), parts, ui_swinfo); return; } + std::string error_string; std::string string_list = std::string(ui_swinfo->listname).append(":").append(ui_swinfo->shortname).append(":").append(ui_swinfo->part).append(":").append(ui_swinfo->instance); - moptions.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); std::string snap_list = std::string(ui_swinfo->listname).append(PATH_SEPARATOR).append(ui_swinfo->shortname); - moptions.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); reselect_last::driver = drivlist.driver().name; reselect_last::software = ui_swinfo->shortname; reselect_last::swlist = ui_swinfo->listname; diff --git a/src/frontend/mame/ui/slotopt.cpp b/src/frontend/mame/ui/slotopt.cpp index 8434a667899..42ec9e1a896 100644 --- a/src/frontend/mame/ui/slotopt.cpp +++ b/src/frontend/mame/ui/slotopt.cpp @@ -27,9 +27,9 @@ device_slot_option *menu_slot_devices::slot_get_current_option(device_slot_inter std::string current; const char *slot_option_name = slot.device().tag() + 1; - if (!slot.fixed()) + if (!slot.fixed() && machine().options().slot_options().count(slot_option_name) > 0) { - current = machine().options().slot_option(slot_option_name).value(); + current = machine().options().slot_options()[slot_option_name].value(); } else { @@ -140,7 +140,9 @@ const char *menu_slot_devices::slot_get_option(device_slot_interface &slot, int void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char *val) { - machine().options().set_value(slot.device().tag()+1, val, OPTION_PRIORITY_CMDLINE); + std::string error; + machine().options().set_value(slot.device().tag()+1, val, OPTION_PRIORITY_CMDLINE, error); + assert(error.empty()); } /*------------------------------------------------- @@ -198,6 +200,7 @@ void menu_slot_devices::handle() { if ((uintptr_t)menu_event->itemref == 1 && menu_event->iptkey == IPT_UI_SELECT) { + mame_options::add_slot_options(machine().options()); machine().schedule_hard_reset(); } else if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) diff --git a/src/frontend/mame/ui/sndmenu.cpp b/src/frontend/mame/ui/sndmenu.cpp index f2304b529ea..0a0d1c019ed 100644 --- a/src/frontend/mame/ui/sndmenu.cpp +++ b/src/frontend/mame/ui/sndmenu.cpp @@ -45,19 +45,23 @@ menu_sound_options::menu_sound_options(mame_ui_manager &mui, render_container &c menu_sound_options::~menu_sound_options() { + std::string error_string; emu_options &moptions = machine().options(); if (strcmp(moptions.value(OSDOPTION_SOUND),m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE)!=0) { - moptions.set_value(OSDOPTION_SOUND, m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE, OPTION_PRIORITY_CMDLINE); + moptions.set_value(OSDOPTION_SOUND, m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE, OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(OSDOPTION_SOUND); } if (moptions.int_value(OPTION_SAMPLERATE)!=m_sound_rate[m_cur_rates]) { - moptions.set_value(OPTION_SAMPLERATE, m_sound_rate[m_cur_rates], OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_SAMPLERATE, m_sound_rate[m_cur_rates], OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(OPTION_SAMPLERATE); } if (moptions.bool_value(OPTION_SAMPLES)!=m_samples) { - moptions.set_value(OPTION_SAMPLES, m_samples, OPTION_PRIORITY_CMDLINE); + moptions.set_value(OPTION_SAMPLES, m_samples, OPTION_PRIORITY_CMDLINE, error_string); + machine().options().mark_changed(OPTION_SAMPLES); } } diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index ab18a5a5a95..5a8cb83cd8b 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -240,7 +240,8 @@ void submenu::handle() { case OPTION_BOOLEAN: changed = true; - sm_option.options->set_value(sm_option.name, !strcmp(sm_option.entry->value(),"1") ? "0" : "1", OPTION_PRIORITY_CMDLINE); + sm_option.options->set_value(sm_option.name, !strcmp(sm_option.entry->value(),"1") ? "0" : "1", OPTION_PRIORITY_CMDLINE, error_string); + sm_option.entry->mark_changed(); break; case OPTION_INTEGER: if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) @@ -248,7 +249,8 @@ void submenu::handle() changed = true; int i_cur = atoi(sm_option.entry->value()); (menu_event->iptkey == IPT_UI_LEFT) ? i_cur-- : i_cur++; - sm_option.options->set_value(sm_option.name, i_cur, OPTION_PRIORITY_CMDLINE); + sm_option.options->set_value(sm_option.name, i_cur, OPTION_PRIORITY_CMDLINE, error_string); + sm_option.entry->mark_changed(); } break; case OPTION_FLOAT: @@ -258,19 +260,17 @@ void submenu::handle() f_cur = atof(sm_option.entry->value()); if (sm_option.entry->has_range()) { - const char *minimum = sm_option.entry->minimum(); - const char *maximum = sm_option.entry->maximum(); - f_step = atof(minimum); + f_step = atof(sm_option.entry->minimum()); if (f_step <= 0.0f) { - int pmin = getprecisionchr(minimum); - int pmax = getprecisionchr(maximum); + int pmin = getprecisionchr(sm_option.entry->minimum()); + int pmax = getprecisionchr(sm_option.entry->maximum()); tmptxt = '1' + std::string((pmin > pmax) ? pmin : pmax, '0'); f_step = 1 / atof(tmptxt.c_str()); } } else { - int precision = getprecisionchr(sm_option.entry->default_value().c_str()); + int precision = getprecisionchr(sm_option.entry->default_value()); tmptxt = '1' + std::string(precision, '0'); f_step = 1 / atof(tmptxt.c_str()); } @@ -279,7 +279,8 @@ void submenu::handle() else f_cur += f_step; tmptxt = string_format("%g", f_cur); - sm_option.options->set_value(sm_option.name, tmptxt.c_str(), OPTION_PRIORITY_CMDLINE); + sm_option.options->set_value(sm_option.name, tmptxt.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + sm_option.entry->mark_changed(); } break; case OPTION_STRING: @@ -292,11 +293,10 @@ void submenu::handle() v_cur = sm_option.value[--cur_value]; else v_cur = sm_option.value[++cur_value]; - sm_option.options->set_value(sm_option.name, v_cur.c_str(), OPTION_PRIORITY_CMDLINE); + sm_option.options->set_value(sm_option.name, v_cur.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + sm_option.entry->mark_changed(); } break; - default: - break; } break; default: @@ -391,7 +391,7 @@ void submenu::populate(float &customtop, float &custombottom) break; case OPTION_STRING: { - std::string v_cur(sm_option->entry->value()); + std::string const v_cur(sm_option->entry->value()); int const cur_value = std::distance(sm_option->value.begin(), std::find(sm_option->value.begin(), sm_option->value.end(), v_cur)); arrow_flags = get_arrow_flags(0, int(unsigned(sm_option->value.size() - 1)), cur_value); item_append(_(sm_option->description), diff --git a/src/frontend/mame/ui/submenu.h b/src/frontend/mame/ui/submenu.h index f17b6fe53ff..acaf28d90e4 100644 --- a/src/frontend/mame/ui/submenu.h +++ b/src/frontend/mame/ui/submenu.h @@ -48,7 +48,7 @@ public: option_type type; const char *description; const char *name; - core_options::entry::shared_ptr entry; + core_options::entry *entry; core_options *options; std::vector<std::string> value; }; diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index fc080835893..cf4ea2c9cc5 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -2228,18 +2228,14 @@ void mame_ui_manager::popup_time_string(int seconds, std::string message) void mame_ui_manager::load_ui_options() { // parse the file + std::string error; // attempt to open the output file emu_file file(machine().options().ini_path(), OPEN_FLAG_READ); if (file.open("ui.ini") == osd_file::error::NONE) { - try - { - options().parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, true); - } - catch (options_exception &) - { + bool result = options().parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_DRIVER_INI, error); + if (!result) osd_printf_error("**Error loading ui.ini**"); - } } } @@ -2270,37 +2266,28 @@ void mame_ui_manager::save_main_option() { // parse the file std::string error; - emu_options options(true); // This way we make sure that all OSD parts are in - - options.copy_from(machine().options()); + emu_options options(machine().options()); // This way we make sure that all OSD parts are in + std::string error_string; // attempt to open the main ini file { emu_file file(machine().options().ini_path(), OPEN_FLAG_READ); if (file.open(emulator_info::get_configname(), ".ini") == osd_file::error::NONE) { - try - { - options.parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, true); - } - catch(options_error_exception &ex) + bool result = options.parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_DRIVER_INI, error); + if (!result) { - osd_printf_error("**Error loading %s.ini**: %s\n", emulator_info::get_configname(), ex.message().c_str()); + osd_printf_error("**Error loading %s.ini**", emulator_info::get_configname()); return; } - catch (options_exception &) - { - // ignore other exceptions - } } } - for (const auto &f_entry : machine().options().entries()) + for (emu_options::entry &f_entry : machine().options()) { - const char *value = f_entry->value(); - if (value && strcmp(value, options.value(f_entry->name().c_str()))) + if (f_entry.is_changed()) { - options.set_value(f_entry->name(), *f_entry->value(), OPTION_PRIORITY_CMDLINE); + options.set_value(f_entry.name(), f_entry.value(), OPTION_PRIORITY_CMDLINE, error_string); } } diff --git a/src/lib/util/options.cpp b/src/lib/util/options.cpp index 8889107eba1..8500eda327d 100644 --- a/src/lib/util/options.cpp +++ b/src/lib/util/options.cpp @@ -2,7 +2,7 @@ // copyright-holders:Aaron Giles /*************************************************************************** - options.cpp + options.c Core options code code @@ -44,335 +44,155 @@ const char *const core_options::s_option_unadorned[MAX_UNADORNED_OPTIONS] = }; -//************************************************************************** -// OPTIONS EXCEPTION CLASS -//************************************************************************** - -//------------------------------------------------- -// options_exception - constructor -//------------------------------------------------- - -options_exception::options_exception(std::string &&message) - : m_message(std::move(message)) -{ -} - - -//------------------------------------------------- -// options_warning_exception - constructor -//------------------------------------------------- - -options_warning_exception::options_warning_exception(std::string &&message) - : options_exception(std::move(message)) -{ -} - - -//------------------------------------------------- -// options_error_exception - constructor -//------------------------------------------------- - -options_error_exception::options_error_exception(std::string &&message) - : options_exception(std::move(message)) -{ -} - //************************************************************************** -// CORE OPTIONS ENTRY BASE CLASS +// CORE OPTIONS ENTRY //************************************************************************** //------------------------------------------------- // entry - constructor //------------------------------------------------- -core_options::entry::entry(std::vector<std::string> &&names, core_options::option_type type, const char *description) - : m_names(std::move(names)) - , m_priority(OPTION_PRIORITY_DEFAULT) - , m_type(type) - , m_description(description) -{ - if (type == option_type::HEADER) - assert(m_names.size() == 0); - else - assert(m_names.size() > 0); -} - -core_options::entry::entry(std::string &&name, core_options::option_type type, const char *description) - : entry(std::vector<std::string>({ std::move(name) }), type, description) -{ -} - - -//------------------------------------------------- -// entry - destructor -//------------------------------------------------- - -core_options::entry::~entry() +core_options::entry::entry(const char *name, const char *description, uint32_t flags, const char *defvalue) + : m_next(nullptr), + m_flags(flags), + m_error_reported(false), + m_priority(OPTION_PRIORITY_DEFAULT), + m_description(description), + m_changed(false) { -} - - -//------------------------------------------------- -// entry::value -//------------------------------------------------- - -const char *core_options::entry::value() const -{ - // returning 'nullptr' from here signifies a value entry that is essentially "write only" - // and cannot be meaningfully persisted (e.g. - a command or the software name) - return nullptr; -} - - -//------------------------------------------------- -// entry::set_value -//------------------------------------------------- - -void core_options::entry::set_value(std::string &&newvalue, int priority_value, bool always_override) -{ - // it is invalid to set the value on a header - assert(type() != option_type::HEADER); - - // only set the value if we have priority - if (always_override || priority_value >= priority()) - { - internal_set_value(std::move(newvalue)); - m_priority = priority_value; - - // invoke the value changed handler, if appropriate - if (m_value_changed_handler) - m_value_changed_handler(value()); - } -} - - -//------------------------------------------------- -// entry::set_default_value -//------------------------------------------------- - -void core_options::entry::set_default_value(std::string &&newvalue) -{ - // set_default_value() is not necessarily supported for all entry types - throw false; -} - - -//------------------------------------------------- -// entry::validate -//------------------------------------------------- - -void core_options::entry::validate(const std::string &data) -{ - float fval; - int ival; - int minimum_integer, maximum_integer; - float minimum_float, maximum_float; - - switch (type()) + // copy in the name(s) as appropriate + if (name != nullptr) { - case option_type::BOOLEAN: - // booleans must be 0 or 1 - if (sscanf(data.c_str(), "%d", &ival) != 1 || ival < 0 || ival > 1) - throw options_warning_exception("Illegal boolean value for %s: \"%s\"; reverting to %s\n", name(), data, value()); - break; - - case option_type::INTEGER: - // integers must be integral - if (sscanf(data.c_str(), "%d", &ival) != 1) - throw options_warning_exception("Illegal integer value for %s: \"%s\"; reverting to %s\n", name(), data, value()); - - // range checking - if (has_range()) + // first extract any range + std::string namestr(name); + int lparen = namestr.find_first_of('(',0); + int dash = namestr.find_first_of('-',lparen + 1); + int rparen = namestr.find_first_of(')',dash + 1); + if (lparen != -1 && dash != -1 && rparen != -1) { - minimum_integer = atoi(minimum()); - maximum_integer = atoi(maximum()); - if (ival < minimum_integer || ival > maximum_integer) - throw options_warning_exception("Out-of-range integer value for %s: \"%s\" (must be between %d and %d); reverting to %s\n", name(), data, minimum_integer, maximum_integer, value()); + strtrimspace(m_minimum.assign(namestr.substr(lparen + 1, dash - (lparen + 1)))); + strtrimspace(m_maximum.assign(namestr.substr(dash + 1, rparen - (dash + 1)))); + namestr.erase(lparen, rparen + 1 - lparen); } - break; - - case option_type::FLOAT: - if (sscanf(data.c_str(), "%f", &fval) != 1) - throw options_warning_exception("Illegal float value for %s: \"%s\"; reverting to %s\n", name(), data, value()); - // range checking - if (has_range()) + // then chop up any semicolon-separated names + int semi; + int nameindex = 0; + while ((semi = namestr.find_first_of(';')) != -1 && nameindex < ARRAY_LENGTH(m_name)) { - minimum_float = atof(minimum()); - maximum_float = atof(maximum()); - if (fval < minimum_float || fval > maximum_float) - throw options_warning_exception("Out-of-range float value for %s: \"%s\" (must be between %f and %f); reverting to %s\n", name(), data, minimum_float, maximum_float, value()); + m_name[nameindex++].assign(namestr.substr(0, semi)); + namestr.erase(0, semi + 1); } - break; - case OPTION_STRING: - // strings can be anything - break; - - case OPTION_INVALID: - case OPTION_HEADER: - default: - // anything else is invalid - throw options_error_exception("Attempted to set invalid option %s\n", name()); + // finally add the last item + if (nameindex < ARRAY_LENGTH(m_name)) + m_name[nameindex++] = namestr; } -} - -//------------------------------------------------- -// entry::minimum -//------------------------------------------------- - -const char *core_options::entry::minimum() const -{ - return nullptr; + // set the default value + if (defvalue != nullptr) + m_defdata = defvalue; + m_data = m_defdata; } //------------------------------------------------- -// entry::maximum +// set_value - update our data value //------------------------------------------------- -const char *core_options::entry::maximum() const +void core_options::entry::set_value(const char *newdata, int priority) { - return nullptr; -} - - -//------------------------------------------------- -// entry::has_range -//------------------------------------------------- + // ignore if we don't have priority + if (priority < m_priority) + return; -bool core_options::entry::has_range() const -{ - return minimum() && maximum(); + // set the data and priority, then bump the sequence + m_data = newdata; + m_priority = priority; } //------------------------------------------------- -// entry::default_value +// set_default_value - set the default value of +// an option, and reset the current value to it //------------------------------------------------- -const std::string &core_options::entry::default_value() const +void core_options::entry::set_default_value(const char *defvalue) { - // I don't really want this generally available, but MewUI seems to need it. Please - // do not use - throw false; + m_data = defvalue; + m_defdata = defvalue; + m_priority = OPTION_PRIORITY_DEFAULT; } -//************************************************************************** -// CORE OPTIONS SIMPLE ENTRYCLASS -//************************************************************************** - //------------------------------------------------- -// simple_entry - constructor +// set_description - set the description of +// an option //------------------------------------------------- -core_options::simple_entry::simple_entry(std::vector<std::string> &&names, const char *description, core_options::option_type type, std::string &&defdata, std::string &&minimum, std::string &&maximum) - : entry(std::move(names), type, description) - , m_defdata(std::move(defdata)) - , m_minimum(std::move(minimum)) - , m_maximum(std::move(maximum)) +void core_options::entry::set_description(const char *description) { - m_data = m_defdata; + m_description = description; } -//------------------------------------------------- -// simple_entry - destructor -//------------------------------------------------- - -core_options::simple_entry::~simple_entry() +void core_options::entry::set_flag(uint32_t mask, uint32_t flag) { + m_flags = ( m_flags & mask ) | flag; } //------------------------------------------------- -// simple_entry::value +// revert - revert back to our default if we are +// within the given priority range //------------------------------------------------- -const char *core_options::simple_entry::value() const +void core_options::entry::revert(int priority_hi, int priority_lo) { - const char *result; - switch (type()) + // if our priority is within the range, revert to the default + if (m_priority <= priority_hi && m_priority >= priority_lo) { - case core_options::option_type::BOOLEAN: - case core_options::option_type::INTEGER: - case core_options::option_type::FLOAT: - case core_options::option_type::STRING: - result = m_data.c_str(); - break; - - default: - result = nullptr; - break; + m_data = m_defdata; + m_priority = OPTION_PRIORITY_DEFAULT; } - return result; } -//------------------------------------------------- -// simple_entry::default_value -//------------------------------------------------- - -const std::string &core_options::simple_entry::default_value() const -{ - // only MewUI seems to need this; please don't use - return m_defdata; -} +//************************************************************************** +// CORE OPTIONS +//************************************************************************** //------------------------------------------------- -// internal_set_value +// core_options - constructor //------------------------------------------------- -void core_options::simple_entry::internal_set_value(std::string &&newvalue) +core_options::core_options() { - m_data = std::move(newvalue); } - -//------------------------------------------------- -// set_default_value -//------------------------------------------------- - -void core_options::simple_entry::set_default_value(std::string &&newvalue) +core_options::core_options(const options_entry *entrylist) { - m_defdata = std::move(newvalue); + add_entries(entrylist); } - -//------------------------------------------------- -// minimum -//------------------------------------------------- - -const char *core_options::simple_entry::minimum() const +core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2) { - return m_minimum.c_str(); + add_entries(entrylist1); + add_entries(entrylist2); } - -//------------------------------------------------- -// maximum -//------------------------------------------------- - -const char *core_options::simple_entry::maximum() const +core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2, const options_entry *entrylist3) { - return m_maximum.c_str(); + add_entries(entrylist1); + add_entries(entrylist2); + add_entries(entrylist3); } - -//************************************************************************** -// CORE OPTIONS -//************************************************************************** - -//------------------------------------------------- -// core_options - constructor -//------------------------------------------------- - -core_options::core_options() +core_options::core_options(const core_options &src) { + copyfrom(src); } @@ -386,140 +206,82 @@ core_options::~core_options() //------------------------------------------------- -// add_entry - adds an entry +// operator= - assignment operator //------------------------------------------------- -void core_options::add_entry(entry::shared_ptr &&entry, const char *after_header) +core_options &core_options::operator=(const core_options &rhs) { - // update the entry map - for (const std::string &name : entry->names()) - { - // append the entry - add_to_entry_map(std::string(name), entry); - - // for booleans, add the "-noXYZ" option as well - if (entry->type() == option_type::BOOLEAN) - add_to_entry_map(std::string("no") + name, entry); - } - - // and add the entry to the vector - m_entries.emplace_back(std::move(entry)); + // ignore self-assignment + if (this != &rhs) + copyfrom(rhs); + return *this; } //------------------------------------------------- -// add_to_entry_map - adds an entry to the entry -// map +// operator== - compare two sets of options //------------------------------------------------- -void core_options::add_to_entry_map(std::string &&name, entry::shared_ptr &entry) +bool core_options::operator==(const core_options &rhs) { - // it is illegal to call this method for something that already ex0ists - assert(m_entrymap.find(name) == m_entrymap.end()); + // iterate over options in the first list + for (entry &curentry : m_entrylist) + if (!curentry.is_header()) + { + // if the values differ, return false + if (strcmp(curentry.value(), rhs.value(curentry.name())) != 0) + return false; + } - // append the entry - m_entrymap.emplace(std::make_pair(name, entry::weak_ptr(entry))); + return true; } //------------------------------------------------- -// add_entry - adds an entry based on an -// options_entry +// operator!= - compare two sets of options //------------------------------------------------- -void core_options::add_entry(const options_entry &opt, bool override_existing) +bool core_options::operator!=(const core_options &rhs) { - std::vector<std::string> names; - std::string minimum, maximum; - - // copy in the name(s) as appropriate - if (opt.name) - { - // first extract any range - std::string namestr(opt.name); - int lparen = namestr.find_first_of('(', 0); - int dash = namestr.find_first_of('-', lparen + 1); - int rparen = namestr.find_first_of(')', dash + 1); - if (lparen != -1 && dash != -1 && rparen != -1) - { - strtrimspace(minimum.assign(namestr.substr(lparen + 1, dash - (lparen + 1)))); - strtrimspace(maximum.assign(namestr.substr(dash + 1, rparen - (dash + 1)))); - namestr.erase(lparen, rparen + 1 - lparen); - } + return !operator==(rhs); +} - // then chop up any semicolon-separated names - size_t semi; - while ((semi = namestr.find_first_of(';')) != std::string::npos) - { - names.push_back(namestr.substr(0, semi)); - namestr.erase(0, semi + 1); - } - // finally add the last item - names.push_back(std::move(namestr)); - } +//------------------------------------------------- +// add_entry - add an entry to the current +// options set +//------------------------------------------------- - // we might be called with an existing entry - entry::shared_ptr existing_entry; - do +void core_options::add_entry(const char *name, const char *description, uint32_t flags, const char *defvalue, bool override_existing) +{ + // allocate a new entry + auto newentry = global_alloc(entry(name, description, flags, defvalue)); + if (newentry->name() != nullptr) { - for (const std::string &name : names) - { - existing_entry = get_entry(name.c_str()); - if (existing_entry) - break; - } - - if (existing_entry) + // see if we match an existing entry + auto checkentry = m_entrymap.find(newentry->name()); + if (checkentry != m_entrymap.end()) { + entry *existing = checkentry->second; + // if we're overriding existing entries, then remove the old one if (override_existing) - remove_entry(*existing_entry); + m_entrylist.remove(*existing); + + // otherwise, just override the default and current values and throw out the new entry else + { + existing->set_default_value(newentry->value()); + global_free(newentry); return; + } } - } while (existing_entry); - - // set the default value - std::string defdata = opt.defvalue ? opt.defvalue : ""; - - // create and add the entry - add_entry( - std::move(names), - opt.description, - opt.type, - std::move(defdata), - std::move(minimum), - std::move(maximum)); -} - - -//------------------------------------------------- -// add_entry -//------------------------------------------------- - -void core_options::add_entry(std::vector<std::string> &&names, const char *description, option_type type, std::string &&default_value, std::string &&minimum, std::string &&maximum) -{ - // create the entry - entry::shared_ptr new_entry = std::make_shared<simple_entry>( - std::move(names), - description, - type, - std::move(default_value), - std::move(minimum), - std::move(maximum)); - - // and add it - add_entry(std::move(new_entry)); -} + // need to call value_changed() with initial value + value_changed(newentry->name(), newentry->value()); + } -//------------------------------------------------- -// add_header -//------------------------------------------------- - -void core_options::add_header(const char *description) -{ - add_entry(std::vector<std::string>(), description, option_type::HEADER); + // add us to the list and maps + append_entry(*newentry); } @@ -531,8 +293,8 @@ void core_options::add_header(const char *description) void core_options::add_entries(const options_entry *entrylist, bool override_existing) { // loop over entries until we hit a nullptr name - for (int i = 0; entrylist[i].name || entrylist[i].type == option_type::HEADER; i++) - add_entry(entrylist[i], override_existing); + for ( ; entrylist->name != nullptr || (entrylist->flags & OPTION_HEADER) != 0; entrylist++) + add_entry(*entrylist, override_existing); } @@ -543,8 +305,13 @@ void core_options::add_entries(const options_entry *entrylist, bool override_exi void core_options::set_default_value(const char *name, const char *defvalue) { + // find the entry and bail if we can't + auto curentry = m_entrymap.find(name); + if (curentry == m_entrymap.end()) + return; + // update the data and default data - get_entry(name)->set_default_value(defvalue); + curentry->second->set_default_value(defvalue); } @@ -555,8 +322,13 @@ void core_options::set_default_value(const char *name, const char *defvalue) void core_options::set_description(const char *name, const char *description) { + // find the entry and bail if we can't + auto curentry = m_entrymap.find(name); + if (curentry == m_entrymap.end()) + return; + // update the data and default data - get_entry(name)->set_description(description); + curentry->second->set_description(description); } @@ -565,12 +337,10 @@ void core_options::set_description(const char *name, const char *description) // command line arguments //------------------------------------------------- -void core_options::parse_command_line(std::vector<std::string> &args, int priority) +bool core_options::parse_command_line(std::vector<std::string> &args, int priority, std::string &error_string) { - std::ostringstream error_stream; - condition_type condition = condition_type::NONE; - // reset the errors and the command + error_string.clear(); m_command.clear(); // iterate through arguments @@ -584,8 +354,8 @@ void core_options::parse_command_line(std::vector<std::string> &args, int priori const char *optionname = is_unadorned ? core_options::unadorned(unadorned_index++) : &curarg[1]; // find our entry; if not found, continue - auto curentry = get_entry(optionname); - if (!curentry) + auto curentry = m_entrymap.find(optionname); + if (curentry == m_entrymap.end()) { // we need to relocate this option if (new_argc != arg) @@ -606,19 +376,21 @@ void core_options::parse_command_line(std::vector<std::string> &args, int priori } // process commands first - if (curentry->type() == option_type::COMMAND) + if (curentry->second->type() == OPTION_COMMAND) { // can only have one command if (!m_command.empty()) - throw options_error_exception("Error: multiple commands specified -%s and %s\n", m_command, curarg); - - m_command = curentry->name(); + { + error_string.append(string_format("Error: multiple commands specified -%s and %s\n", m_command, curarg)); + return false; + } + m_command = curentry->second->name(); continue; } // get the data for this argument, special casing booleans std::string newdata; - if (curentry->type() == option_type::BOOLEAN) + if (curentry->second->type() == OPTION_BOOLEAN) { newdata = (strncmp(&curarg[1], "no", 2) == 0) ? "0" : "1"; } @@ -633,18 +405,17 @@ void core_options::parse_command_line(std::vector<std::string> &args, int priori } else { - throw options_error_exception("Error: option %s expected a parameter\n", curarg); + error_string.append(string_format("Error: option %s expected a parameter\n", curarg)); + return false; } args[arg].clear(); // set the new data - prettify_and_set_value(*curentry, std::move(newdata), priority, error_stream, condition); + validate_and_set_data(*curentry->second, std::move(newdata), priority, error_string); } args.resize(new_argc); - - // did we have any errors that may need to be aggregated? - throw_options_exception_if_appropriate(condition, error_stream); + return true; } @@ -653,11 +424,8 @@ void core_options::parse_command_line(std::vector<std::string> &args, int priori // an INI file //------------------------------------------------- -void core_options::parse_ini_file(util::core_file &inifile, int priority, bool always_override) +bool core_options::parse_ini_file(util::core_file &inifile, int priority, int ignore_priority, std::string &error_string) { - std::ostringstream error_stream; - condition_type condition = condition_type::NONE; - // loop over lines in the file char buffer[4096]; while (inifile.gets(buffer, ARRAY_LENGTH(buffer)) != nullptr) @@ -681,8 +449,7 @@ void core_options::parse_ini_file(util::core_file &inifile, int priority, bool a // if we hit the end early, print a warning and continue if (*temp == 0) { - condition = std::max(condition, condition_type::WARN); - util::stream_format(error_stream, "Warning: invalid line in INI: %s", buffer); + error_string.append(string_format("Warning: invalid line in INI: %s", buffer)); continue; } @@ -702,68 +469,77 @@ void core_options::parse_ini_file(util::core_file &inifile, int priority, bool a *temp = 0; // find our entry - entry::shared_ptr curentry = get_entry(optionname); - if (!curentry) + auto curentry = m_entrymap.find(optionname); + if (curentry == m_entrymap.end()) { - condition = std::max(condition, condition_type::WARN); - util::stream_format(error_stream, "Warning: unknown option in INI: %s\n", optionname); + if (priority >= ignore_priority) + error_string.append(string_format("Warning: unknown option in INI: %s\n", optionname)); continue; } // set the new data - prettify_and_set_value(*curentry, optiondata, priority, error_stream, condition); + validate_and_set_data(*curentry->second, optiondata, priority, error_string); } - - // did we have any errors that may need to be aggregated? - throw_options_exception_if_appropriate(condition, error_stream); + return true; } //------------------------------------------------- -// throw_options_exception_if_appropriate +// pluck_from_command_line - finds a specific +// value from within a command line //------------------------------------------------- -void core_options::throw_options_exception_if_appropriate(core_options::condition_type condition, std::ostringstream &error_stream) +bool core_options::pluck_from_command_line(std::vector<std::string> &args, const std::string &optionname, std::string &result) { - switch(condition) - { - case condition_type::NONE: - // do nothing - break; + // find this entry within the options (it is illegal to call this with a non-existant option + // so we assert if not present) + auto curentry = m_entrymap.find(optionname); + assert(curentry != m_entrymap.end()); - case condition_type::WARN: - throw options_warning_exception(error_stream.str()); + // build a vector with potential targets + std::vector<std::string> targets; + const char *potential_target; + int index = 0; + while ((potential_target = curentry->second->name(index++)) != nullptr) + { + // not supporting unadorned options for now + targets.push_back(std::string("-") + potential_target); + } - case condition_type::ERR: - throw options_warning_exception(error_stream.str()); + // find each of the targets in the argv array + for (int i = 1; i < args.size() - 1; i++) + { + auto const iter = std::find_if( + targets.begin(), + targets.end(), + [&args, i](const std::string &targ) { return targ == args[i]; }); + if (iter != targets.end()) + { + // get the result + result = std::move(args[i + 1]); - default: - // should not get here - throw false; + // remove this arguments from the list + auto const pos = std::next(args.begin(), i); + args.erase(pos, std::next(pos, 2)); + return true; + } } + + result.clear(); + return false; } //------------------------------------------------- -// copy_from +// revert - revert options at or below a certain +// priority back to their defaults //------------------------------------------------- -void core_options::copy_from(const core_options &that) +void core_options::revert(int priority_hi, int priority_lo) { - for (auto &dest_entry : m_entries) - { - if (dest_entry->names().size() > 0) - { - // identify the source entry - const entry::shared_ptr source_entry = that.get_entry(dest_entry->name()); - if (source_entry) - { - const char *value = source_entry->value(); - if (value) - dest_entry->set_value(value, source_entry->priority(), true); - } - } - } + // iterate over options and revert to defaults if below the given priority + for (entry &curentry : m_entrylist) + curentry.revert(priority_hi, priority_lo); } @@ -784,34 +560,47 @@ std::string core_options::output_ini(const core_options *diff) const std::string overridden_value; // loop over all items - for (auto &curentry : m_entries) + for (entry &curentry : m_entrylist) { - if (curentry->type() == option_type::HEADER) + const char *name = curentry.name(); + const char *value; + switch (override_get_value(name, overridden_value)) { - // header: record description - last_header = curentry->description(); + case override_get_value_result::NONE: + default: + value = curentry.value(); + break; + + case override_get_value_result::SKIP: + continue; + + case override_get_value_result::OVERRIDE: + value = overridden_value.c_str(); + break; } - else + bool is_unadorned = false; + + // check if it's unadorned + if (name && strlen(name) && !strcmp(name, core_options::unadorned(unadorned_index))) { - const std::string &name(curentry->name()); - const char *value(curentry->value()); + unadorned_index++; + is_unadorned = true; + } - // check if it's unadorned - bool is_unadorned = false; - if (name == core_options::unadorned(unadorned_index)) - { - unadorned_index++; - is_unadorned = true; - } + // header: record description + if (curentry.is_header()) + last_header = curentry.description(); - // output entries for all non-command items (items with value) - if (value) + // otherwise, output entries for all non-command items + else if (!curentry.is_command()) + { + if (!curentry.is_internal()) { // look up counterpart in diff, if diff is specified - if (!diff || strcmp(value, diff->value(name.c_str()))) + if (diff == nullptr || strcmp(value, diff->value(name)) != 0) { // output header, if we have one - if (last_header) + if (last_header != nullptr) { if (num_valid_headers++) buffer << '\n'; @@ -822,7 +611,7 @@ std::string core_options::output_ini(const core_options *diff) const // and finally output the data, skip if unadorned if (!is_unadorned) { - if (strchr(value, ' ')) + if (strchr(value, ' ') != nullptr) util::stream_format(buffer, "%-25s \"%s\"\n", name, value); else util::stream_format(buffer, "%-25s %s\n", name, value); @@ -845,15 +634,15 @@ std::string core_options::output_help() const std::ostringstream buffer; // loop over all items - for (auto &curentry : m_entries) + for (entry &curentry : m_entrylist) { // header: just print - if (curentry->type() == option_type::HEADER) - util::stream_format(buffer, "\n#\n# %s\n#\n", curentry->description()); + if (curentry.is_header()) + util::stream_format(buffer, "\n#\n# %s\n#\n", curentry.description()); // otherwise, output entries for all non-deprecated items - else if (curentry->description() != nullptr) - util::stream_format(buffer, "-%-20s%s\n", curentry->name(), curentry->description()); + else if (curentry.description() != nullptr) + util::stream_format(buffer, "-%-20s%s\n", curentry.name(), curentry.description()); } return buffer.str(); } @@ -863,9 +652,10 @@ std::string core_options::output_help() const // value - return the raw option value //------------------------------------------------- -const char *core_options::value(const char *option) const +const char *core_options::value(const char *name) const { - return get_entry(option)->value(); + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second->value() : ""; } @@ -873,143 +663,251 @@ const char *core_options::value(const char *option) const // description - return description of option //------------------------------------------------- -const char *core_options::description(const char *option) const +const char *core_options::description(const char *name) const { - return get_entry(option)->description(); + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second->description() : ""; } -//************************************************************************** -// LEGACY -//************************************************************************** - //------------------------------------------------- -// set_value - set the raw option value +// priority - return the priority of option //------------------------------------------------- -void core_options::set_value(const std::string &name, const std::string &value, int priority) +int core_options::priority(const char *name) const { - set_value(name, std::string(value), priority); + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second->priority() : 0; } -void core_options::set_value(const std::string &name, std::string &&value, int priority) -{ - get_entry(name)->set_value(std::move(value), priority); -} -void core_options::set_value(const std::string &name, int value, int priority) -{ - set_value(name, string_format("%d", value), priority); -} +//------------------------------------------------- +// exists - return if option exists in list +//------------------------------------------------- -void core_options::set_value(const std::string &name, float value, int priority) +bool core_options::exists(const char *name) const { - set_value(name, string_format("%f", value), priority); + return (m_entrymap.find(name) != m_entrymap.end()); } //------------------------------------------------- -// remove_entry - remove an entry from our list -// and map +// set_value - set the raw option value //------------------------------------------------- -void core_options::remove_entry(core_options::entry &delentry) +bool core_options::set_value(const char *name, const char *value, int priority, std::string &error_string) { - // find this in m_entries - auto iter = std::find_if( - m_entries.begin(), - m_entries.end(), - [&delentry](const auto &x) { return &*x == &delentry; }); - assert(iter != m_entries.end()); + // find the entry first + auto curentry = m_entrymap.find(name); + if (curentry == m_entrymap.end()) + { + error_string.append(string_format("Attempted to set unknown option %s\n", name)); + return false; + } - // erase each of the items out of the entry map - for (const std::string &name : delentry.names()) - m_entrymap.erase(name); + // validate and set the item normally + return validate_and_set_data(*curentry->second, value, priority, error_string); +} - // finally erase it - m_entries.erase(iter); +bool core_options::set_value(const char *name, int value, int priority, std::string &error_string) +{ + return set_value(name, string_format("%d", value).c_str(), priority, error_string); } +bool core_options::set_value(const char *name, float value, int priority, std::string &error_string) +{ + return set_value(name, string_format("%f", value).c_str(), priority, error_string); +} -//------------------------------------------------- -// prettify_and_set_value -//------------------------------------------------- -void core_options::prettify_and_set_value(entry &curentry, std::string &&data, int priority, std::ostream &error_stream, condition_type &condition) +void core_options::set_flag(const char *name, uint32_t mask, uint32_t flag) { - // trim any whitespace - strtrimspace(data); - - // trim quotes - if (data.find_first_of('"') == 0 && data.find_last_of('"') == data.length() - 1) + // find the entry first + auto curentry = m_entrymap.find(name); + if ( curentry == m_entrymap.end()) { - data.erase(0, 1); - data.erase(data.length() - 1, 1); + return; } + curentry->second->set_flag(mask, flag); +} - // this is called when parsing a command line or an INI - we want to catch the option_exception and write - // any exception messages to the error stream - try - { - curentry.set_value(std::move(data), priority); - } - catch (options_warning_exception &ex) - { - // we want to aggregate option exceptions - error_stream << ex.message(); - condition = std::max(condition, condition_type::WARN); - } - catch (options_error_exception &ex) +void core_options::mark_changed(const char* name) +{ + // find the entry first + auto curentry = m_entrymap.find(name); + if (curentry == m_entrymap.end()) { - // we want to aggregate option exceptions - error_stream << ex.message(); - condition = std::max(condition, condition_type::ERR); + return; } + curentry->second->mark_changed(); } - //------------------------------------------------- -// get_entry +// reset - reset the options state, removing +// everything //------------------------------------------------- -const core_options::entry::shared_ptr core_options::get_entry(const std::string &name) const +void core_options::reset() { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; + m_entrylist.reset(); + m_entrymap.clear(); } -core_options::entry::shared_ptr core_options::get_entry(const std::string &name) + +//------------------------------------------------- +// append_entry - append an entry to our list +// and index it in the map +//------------------------------------------------- + +void core_options::append_entry(core_options::entry &newentry) { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; + m_entrylist.append(newentry); + + // if we have names, add them to the map + for (int name = 0; name < ARRAY_LENGTH(newentry.m_name); name++) + if (newentry.name(name) != nullptr) + { + m_entrymap.insert(std::make_pair(newentry.name(name), &newentry)); + // for boolean options add a "no" variant as well + if (newentry.type() == OPTION_BOOLEAN) + m_entrymap.insert(std::make_pair(std::string("no").append(newentry.name(name)), &newentry)); + } } //------------------------------------------------- -// set_value_changed_handler +// remove_entry - remove an entry from our list +// and map //------------------------------------------------- -void core_options::set_value_changed_handler(const std::string &name, std::function<void(const char *)> &&handler) +void core_options::remove_entry(core_options::entry &delentry) { - get_entry(name)->set_value_changed_handler(std::move(handler)); + // remove all names from the map + for (int name = 0; name < ARRAY_LENGTH(delentry.m_name); name++) + if (!delentry.m_name[name].empty()) + { + auto entry = m_entrymap.find(delentry.m_name[name]); + if (entry!= m_entrymap.end()) m_entrymap.erase(entry); + } + + // remove the entry from the list + m_entrylist.remove(delentry); } +/** + * @fn void core_options::copyfrom(const core_options &src) + * + * @brief ------------------------------------------------- + * copyfrom - copy options from another set + * -------------------------------------------------. + * + * @param src Source for the. + */ -//------------------------------------------------- -// header_exists -//------------------------------------------------- +void core_options::copyfrom(const core_options &src) +{ + // reset ourselves first + reset(); + + // iterate through the src options and make our own + for (entry &curentry : src.m_entrylist) + append_entry(*global_alloc(entry(curentry.name(), curentry.description(), curentry.flags(), curentry.default_value()))); +} -bool core_options::header_exists(const char *description) const +/** + * @fn bool core_options::validate_and_set_data(core_options::entry &curentry, const char *newdata, int priority, std::string &error_string) + * + * @brief ------------------------------------------------- + * validate_and_set_data - make sure the data is of the appropriate type and within + * range, then set it + * -------------------------------------------------. + * + * @param [in,out] curentry The curentry. + * @param newdata The newdata. + * @param priority The priority. + * @param [in,out] error_string The error string. + * + * @return true if it succeeds, false if it fails. + */ + +bool core_options::validate_and_set_data(core_options::entry &curentry, std::string &&data, int priority, std::string &error_string) { - auto iter = std::find_if( - m_entries.begin(), - m_entries.end(), - [description](const auto &entry) + // trim any whitespace + strtrimspace(data); + + // trim quotes + if (data.find_first_of('"') == 0 && data.find_last_of('"') == data.length() - 1) + { + data.erase(0, 1); + data.erase(data.length() - 1, 1); + } + + // let derived classes override how we set this data + if (override_set_value(curentry.name(), data)) + return true; + + // validate the type of data and optionally the range + float fval; + int ival; + switch (curentry.type()) + { + // booleans must be 0 or 1 + case OPTION_BOOLEAN: + if (sscanf(data.c_str(), "%d", &ival) != 1 || ival < 0 || ival > 1) + { + error_string.append(string_format("Illegal boolean value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); + return false; + } + break; + + // integers must be integral + case OPTION_INTEGER: + if (sscanf(data.c_str(), "%d", &ival) != 1) + { + error_string.append(string_format("Illegal integer value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); + return false; + } + if (curentry.has_range() && (ival < atoi(curentry.minimum()) || ival > atoi(curentry.maximum()))) { - return entry->type() == option_type::HEADER - && entry->description() - && !strcmp(entry->description(), description); - }); + error_string.append(string_format("Out-of-range integer value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.c_str(), curentry.minimum(), curentry.maximum(), curentry.value())); + return false; + } + break; - return iter != m_entries.end(); + // floating-point values must be numeric + case OPTION_FLOAT: + if (sscanf(data.c_str(), "%f", &fval) != 1) + { + error_string.append(string_format("Illegal float value for %s: \"%s\"; reverting to %s\n", curentry.name(), data.c_str(), curentry.value())); + return false; + } + if (curentry.has_range() && ((double) fval < atof(curentry.minimum()) || (double) fval > atof(curentry.maximum()))) + { + error_string.append(string_format("Out-of-range float value for %s: \"%s\" (must be between %s and %s); reverting to %s\n", curentry.name(), data.c_str(), curentry.minimum(), curentry.maximum(), curentry.value())); + return false; + } + break; + + // strings can be anything + case OPTION_STRING: + break; + + // anything else is invalid + case OPTION_INVALID: + case OPTION_HEADER: + default: + error_string.append(string_format("Attempted to set invalid option %s\n", curentry.name())); + return false; + } + + // set the data + curentry.set_value(data.c_str(), priority); + value_changed(curentry.name(), data); + return true; +} + +core_options::entry *core_options::get_entry(const char *name) const +{ + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second : nullptr; } diff --git a/src/lib/util/options.h b/src/lib/util/options.h index a36ff95cab6..edf7f8600c8 100644 --- a/src/lib/util/options.h +++ b/src/lib/util/options.h @@ -13,13 +13,26 @@ #include "corefile.h" #include <unordered_map> -#include <sstream> + //************************************************************************** // CONSTANTS //************************************************************************** +// option types +const uint32_t OPTION_TYPE_MASK = 0x0007; // up to 8 different types +enum +{ + OPTION_INVALID, // invalid + OPTION_HEADER, // a header item + OPTION_COMMAND, // a command + OPTION_BOOLEAN, // boolean option + OPTION_INTEGER, // integer option + OPTION_FLOAT, // floating-point option + OPTION_STRING // string option +}; + // option priorities const int OPTION_PRIORITY_DEFAULT = 0; // defaults are at 0 priority const int OPTION_PRIORITY_LOW = 50; // low priority @@ -27,55 +40,20 @@ const int OPTION_PRIORITY_NORMAL = 100; // normal priority const int OPTION_PRIORITY_HIGH = 150; // high priority const int OPTION_PRIORITY_MAXIMUM = 255; // maximum priority +const uint32_t OPTION_FLAG_INTERNAL = 0x40000000; + //************************************************************************** // TYPE DEFINITIONS //************************************************************************** -struct options_entry; - -// exception thrown by core_options when an illegal request is made -class options_exception : public std::exception -{ -public: - const std::string &message() const { return m_message; } - virtual const char *what() const noexcept override { return message().c_str(); } - -protected: - options_exception(std::string &&message); - -private: - std::string m_message; -}; - -class options_warning_exception : public options_exception -{ -public: - template <typename... Params> options_warning_exception(const char *fmt, Params &&...args) - : options_warning_exception(util::string_format(fmt, std::forward<Params>(args)...)) - { - } - - options_warning_exception(std::string &&message); - options_warning_exception(const options_warning_exception &) = default; - options_warning_exception(options_warning_exception &&) = default; - options_warning_exception& operator=(const options_warning_exception &) = default; - options_warning_exception& operator=(options_warning_exception &&) = default; -}; - -class options_error_exception : public options_exception +// static structure describing a single option with its description and default value +struct options_entry { -public: - template <typename... Params> options_error_exception(const char *fmt, Params &&...args) - : options_error_exception(util::string_format(fmt, std::forward<Params>(args)...)) - { - } - - options_error_exception(std::string &&message); - options_error_exception(const options_error_exception &) = default; - options_error_exception(options_error_exception &&) = default; - options_error_exception& operator=(const options_error_exception &) = default; - options_error_exception& operator=(options_error_exception &&) = default; + const char * name; // name on the command line + const char * defvalue; // default value of this argument + uint32_t flags; // flags to describe the option + const char * description; // description for -showusage }; @@ -85,95 +63,94 @@ class core_options static const int MAX_UNADORNED_OPTIONS = 16; public: - enum class option_type - { - INVALID, // invalid - HEADER, // a header item - COMMAND, // a command - BOOLEAN, // boolean option - INTEGER, // integer option - FLOAT, // floating-point option - STRING // string option - }; - // information about a single entry in the options class entry { - public: - typedef std::shared_ptr<entry> shared_ptr; - typedef std::weak_ptr<entry> weak_ptr; - - // constructor/destructor - entry(std::vector<std::string> &&names, option_type type = option_type::STRING, const char *description = nullptr); - entry(std::string &&name, option_type type = option_type::STRING, const char *description = nullptr); - entry(const entry &) = delete; - entry(entry &&) = delete; - entry& operator=(const entry &) = delete; - entry& operator=(entry &&) = delete; - virtual ~entry(); - - // accessors - const std::vector<std::string> &names() const { return m_names; } - const std::string &name() const { return m_names[0]; } - virtual const char *value() const; - int priority() const { return m_priority; } - option_type type() const { return m_type; } - const char *description() const { return m_description; } - virtual const std::string &default_value() const; - virtual const char *minimum() const; - virtual const char *maximum() const; - bool has_range() const; + friend class core_options; + friend class simple_list<entry>; - // mutators - void set_value(std::string &&newvalue, int priority, bool always_override = false); - virtual void set_default_value(std::string &&newvalue); - void set_description(const char *description) { m_description = description; } - void set_value_changed_handler(std::function<void(const char *)> &&handler) { m_value_changed_handler = std::move(handler); } + // construction/destruction + entry(const char *name, const char *description, uint32_t flags = 0, const char *defvalue = nullptr); - protected: - virtual void internal_set_value(std::string &&newvalue) = 0; + public: + // getters + entry *next() const { return m_next; } + const char *name(int index = 0) const { return (index < ARRAY_LENGTH(m_name) && !m_name[index].empty()) ? m_name[index].c_str() : nullptr; } + const char *description() const { return m_description; } + const char *value() const { return m_data.c_str(); } + const char *default_value() const { return m_defdata.c_str(); } + const char *minimum() const { return m_minimum.c_str(); } + const char *maximum() const { return m_maximum.c_str(); } + int type() const { return (m_flags & OPTION_TYPE_MASK); } + uint32_t flags() const { return m_flags; } + bool is_header() const { return type() == OPTION_HEADER; } + bool is_command() const { return type() == OPTION_COMMAND; } + bool is_internal() const { return m_flags & OPTION_FLAG_INTERNAL; } + bool has_range() const { return (!m_minimum.empty() && !m_maximum.empty()); } + int priority() const { return m_priority; } + bool is_changed() const { return m_changed; } + + // setters + void set_value(const char *newvalue, int priority); + void set_default_value(const char *defvalue); + void set_description(const char *description); + void set_flag(uint32_t mask, uint32_t flag); + void mark_changed() { m_changed = true; } + void revert(int priority_hi, int priority_lo); private: - void validate(const std::string &value); - - std::vector<std::string> m_names; - int m_priority; - core_options::option_type m_type; - const char * m_description; - std::function<void(const char *)> m_value_changed_handler; + // internal state + entry * m_next; // link to the next data + uint32_t m_flags; // flags from the entry + bool m_error_reported; // have we reported an error on this option yet? + int m_priority; // priority of the data set + const char * m_description; // description for this item + std::string m_name[4]; // up to 4 names for the item + std::string m_data; // data for this item + std::string m_defdata; // default data for this item + std::string m_minimum; // minimum value + std::string m_maximum; // maximum value + bool m_changed; // changed flag }; // construction/destruction core_options(); - core_options(const core_options &) = delete; - core_options(core_options &&) = delete; - core_options& operator=(const core_options &) = delete; - core_options& operator=(core_options &&) = delete; + core_options(const options_entry *entrylist); + core_options(const options_entry *entrylist1, const options_entry *entrylist2); + core_options(const options_entry *entrylist1, const options_entry *entrylist2, const options_entry *entrylist3); + core_options(const core_options &src); virtual ~core_options(); + // operators + core_options &operator=(const core_options &rhs); + bool operator==(const core_options &rhs); + bool operator!=(const core_options &rhs); + // getters + entry *first() const { return m_entrylist.first(); } const std::string &command() const { return m_command; } - const entry::shared_ptr get_entry(const std::string &name) const; - entry::shared_ptr get_entry(const std::string &name); - const std::vector<entry::shared_ptr> &entries() const { return m_entries; } - bool exists(const std::string &name) const { return get_entry(name) != nullptr; } - bool header_exists(const char *description) const; + entry *get_entry(const char *name) const; + + // range iterators + using auto_iterator = simple_list<entry>::auto_iterator; + auto_iterator begin() const { return m_entrylist.begin(); } + auto_iterator end() const { return m_entrylist.end(); } // configuration - void add_entry(entry::shared_ptr &&entry, const char *after_header = nullptr); - void add_entry(const options_entry &entry, bool override_existing = false); - void add_entry(std::vector<std::string> &&names, const char *description, option_type type, std::string &&default_value = "", std::string &&minimum = "", std::string &&maximum = ""); - void add_header(const char *description); + void add_entry(const char *name, const char *description, uint32_t flags = 0, const char *defvalue = nullptr, bool override_existing = false); + void add_entry(const options_entry &data, bool override_existing = false) { add_entry(data.name, data.description, data.flags, data.defvalue, override_existing); } void add_entries(const options_entry *entrylist, bool override_existing = false); void set_default_value(const char *name, const char *defvalue); void set_description(const char *name, const char *description); void remove_entry(entry &delentry); - void set_value_changed_handler(const std::string &name, std::function<void(const char *)> &&handler); // parsing/input - void parse_command_line(std::vector<std::string> &args, int priority); - void parse_ini_file(util::core_file &inifile, int priority, bool always_override); - void copy_from(const core_options &that); + bool parse_command_line(std::vector<std::string> &args, int priority, std::string &error_string); + bool parse_ini_file(util::core_file &inifile, int priority, int ignore_priority, std::string &error_string); + bool pluck_from_command_line(std::vector<std::string> &args, const std::string &name, std::string &result); + + // reverting + void revert(int priority_hi = OPTION_PRIORITY_MAXIMUM, int priority_lo = OPTION_PRIORITY_DEFAULT); // output std::string output_ini(const core_options *diff = nullptr) const; @@ -182,88 +159,51 @@ public: // reading const char *value(const char *option) const; const char *description(const char *option) const; - bool bool_value(const char *option) const { return int_value(option) != 0; } - int int_value(const char *option) const { return atoi(value(option)); } - float float_value(const char *option) const { return atof(value(option)); } + int priority(const char *option) const; + bool bool_value(const char *name) const { return (atoi(value(name)) != 0); } + int int_value(const char *name) const { return atoi(value(name)); } + float float_value(const char *name) const { return atof(value(name)); } + bool exists(const char *name) const; // setting - void set_value(const std::string &name, const std::string &value, int priority); - void set_value(const std::string &name, std::string &&value, int priority); - void set_value(const std::string &name, int value, int priority); - void set_value(const std::string &name, float value, int priority); + bool set_value(const char *name, const char *value, int priority, std::string &error_string); + bool set_value(const char *name, int value, int priority, std::string &error_string); + bool set_value(const char *name, float value, int priority, std::string &error_string); + void set_flag(const char *name, uint32_t mask, uint32_t flags); + void mark_changed(const char *name); // misc static const char *unadorned(int x = 0) { return s_option_unadorned[std::min(x, MAX_UNADORNED_OPTIONS - 1)]; } + int options_count() const { return m_entrylist.count(); } -private: - class simple_entry : public entry - { - public: - // construction/destruction - simple_entry(std::vector<std::string> &&names, const char *description, core_options::option_type type, std::string &&defdata, std::string &&minimum, std::string &&maximum); - simple_entry(const simple_entry &) = delete; - simple_entry(simple_entry &&) = delete; - simple_entry& operator=(const simple_entry &) = delete; - simple_entry& operator=(simple_entry &&) = delete; - ~simple_entry(); - - // getters - virtual const char *value() const override; - virtual const char *minimum() const override; - virtual const char *maximum() const override; - virtual const std::string &default_value() const override; - - virtual void set_default_value(std::string &&newvalue) override; - - protected: - virtual void internal_set_value(std::string &&newvalue) override; - - private: - // internal state - std::string m_data; // data for this item - std::string m_defdata; // default data for this item - std::string m_minimum; // minimum value - std::string m_maximum; // maximum value - }; - - // used internally in core_options - enum class condition_type +protected: + // This is a hook to allow option value retrieval to be overridden for various reasons; this is a crude + // extensibility mechanism that should really be replaced by something better + enum class override_get_value_result { NONE, - WARN, - ERR + OVERRIDE, + SKIP }; + virtual void value_changed(const std::string &name, const std::string &value) {} + virtual override_get_value_result override_get_value(const char *name, std::string &value) const { return override_get_value_result::NONE; } + virtual bool override_set_value(const char *name, const std::string &value) { return false; } + +private: // internal helpers - void add_to_entry_map(std::string &&name, entry::shared_ptr &entry); - void prettify_and_set_value(entry &curentry, std::string &&data, int priority, std::ostream &error_stream, condition_type &condition); - void throw_options_exception_if_appropriate(condition_type condition, std::ostringstream &error_stream); + void reset(); + void append_entry(entry &newentry); + void copyfrom(const core_options &src); + bool validate_and_set_data(entry &curentry, std::string &&newdata, int priority, std::string &error_string); // internal state - std::vector<entry::shared_ptr> m_entries; // cannonical list of entries - std::unordered_map<std::string, entry::weak_ptr> m_entrymap; // map for fast lookup - std::string m_command; // command found - static const char *const s_option_unadorned[]; // array of unadorned option "names" -}; - - -// static structure describing a single option with its description and default value -struct options_entry -{ - const char * name; // name on the command line - const char * defvalue; // default value of this argument - core_options::option_type type; // type of option - const char * description; // description for -showusage + simple_list<entry> m_entrylist; // head of list of entries + std::unordered_map<std::string,entry *> m_entrymap; // map for fast lookup + std::string m_command; // command found + static const char *const s_option_unadorned[]; // array of unadorned option "names" }; -// legacy option types -const core_options::option_type OPTION_INVALID = core_options::option_type::INVALID; -const core_options::option_type OPTION_HEADER = core_options::option_type::HEADER; -const core_options::option_type OPTION_COMMAND = core_options::option_type::COMMAND; -const core_options::option_type OPTION_BOOLEAN = core_options::option_type::BOOLEAN; -const core_options::option_type OPTION_INTEGER = core_options::option_type::INTEGER; -const core_options::option_type OPTION_FLOAT = core_options::option_type::FLOAT; -const core_options::option_type OPTION_STRING = core_options::option_type::STRING; #endif // MAME_LIB_UTIL_OPTIONS_H diff --git a/src/osd/modules/lib/osdobj_common.h b/src/osd/modules/lib/osdobj_common.h index 2c2f750fdbe..df8096840cf 100644 --- a/src/osd/modules/lib/osdobj_common.h +++ b/src/osd/modules/lib/osdobj_common.h @@ -270,7 +270,7 @@ private: // FIXME: should be elsewhere osd_module *select_module_options(const core_options &opts, const std::string &opt_name) { - std::string opt_val = opts.exists(opt_name) ? opts.value(opt_name.c_str()) : ""; + std::string opt_val = opts.value(opt_name.c_str()); if (opt_val.compare("auto")==0) opt_val = ""; else if (!m_mod_man.type_has_name(opt_name.c_str(), opt_val.c_str())) diff --git a/src/osd/modules/render/drawbgfx.cpp b/src/osd/modules/render/drawbgfx.cpp index 31ddc58c145..2fb0d0b3a1e 100644 --- a/src/osd/modules/render/drawbgfx.cpp +++ b/src/osd/modules/render/drawbgfx.cpp @@ -85,6 +85,7 @@ renderer_bgfx::renderer_bgfx(std::shared_ptr<osd_window> w) , m_avi_writer(nullptr) , m_avi_target(nullptr) { + m_options = downcast<osd_options &>(assert_window()->machine().options()); } //============================================================ diff --git a/src/osd/sdl/sdlmain.cpp b/src/osd/sdl/sdlmain.cpp index 10e57ec5766..31f65e2ba66 100644 --- a/src/osd/sdl/sdlmain.cpp +++ b/src/osd/sdl/sdlmain.cpp @@ -408,12 +408,14 @@ void sdl_osd_interface::init(running_machine &machine) // determine if we are benchmarking, and adjust options appropriately int bench = options().bench(); + std::string error_string; if (bench > 0) { - options().set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM); - options().set_value(OSDOPTION_SOUND, "none", OPTION_PRIORITY_MAXIMUM); - options().set_value(OSDOPTION_VIDEO, "none", OPTION_PRIORITY_MAXIMUM); - options().set_value(OPTION_SECONDS_TO_RUN, bench, OPTION_PRIORITY_MAXIMUM); + options().set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM, error_string); + options().set_value(OSDOPTION_SOUND, "none", OPTION_PRIORITY_MAXIMUM, error_string); + options().set_value(OSDOPTION_VIDEO, "none", OPTION_PRIORITY_MAXIMUM, error_string); + options().set_value(OPTION_SECONDS_TO_RUN, bench, OPTION_PRIORITY_MAXIMUM, error_string); + assert(error_string.c_str()[0] == 0); } // Some driver options - must be before audio init! diff --git a/src/osd/windows/winmain.cpp b/src/osd/windows/winmain.cpp index 805a769d1b8..efec6700849 100644 --- a/src/osd/windows/winmain.cpp +++ b/src/osd/windows/winmain.cpp @@ -515,20 +515,23 @@ void windows_osd_interface::init(running_machine &machine) // determine if we are benchmarking, and adjust options appropriately int bench = options.bench(); + std::string error_string; if (bench > 0) { - options.set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM); - options.set_value(OSDOPTION_SOUND, "none", OPTION_PRIORITY_MAXIMUM); - options.set_value(OSDOPTION_VIDEO, "none", OPTION_PRIORITY_MAXIMUM); - options.set_value(OPTION_SECONDS_TO_RUN, bench, OPTION_PRIORITY_MAXIMUM); + options.set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM, error_string); + options.set_value(OSDOPTION_SOUND, "none", OPTION_PRIORITY_MAXIMUM, error_string); + options.set_value(OSDOPTION_VIDEO, "none", OPTION_PRIORITY_MAXIMUM, error_string); + options.set_value(OPTION_SECONDS_TO_RUN, bench, OPTION_PRIORITY_MAXIMUM, error_string); + assert(error_string.empty()); } // determine if we are profiling, and adjust options appropriately int profile = options.profile(); if (profile > 0) { - options.set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM); - options.set_value(OSDOPTION_NUMPROCESSORS, 1, OPTION_PRIORITY_MAXIMUM); + options.set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM, error_string); + options.set_value(OSDOPTION_NUMPROCESSORS, 1, OPTION_PRIORITY_MAXIMUM, error_string); + assert(error_string.empty()); } // thread priority |
