From b193e05cd7c8456a2648d43854645da84f56ddbd Mon Sep 17 00:00:00 2001 From: npwoods Date: Sat, 24 Jun 2017 22:48:56 -0400 Subject: Overhaul to how MAME handles options, take two (#2341) --- src/emu/diimage.cpp | 11 +- src/emu/diimage.h | 6 +- src/emu/emuopts.cpp | 982 +++++++++++++++++++++++++++++++++--- src/emu/emuopts.h | 160 ++++-- src/emu/image.cpp | 45 +- src/emu/mconfig.cpp | 11 +- src/emu/natkeyboard.cpp | 4 +- src/emu/romload.cpp | 18 +- src/frontend/mame/clifront.cpp | 96 ++-- src/frontend/mame/clifront.h | 6 +- src/frontend/mame/info.cpp | 2 - src/frontend/mame/luaengine.cpp | 23 +- src/frontend/mame/mame.cpp | 45 +- src/frontend/mame/mameopts.cpp | 685 +++---------------------- src/frontend/mame/mameopts.h | 36 +- src/frontend/mame/pluginopts.cpp | 2 +- src/frontend/mame/ui/custui.cpp | 38 +- src/frontend/mame/ui/dirmenu.cpp | 15 +- src/frontend/mame/ui/info.cpp | 3 +- src/frontend/mame/ui/miscmenu.cpp | 28 +- src/frontend/mame/ui/selgame.cpp | 14 +- src/frontend/mame/ui/selsoft.cpp | 26 +- src/frontend/mame/ui/slotopt.cpp | 93 +++- src/frontend/mame/ui/slotopt.h | 7 + src/frontend/mame/ui/sndmenu.cpp | 10 +- src/frontend/mame/ui/submenu.cpp | 26 +- src/frontend/mame/ui/submenu.h | 2 +- src/frontend/mame/ui/ui.cpp | 33 +- src/lib/util/options.cpp | 980 +++++++++++++++++++---------------- src/lib/util/options.h | 298 ++++++----- src/osd/modules/lib/osdobj_common.h | 2 +- src/osd/modules/render/drawbgfx.cpp | 1 - src/osd/sdl/sdlmain.cpp | 10 +- src/osd/windows/winmain.cpp | 15 +- 34 files changed, 2122 insertions(+), 1611 deletions(-) diff --git a/src/emu/diimage.cpp b/src/emu/diimage.cpp index 2ef638d461a..fc321185219 100644 --- a/src/emu/diimage.cpp +++ b/src/emu/diimage.cpp @@ -1217,7 +1217,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_options()[instance_name()] = path; + device().machine().options().image_option(instance_name()).specify(path); } @@ -1301,9 +1301,10 @@ void device_image_interface::update_names() if (brief_name == nullptr) brief_name = device_brieftypename(image_type()); + m_canonical_instance_name = string_format("%s%d", inst_name, index + 1); if (count > 1) { - m_instance_name = string_format("%s%d", inst_name, index + 1); + m_instance_name = m_canonical_instance_name; m_brief_instance_name = string_format("%s%d", brief_name, index + 1); } else @@ -1454,11 +1455,11 @@ std::string device_image_interface::software_get_default_slot(const char *defaul { std::string result; - auto iter = device().mconfig().options().image_options().find(instance_name()); - if (iter != device().mconfig().options().image_options().end() && !iter->second.empty()) + const std::string &image_name(device().mconfig().options().image_option(instance_name()).value()); + if (!image_name.empty()) { result.assign(default_card_slot); - const software_part *swpart = find_software_item(iter->second, true); + const software_part *swpart = find_software_item(image_name, true); if (swpart != nullptr) { const char *slot = swpart->feature("slot"); diff --git a/src/emu/diimage.h b/src/emu/diimage.h index 19b7b18292b..c6d3d977caa 100644 --- a/src/emu/diimage.h +++ b/src/emu/diimage.h @@ -213,6 +213,7 @@ 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_canonical_instance_name; } bool uses_file_extension(const char *file_extension) const; const formatlist_type &formatlist() const { return m_formatlist; } @@ -329,8 +330,9 @@ private: util::hash_collection m_hash; - std::string m_brief_instance_name; - std::string m_instance_name; + std::string m_instance_name; // e.g. - "cartridge", "floppydisk2" + std::string m_brief_instance_name; // e.g. - "cart", "flop2" + std::string m_canonical_instance_name; // e.g. - "cartridge1", "floppydisk2" - only used internally in emuopts.cpp // in the case of arcade cabinet with fixed carts inserted, // we want to disable command line cart loading... diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp index 2863ef35caf..d9dc26cea69 100644 --- a/src/emu/emuopts.cpp +++ b/src/emu/emuopts.cpp @@ -10,6 +10,11 @@ #include "emu.h" #include "emuopts.h" +#include "drivenum.h" +#include "softlist_dev.h" +#include "hashfile.h" + +#include //************************************************************************** @@ -212,6 +217,191 @@ 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 + { + // This is returning an empty string instead of nullptr to signify that + // specifying the value is a meaningful operation. The option types that + // return nullptr are option types that cannot have a value (e.g. - commands) + // + // See comments in core_options::entry::value() and core_options::simple_entry::value() + return m_host.system() ? m_host.system()->name : ""; + } + + protected: + virtual void internal_set_value(std::string &&newvalue) override + { + m_host.set_system_name(std::move(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(std::move(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 is a temporary variable used to keep the specified value + // so the result can be returned as 'const char *'. Obviously, this + // value will be trampled upon if value() is called again. This doesn't + // happen in practice + // + // In reality, I want to really return std::optional here + 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), false); + } + + 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 &&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), false); + } + + private: + image_option &m_host; + }; + + // existing option tracker class; used by slot/image calculus to identify existing + // options for later purging + template + class existing_option_tracker + { + public: + existing_option_tracker(const std::unordered_map &map) + { + m_vec.reserve(map.size()); + for (const auto &entry : map) + m_vec.push_back(&entry.first); + } + + template + void remove(const 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::iterator begin() { return m_vec.begin(); } + std::vector::iterator end() { return m_vec.end(); } + + private: + std::vector m_vec; + }; + + + //------------------------------------------------- + // get_full_option_names + //------------------------------------------------- + + std::vector get_full_option_names(const device_image_interface &image) + { + std::vector 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; + } + + + //------------------------------------------------- + // conditionally_peg_priority + //------------------------------------------------- + + void conditionally_peg_priority(core_options::entry::weak_ptr &entry, bool peg_priority) + { + // if the [image|slot] entry was specified outside of the context of the options sytem, we need + // to peg the priority of any associated core_options::entry at the maximum priority + if (peg_priority && !entry.expired()) + entry.lock()->set_priority(OPTION_PRIORITY_MAXIMUM); + } +} + + //************************************************************************** // EMU OPTIONS //************************************************************************** @@ -220,125 +410,662 @@ const options_entry emu_options::s_option_entries[] = // emu_options - constructor //------------------------------------------------- -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) +emu_options::emu_options(option_support support) + : m_support(support) + , m_system(nullptr) + , m_coin_impulse(0) + , m_joystick_contradictory(false) + , m_sleep(true) + , m_refresh_speed(false) + , m_ui(UI_CABINET) { + // add entries + if (support == option_support::FULL || support == option_support::GENERAL_AND_SYSTEM) + add_entry(std::make_shared(*this)); + if (support == option_support::FULL) + add_entry(std::make_shared(*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; + }); } //------------------------------------------------- -// value_changed - to prevent tagmap -// lookups keep copies of frequently requested -// options in member variables. +// emu_options - destructor //------------------------------------------------- -void emu_options::value_changed(const std::string &name, const std::string &value) +emu_options::~emu_options() { - if (name == OPTION_COIN_IMPULSE) +} + + +//------------------------------------------------- +// system_name +//------------------------------------------------- + +const char *emu_options::system_name() const +{ + 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(std::string &&new_system_name) +{ + const game_driver *new_system = nullptr; + + // we are making an attempt - record what we're attempting + m_attempted_system_name = std::move(new_system_name); + + // was a system name specified? + if (!m_attempted_system_name.empty()) { - m_coin_impulse = int_value(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(m_attempted_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'", m_attempted_system_name); + new_system = &driver_list::driver(index); } - else if (name == OPTION_JOYSTICK_CONTRADICTORY) + + // did we change anything? + if (new_system != m_system) { - m_joystick_contradictory = bool_value(OPTION_JOYSTICK_CONTRADICTORY); + // if so, specify the new system and update (if we're fully supporting slot/image options) + m_system = new_system; + m_software_name.clear(); + if (m_support == option_support::FULL) + update_slot_and_image_options(); } - else if (name == OPTION_SLEEP) +} + + +//------------------------------------------------- +// 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) +{ + set_system_name(std::string(new_system_name)); +} + + +//------------------------------------------------- +// update_slot_and_image_options +//------------------------------------------------- + +void emu_options::update_slot_and_image_options() +{ + bool changed; + do { - m_sleep = bool_value(OPTION_SLEEP); - } - else if (name == OPTION_REFRESHSPEED) + 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) { - m_refresh_speed = bool_value(OPTION_REFRESHSPEED); + // 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); + } + } + + } } - else if (name == OPTION_UI) + + // at this point we need to purge stray slot options that may no longer be pertinent + for (auto &opt_name : existing) { - if (value == "simple") - m_ui = UI_SIMPLE; - else - m_ui = UI_CABINET; + 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; } //------------------------------------------------- -// override_get_value - when saving to an INI, we -// need to hook into that process so we can write -// out image/slot options +// add_and_remove_slot_options - add any missing +// and/or purge extraneous slot options //------------------------------------------------- -core_options::override_get_value_result emu_options::override_get_value(const char *name, std::string &value) const +bool emu_options::add_and_remove_image_options() { - if (name) + // 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) { - auto slotiter = m_slot_options.find(name); - if (slotiter != m_slot_options.end()) + // 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())) { - value = slotiter->second.specified_value(); - return slotiter->second.specified() - ? override_get_value_result::OVERRIDE - : override_get_value_result::SKIP; + 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; } + } + + // at this point we need to purge stray image options that may no longer be pertinent + for (auto &opt_name : existing) + { + 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; + } + + return changed; +} - auto imageiter = m_image_options.find(name); - if (imageiter != m_image_options.end()) + +//------------------------------------------------- +// reevaluate_default_card_software - based on recent +// changes in what images are mounted, give drivers +// a chance to specify new default slot options +//------------------------------------------------- + +void emu_options::reevaluate_default_card_software() +{ + // if we don't have a system specified, this is + // a meaningless operation + if (!m_system) + return; + + bool found; + do + { + // 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())) { - value = imageiter->second; - return override_get_value_result::OVERRIDE; + // 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 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(&slot); + if (image) + { + image_path = image_option(image->instance_name()).value(); + + get_hashfile_extrainfo = [image, this](util::core_file &file, std::string &extrainfo) + { + util::hash_collection hashes = image->calculate_hash_on_file(file); + + return hashfile_extrainfo( + hash_path(), + image->device().mconfig().gamedrv(), + hashes, + extrainfo); + }; } - return override_get_value_result::NONE; + // 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); } //------------------------------------------------- -// 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 +// set_software - called to load "unqualified" +// software out of a software list (e.g. - "mame nes 'zelda'") //------------------------------------------------- -bool emu_options::override_set_value(const char *name, const std::string &value) +void emu_options::set_software(std::string &&new_software) { - auto slotiter = m_slot_options.find(name); - if (slotiter != m_slot_options.end()) + // 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()) { - slotiter->second.specify(std::string(value)); - return true; + // 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"); } - auto imageiter = m_image_options.find(name); - if (imageiter != m_image_options.end()) + // we've succeeded; update the set name + m_software_name = std::move(new_software); +} + + +//------------------------------------------------- +// 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()) { - // 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; + // 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 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 '<>_default' + // + // example from SMS: + // + // + // ... + // + // + 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); + + // The text of this options_error_exception() is then passed to osd_printf_error() in cli_frontend::execute(). Therefore, it needs + // to be human readable text. We want to snake through a message about software incompatibility while being silent if that is not + // the case. + // + // Arguably, anything related to user-visible text should really be done within src/frontend. The invocation of + // software_list_device::display_matches() should really be done there as well + if (!found) + throw options_error_exception(""); + else + throw options_error_exception("Software '%s' is incompatible with system '%s'\n", software_name, m_system->name); + } + } } + return results; +} + + +//------------------------------------------------- +// find_slot_option +//------------------------------------------------- - return false; +const slot_option *emu_options::find_slot_option(const std::string &device_name) const +{ + auto iter = m_slot_options.find(device_name); + return iter != m_slot_options.end() ? &iter->second : nullptr; +} + +slot_option *emu_options::find_slot_option(const std::string &device_name) +{ + auto iter = m_slot_options.find(device_name); + return iter != m_slot_options.end() ? &iter->second : nullptr; } + +//------------------------------------------------- +// slot_option +//------------------------------------------------- + +const slot_option &emu_options::slot_option(const std::string &device_name) const +{ + const ::slot_option *opt = find_slot_option(device_name); + assert(opt && "Attempt to access non-existent slot option"); + return *opt; +} + +slot_option &emu_options::slot_option(const std::string &device_name) +{ + ::slot_option *opt = find_slot_option(device_name); + assert(opt && "Attempt to access non-existent slot option"); + return *opt; +} + + +//------------------------------------------------- +// 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; +} + + +//------------------------------------------------- +// command_argument_processed +//------------------------------------------------- + +void emu_options::command_argument_processed() +{ + // some command line arguments require that the system name be set, so we can get slot options + if (command_arguments().size() == 1 && + (command() == "listdevices" || (command() == "listslots") || (command() == "listmedia"))) + { + set_system_name(command_arguments()[0]); + } +} + + +//************************************************************************** +// SLOT OPTIONS +//************************************************************************** + //------------------------------------------------- // slot_option ctor //------------------------------------------------- -slot_option::slot_option(const char *default_value) - : m_specified(false) +slot_option::slot_option(emu_options &host, const char *default_value) + : m_host(host) + , m_specified(false) , m_default_value(default_value ? default_value : "") { } @@ -394,12 +1121,14 @@ std::string slot_option::specified_value() const // slot_option::specify //------------------------------------------------- -void slot_option::specify(std::string &&text) +void slot_option::specify(std::string &&text, bool peg_priority) { + // 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="; - - size_t pos = text.find(bios_arg); + const size_t pos = text.find(bios_arg); if (pos != std::string::npos) { m_specified = true; @@ -412,6 +1141,49 @@ void slot_option::specify(std::string &&text) m_specified_value = std::move(text); m_specified_bios = ""; } + + conditionally_peg_priority(m_entry, peg_priority); + + // we may have changed + possibly_changed(old_value); +} + + +//------------------------------------------------- +// slot_option::specify +//------------------------------------------------- + +void slot_option::specify(const std::string &text, bool peg_priority) +{ + specify(std::string(text), peg_priority); +} + + +//------------------------------------------------- +// 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(); } @@ -428,3 +1200,75 @@ 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(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_canonical_instance_name(cannonical_instance_name) +{ +} + + +//------------------------------------------------- +// image_option::specify +//------------------------------------------------- + +void image_option::specify(const std::string &value, bool peg_priority) +{ + if (value != m_value) + { + m_value = value; + m_host.reevaluate_default_card_software(); + } + conditionally_peg_priority(m_entry, peg_priority); +} + +void image_option::specify(std::string &&value, bool peg_priority) +{ + if (value != m_value) + { + m_value = std::move(value); + m_host.reevaluate_default_card_software(); + } + conditionally_peg_priority(m_entry, peg_priority); +} + + +//------------------------------------------------- +// image_option::setup_option_entry +//------------------------------------------------- + +core_options::entry::shared_ptr image_option::setup_option_entry(std::vector &&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(std::move(names), *this); + m_entry = entry; + return entry; +} diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index e592d04d6e3..21fa27fb22f 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -195,56 +195,78 @@ // TYPE DEFINITIONS //************************************************************************** +class game_driver; +class device_slot_interface; +class emu_options; + class slot_option { public: - slot_option(const char *default_value = nullptr); - slot_option(const slot_option &that) = default; + slot_option(emu_options &host, const char *default_value); + slot_option(const slot_option &that) = delete; 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 specify(const std::string &text, bool peg_priority = true); + void specify(std::string &&text, bool peg_priority = true); void set_bios(std::string &&text); - void set_default_card_software(std::string &&s) { m_default_card_software = std::move(s); } + 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); private: - bool m_specified; - std::string m_specified_value; - std::string m_specified_bios; - std::string m_default_card_software; - std::string m_default_value; + 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 &canonical_instance_name); + image_option(const image_option &that) = delete; + image_option(image_option &&that) = default; + + // accessors + const std::string &canonical_instance_name() const { return m_canonical_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, bool peg_priority = true); + void specify(std::string &&value, bool peg_priority = true); + + // instantiates an option entry (don't call outside of emuopts.cpp) + core_options::entry::shared_ptr setup_option_entry(std::vector &&names); + +private: + emu_options & m_host; + std::string m_canonical_instance_name; + std::string m_value; + core_options::entry::weak_ptr m_entry; }; class emu_options : public core_options { + friend class slot_option; + friend class image_option; public: enum ui_option { @@ -252,12 +274,27 @@ public: UI_SIMPLE }; + enum class option_support + { + FULL, // full option support + GENERAL_AND_SYSTEM, // support for general options and system (no softlist) + GENERAL_ONLY // only support for general options + }; + // construction/destruction - emu_options(); + emu_options(option_support support = option_support::FULL); + ~emu_options(); + + // mutation + void set_system_name(const std::string &new_system_name); + void set_system_name(std::string &&new_system_name); + void set_software(std::string &&new_software); // core options - const char *system_name() const { return value(OPTION_SYSTEMNAME); } - const char *software_name() const { return value(OPTION_SOFTWARENAME); } + const game_driver *system() const { return m_system; } + const char *system_name() const; + const std::string &attempted_system_name() const { return m_attempted_system_name; } + const std::string &software_name() const { return m_software_name; } // core configuration options bool read_config() const { return bool_value(OPTION_READCONFIG); } @@ -427,36 +464,63 @@ public: const char *language() const { return value(OPTION_LANGUAGE); } - // Web server specific optopns + // Web server specific options 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 - std::map &slot_options() { return m_slot_options; } - const std::map &slot_options() const { return m_slot_options; } - std::map &image_options() { return m_image_options; } - const std::map &image_options() const { return m_image_options; } + const ::slot_option &slot_option(const std::string &device_name) const; + ::slot_option &slot_option(const std::string &device_name); + const ::slot_option *find_slot_option(const std::string &device_name) const; + ::slot_option *find_slot_option(const std::string &device_name); + bool has_slot_option(const std::string &device_name) const { return find_slot_option(device_name) ? true : false; } + const ::image_option &image_option(const std::string &device_name) const; + ::image_option &image_option(const std::string &device_name); 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; + virtual void command_argument_processed() override; private: - static const options_entry s_option_entries[]; + struct software_options + { + std::unordered_map slot; + std::unordered_map 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); + + // static list of options entries + static const options_entry s_option_entries[]; + + // the basics + option_support m_support; + const game_driver * m_system; // slots and devices - std::map m_slot_options; - std::map m_image_options; + std::unordered_map m_slot_options; + std::unordered_map m_image_options_cannonical; + std::unordered_map 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; + + // special option; the system name we tried to specify + std::string m_attempted_system_name; + + // special option; the software set name that we did specify + std::string m_software_name; }; #endif // MAME_EMU_EMUOPTS_H diff --git a/src/emu/image.cpp b/src/emu/image.cpp index 416d2744fea..08d532bef51 100644 --- a/src/emu/image.cpp +++ b/src/emu/image.cpp @@ -38,37 +38,12 @@ image_manager::image_manager(running_machine &machine) continue; // find the image option in image_options() - 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()); - } - } + const std::string &startup_image(machine.options().image_option(image.instance_name()).value()); // is an image specified for this image? - if (iter != machine.options().image_options().end() && !iter->second.empty()) + if (!startup_image.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 @@ -93,7 +68,7 @@ image_manager::image_manager(running_machine &machine) image.unload(); // make sure it is removed from the ini file too - machine.options().image_options()[image.instance_name()] = ""; + machine.options().image_option(image.instance_name()).specify(""); if (machine.options().write_config()) write_config(machine.options(), nullptr, &machine.system()); @@ -208,9 +183,15 @@ void image_manager::options_extract() { for (device_image_interface &image : image_interface_iterator(machine().root_device())) { - // only perform this activity for devices where is_reset_on_load() is false; for devices - // where this is true, manipulation of this value is done in reset_and_load() - if (!image.is_reset_on_load()) + // There are two scenarios where we want to extract the option: + // + // 1. When for the device, is_reset_on_load() is false (mounting devices for which is reset_and_load() + // is true forces a reset, hence the name) + // + // 2. When is_reset_on_load(), and this results in a device being unmounted (unmounting is_reset_and_load() + // doesn't force an unmount) + if (!image.is_reset_on_load() + || (!image.exists() && !machine().options().image_option(image.instance_name()).value().empty())) { // we have to assemble the image option differently for software lists and for normal images std::string image_opt; @@ -223,7 +204,7 @@ void image_manager::options_extract() } // and set the option - machine().options().image_options()[image.instance_name()] = std::move(image_opt); + machine().options().image_option(image.instance_name()).specify(std::move(image_opt)); } } diff --git a/src/emu/mconfig.cpp b/src/emu/mconfig.cpp index 485d9cc9268..477f73a018d 100644 --- a/src/emu/mconfig.cpp +++ b/src/emu/mconfig.cpp @@ -39,21 +39,20 @@ 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.slot_options().count(slot_option_name); + bool has_option = options.has_slot_option(slot_option_name); const char *selval; bool is_default; if (!has_option) { - // 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) + // 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 selval = slot.default_option(); is_default = true; } else { - const slot_option &opt = options.slot_options()[slot_option_name]; + const slot_option &opt = options.slot_option(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 6e334b92066..810d13b0515 100644 --- a/src/emu/natkeyboard.cpp +++ b/src/emu/natkeyboard.cpp @@ -365,9 +365,7 @@ void natural_keyboard::set_in_use(bool usage) { // update active usage m_in_use = usage; - std::string error; - machine().options().set_value(OPTION_NATURAL_KEYBOARD, usage, OPTION_PRIORITY_CMDLINE, error); - assert(error.empty()); + machine().options().set_value(OPTION_NATURAL_KEYBOARD, usage, OPTION_PRIORITY_CMDLINE); // 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 118c7fcc0fe..0e702d885b2 100644 --- a/src/emu/romload.cpp +++ b/src/emu/romload.cpp @@ -1091,10 +1091,9 @@ 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; - 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); + driver_specific_options.set_system_name(current_driver.name); + 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); // 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); @@ -1496,12 +1495,13 @@ rom_load_manager::rom_load_manager(running_machine &machine) specbios.assign(machine.options().bios()); else { - 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] + const device_slot_interface *slot = dynamic_cast(&device); + const slot_option *slot_opt = slot + ? &machine.options().slot_option(slot->slot_name()) : nullptr; - specbios = opt && !opt->bios().empty() - ? opt->bios().c_str() + + specbios = slot_opt && !slot_opt->bios().empty() + ? slot_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 6e5d77c40e0..a36b47da683 100644 --- a/src/frontend/mame/clifront.cpp +++ b/src/frontend/mame/clifront.cpp @@ -190,26 +190,30 @@ 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 &args) +void cli_frontend::start_execution(mame_machine_manager *manager, const std::vector &args) { - std::string option_errors; + std::ostringstream option_errors; + + // because softlist evaluation relies on hashpath being populated, we are going to go through + // a special step to force it to be evaluated + mame_options::populate_hashpath_from_args_and_inis(m_options, args); // parse the command line, adding any system-specific options - if (!mame_options::parse_command_line(m_options, args, option_errors)) + try + { + m_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE); + } + catch (options_exception &ex) { // 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()); + if (m_options.command().empty() && mame_options::system(m_options) == nullptr && !m_options.attempted_system_name().empty()) + throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", m_options.attempted_system_name().c_str()); // otherwise, error on the options - throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", strtrimspace(option_errors).c_str()); + throw emu_fatalerror(EMU_ERR_INVALID_CONFIG, "%s", ex.message().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,8 +236,11 @@ void cli_frontend::start_execution(mame_machine_manager *manager, std::vectorstart_luaengine(); - if (!option_errors.empty()) - osd_printf_error("Error in command line:\n%s\n", strtrimspace(option_errors).c_str()); + 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 we can't find it, give an appropriate error const game_driver *system = mame_options::system(m_options); @@ -269,16 +276,19 @@ int cli_frontend::execute(std::vector &args) // if a game was specified, wasn't a wildcard, and our error indicates this was the // reason for failure, offer some suggestions - if (m_result == EMU_ERR_NO_SUCH_GAME && *(m_options.system_name()) != 0 && !core_iswildstr(m_options.system_name()) && mame_options::system(m_options) == nullptr) + if (m_result == EMU_ERR_NO_SUCH_GAME + && !m_options.attempted_system_name().empty() + && !core_iswildstr(m_options.attempted_system_name().c_str()) + && mame_options::system(m_options) == nullptr) { // get the top 16 approximate matches driver_enumerator drivlist(m_options); int matches[16]; - drivlist.find_approximate_matches(m_options.system_name(), ARRAY_LENGTH(matches), matches); + drivlist.find_approximate_matches(m_options.attempted_system_name().c_str(), ARRAY_LENGTH(matches), matches); // print them out osd_printf_error("\n\"%s\" approximately matches the following\n" - "supported machines (best match first):\n\n", m_options.system_name()); + "supported machines (best match first):\n\n", m_options.attempted_system_name().c_str()); for (auto & matche : matches) if (matche != -1) osd_printf_error("%-18s%s\n", drivlist.driver(matche).name, drivlist.driver(matche).type.fullname()); @@ -1441,8 +1451,7 @@ void cli_frontend::romident(const std::vector &args) // create our own copy of options for the purposes of ROM identification // so we are not "polluted" with driver-specific slot/image options emu_options options; - std::string error_string; - options.set_value(OPTION_MEDIAPATH, m_options.media_path(), OPTION_PRIORITY_DEFAULT, error_string); + options.set_value(OPTION_MEDIAPATH, m_options.media_path(), OPTION_PRIORITY_DEFAULT); media_identifier ident(options); @@ -1470,24 +1479,24 @@ const cli_frontend::info_command_struct *cli_frontend::find_command(const std::s { static const info_command_struct s_info_commands[] = { - { CLICOMMAND_LISTXML, 0, -1, false, &cli_frontend::listxml, "[pattern] ..." }, - { CLICOMMAND_LISTFULL, 0, 1, false, &cli_frontend::listfull, "[system name]" }, - { CLICOMMAND_LISTSOURCE, 0, 1, false, &cli_frontend::listsource, "[system name]" }, - { CLICOMMAND_LISTCLONES, 0, 1, false, &cli_frontend::listclones, "[system name]" }, - { CLICOMMAND_LISTBROTHERS, 0, 1, false, &cli_frontend::listbrothers, "[system name]" }, - { CLICOMMAND_LISTCRC, 0, 1, false, &cli_frontend::listcrc, "[system name]" }, - { CLICOMMAND_LISTDEVICES, 0, 1, true, &cli_frontend::listdevices, "[system name]" }, - { CLICOMMAND_LISTSLOTS, 0, 1, true, &cli_frontend::listslots, "[system name]" }, - { CLICOMMAND_LISTROMS, 0, -1, false, &cli_frontend::listroms, "[pattern] ..." }, - { CLICOMMAND_LISTSAMPLES, 0, 1, false, &cli_frontend::listsamples, "[system name]" }, - { CLICOMMAND_VERIFYROMS, 0, -1, false, &cli_frontend::verifyroms, "[pattern] ..." }, - { CLICOMMAND_VERIFYSAMPLES, 0, 1, false, &cli_frontend::verifysamples, "[system name|*]" }, - { CLICOMMAND_LISTMEDIA, 0, 1, true, &cli_frontend::listmedia, "[system name]" }, - { CLICOMMAND_LISTSOFTWARE, 0, 1, false, &cli_frontend::listsoftware, "[system name]" }, - { CLICOMMAND_VERIFYSOFTWARE, 0, 1, false, &cli_frontend::verifysoftware, "[system name|*]" }, - { CLICOMMAND_ROMIDENT, 1, 1, false, &cli_frontend::romident, "(file or directory path)" }, - { CLICOMMAND_GETSOFTLIST, 0, 1, false, &cli_frontend::getsoftlist, "[system name|*]" }, - { CLICOMMAND_VERIFYSOFTLIST, 0, 1, false, &cli_frontend::verifysoftlist, "[system name|*]" } + { CLICOMMAND_LISTXML, 0, -1, &cli_frontend::listxml, "[pattern] ..." }, + { CLICOMMAND_LISTFULL, 0, 1, &cli_frontend::listfull, "[system name]" }, + { CLICOMMAND_LISTSOURCE, 0, 1, &cli_frontend::listsource, "[system name]" }, + { CLICOMMAND_LISTCLONES, 0, 1, &cli_frontend::listclones, "[system name]" }, + { CLICOMMAND_LISTBROTHERS, 0, 1, &cli_frontend::listbrothers, "[system name]" }, + { CLICOMMAND_LISTCRC, 0, 1, &cli_frontend::listcrc, "[system name]" }, + { CLICOMMAND_LISTDEVICES, 0, 1, &cli_frontend::listdevices, "[system name]" }, + { CLICOMMAND_LISTSLOTS, 0, 1, &cli_frontend::listslots, "[system name]" }, + { CLICOMMAND_LISTROMS, 0, -1, &cli_frontend::listroms, "[pattern] ..." }, + { CLICOMMAND_LISTSAMPLES, 0, 1, &cli_frontend::listsamples, "[system name]" }, + { CLICOMMAND_VERIFYROMS, 0, -1, &cli_frontend::verifyroms, "[pattern] ..." }, + { CLICOMMAND_VERIFYSAMPLES, 0, 1, &cli_frontend::verifysamples, "[system name|*]" }, + { CLICOMMAND_LISTMEDIA, 0, 1, &cli_frontend::listmedia, "[system name]" }, + { CLICOMMAND_LISTSOFTWARE, 0, 1, &cli_frontend::listsoftware, "[system name]" }, + { CLICOMMAND_VERIFYSOFTWARE, 0, 1, &cli_frontend::verifysoftware, "[system name|*]" }, + { CLICOMMAND_ROMIDENT, 1, 1, &cli_frontend::romident, "(file or directory path)" }, + { CLICOMMAND_GETSOFTLIST, 0, 1, &cli_frontend::getsoftlist, "[system name|*]" }, + { CLICOMMAND_VERIFYSOFTLIST, 0, 1, &cli_frontend::verifysoftlist, "[system name|*]" } }; for (const auto &info_command : s_info_commands) @@ -1499,17 +1508,6 @@ const cli_frontend::info_command_struct *cli_frontend::find_command(const std::s } -//------------------------------------------------- -// parse_slot_options_for_auxverb -//------------------------------------------------- - -bool cli_frontend::parse_slot_options_for_auxverb(const std::string &auxverb) -{ - const info_command_struct *command = find_command(auxverb); - return command && command->specify_system; -} - - //------------------------------------------------- // execute_commands - execute various frontend // commands @@ -1545,10 +1543,10 @@ void cli_frontend::execute_commands(const char *exename) } // other commands need the INIs parsed - std::string option_errors; + std::ostringstream option_errors; mame_options::parse_standard_inis(m_options,option_errors); - if (!option_errors.empty()) - osd_printf_error("%s\n", option_errors.c_str()); + if (option_errors.tellp() > 0) + osd_printf_error("%s\n", option_errors.str().c_str()); // createconfig? if (m_options.command() == CLICOMMAND_CREATECONFIG) diff --git a/src/frontend/mame/clifront.h b/src/frontend/mame/clifront.h index 7e19fdd2e84..71e161dba07 100644 --- a/src/frontend/mame/clifront.h +++ b/src/frontend/mame/clifront.h @@ -34,16 +34,12 @@ public: // execute based on the incoming argc/argv int execute(std::vector &args); - // should we parse slot options for the auxillary verb in question? - static bool parse_slot_options_for_auxverb(const std::string &auxverb); - private: struct info_command_struct { const char *option; int min_args; int max_args; - bool specify_system; void (cli_frontend::*function)(const std::vector &args); const char *usage; }; @@ -72,7 +68,7 @@ private: void execute_commands(const char *exename); void display_help(const char *exename); void output_single_softlist(FILE *out, software_list_device &swlist); - void start_execution(mame_machine_manager *manager, std::vector &args); + void start_execution(mame_machine_manager *manager, const std::vector &args); static const info_command_struct *find_command(const std::string &s); // internal state diff --git a/src/frontend/mame/info.cpp b/src/frontend/mame/info.cpp index 4aebe5b29c6..fa836b27abe 100644 --- a/src/frontend/mame/info.cpp +++ b/src/frontend/mame/info.cpp @@ -191,9 +191,7 @@ const char info_xml_creator::s_dtd_string[] = info_xml_creator::info_xml_creator(emu_options const &options) : m_output(nullptr) - , m_lookup_options(options) { - mame_options::remove_device_options(m_lookup_options); } diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp index 0dde20fe611..6b62cfe726b 100644 --- a/src/frontend/mame/luaengine.cpp +++ b/src/frontend/mame/luaengine.cpp @@ -721,7 +721,7 @@ void lua_engine::initialize() emu["app_version"] = &emulator_info::get_bare_build_version; emu["gamename"] = [this](){ return machine().system().type.fullname(); }; emu["romname"] = [this](){ return machine().basename(); }; - emu["softname"] = [this](){ return machine().options().software_name(); }; + 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) { @@ -1016,9 +1016,11 @@ void lua_engine::initialize() "entries", sol::property([this](core_options &options) { sol::table table = sol().create_table(); int unadorned_index = 0; - for(core_options::entry &curentry : options) + for (auto &curentry : options.entries()) { - const char *name = curentry.name(); + const char *name = curentry->names().size() > 0 + ? curentry->name().c_str() + : nullptr; bool is_unadorned = false; // check if it's unadorned if (name && strlen(name) && !strcmp(name, options.unadorned(unadorned_index))) @@ -1026,8 +1028,8 @@ void lua_engine::initialize() unadorned_index++; is_unadorned = true; } - if (!curentry.is_header() && !curentry.is_command() && !curentry.is_internal() && !is_unadorned) - table[name] = &curentry; + if (curentry->type() != core_options::option_type::HEADER && curentry->type() != core_options::option_type::COMMAND && !is_unadorned) + table[name] = &*curentry; } return table; })); @@ -1068,18 +1070,19 @@ void lua_engine::initialize() e.set_value(val, OPTION_PRIORITY_CMDLINE); }, [this](core_options::entry &e) -> sol::object { - if(!e.type()) + if (e.type() == core_options::option_type::INVALID) return sol::make_object(sol(), sol::nil); switch(e.type()) { - case OPTION_BOOLEAN: + case core_options::option_type::BOOLEAN: return sol::make_object(sol(), atoi(e.value()) != 0); - case OPTION_INTEGER: + case core_options::option_type::INTEGER: return sol::make_object(sol(), atoi(e.value())); - case OPTION_FLOAT: + case core_options::option_type::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 cdab8e4d9c9..bc9d9b334b7 100644 --- a/src/frontend/mame/mame.cpp +++ b/src/frontend/mame/mame.cpp @@ -124,36 +124,38 @@ void mame_machine_manager::start_luaengine() std::vector 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) { - bool result = m_plugins->parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, error); - if (!result) + try + { + m_plugins->parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, false); + } + catch (options_exception &) + { osd_printf_error("**Error loading plugin.ini**\n"); + } } } - for (auto &curentry : *m_plugins) + for (auto &curentry : m_plugins->entries()) { - if (!curentry.is_header()) + if (curentry->type() != core_options::option_type::HEADER) { - if (std::find(include.begin(), include.end(), curentry.name()) != include.end()) + if (std::find(include.begin(), include.end(), curentry->name()) != include.end()) { - std::string error_string; - m_plugins->set_value(curentry.name(), "1", OPTION_PRIORITY_CMDLINE, error_string); + m_plugins->set_value(curentry->name(), "1", OPTION_PRIORITY_CMDLINE); } - if (std::find(exclude.begin(), exclude.end(), curentry.name()) != exclude.end()) + if (std::find(exclude.begin(), exclude.end(), curentry->name()) != exclude.end()) { - std::string error_string; - m_plugins->set_value(curentry.name(), "0", OPTION_PRIORITY_CMDLINE, error_string); + m_plugins->set_value(curentry->name(), "0", OPTION_PRIORITY_CMDLINE); } } } } - if (options().console()) { - std::string error_string; - m_plugins->set_value("console", "1", OPTION_PRIORITY_CMDLINE, error_string); + if (options().console()) + { + m_plugins->set_value("console", "1", OPTION_PRIORITY_CMDLINE); } m_lua->initialize(); @@ -202,9 +204,8 @@ int mame_machine_manager::execute() // parse any INI files as the first thing if (m_options.read_config()) { - m_options.revert(OPTION_PRIORITY_INI); - std::string errors; - mame_options::parse_standard_inis(m_options,errors); + std::ostringstream errors; + mame_options::parse_standard_inis(m_options, errors); } // otherwise, perform validity checks before anything else @@ -216,11 +217,6 @@ 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); @@ -237,12 +233,13 @@ int mame_machine_manager::execute() if (m_new_driver_pending) { // set up new system name and adjust device options accordingly - mame_options::set_system_name(m_options,m_new_driver_pending->name); + m_options.set_system_name(m_new_driver_pending->name); m_firstrun = true; } else { - if (machine.exit_pending()) mame_options::set_system_name(m_options,""); + if (machine.exit_pending()) + m_options.set_system_name(""); } if (machine.exit_pending() && (!started_empty || is_empty)) diff --git a/src/frontend/mame/mameopts.cpp b/src/frontend/mame/mameopts.cpp index 9a00f1543e2..fc73c36a9f7 100644 --- a/src/frontend/mame/mameopts.cpp +++ b/src/frontend/mame/mameopts.cpp @@ -22,511 +22,21 @@ #include -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 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(&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 &args, std::string &error_string) -{ - // parse the command line - if (!options.parse_command_line(args, OPTION_PRIORITY_CMDLINE, error_string)) - return false; - - // in order to evaluate softlist options, we need to fish any hashpath variable out of INI files; this is - // because hashpath in particular can affect softlist evaluation - if (options.software_name()[0] != '\0' && options.read_config()) - populate_hashpath_from_ini_files(options); - - // 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(); - }; - - // some auxillary verbs expect that slot options are specified; and to do this we need to figure - // out if this is necessary for this particular auxillary verb, and if so, set the system name - if (!options.command().empty() - && cli_frontend::parse_slot_options_for_auxverb(options.command()) - && !options.command_arguments().empty() - && !core_iswildstr(options.command_arguments()[0].c_str())) - { - std::string error_string; - options.set_value(OPTION_SYSTEMNAME, options.command_arguments()[0].c_str(), OPTION_PRIORITY_CMDLINE, error_string); - - const game_driver *system = mame_options::system(options); - if (!system) - throw emu_fatalerror(EMU_ERR_NO_SUCH_GAME, "Unknown system '%s'", options.system_name()); - } - - // 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 mame_options::evaluate_initial_softlist_options(emu_options &options) -{ - std::map 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 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 '<>_default' - // - // example from SMS: - // - // - // ... - // - // - 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::string &error_string, const game_driver *driver) +void mame_options::parse_standard_inis(emu_options &options, std::ostream &error_stream, 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_string); + 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); // debug mode: parse "debug.ini" as well if (options.debug()) - parse_one_ini(options,"debug", OPTION_PRIORITY_DEBUG_INI, &error_string); + parse_one_ini(options, "debug", OPTION_PRIORITY_DEBUG_INI, &error_stream); // if we have a valid system driver, parse system-specific INI files const game_driver *cursystem = (driver == nullptr) ? system(options) : driver; @@ -535,18 +45,18 @@ void mame_options::parse_standard_inis(emu_options &options, std::string &error_ // parse "vertical.ini" or "horizont.ini" if (cursystem->flags & ORIENTATION_SWAP_XY) - parse_one_ini(options,"vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_string); + parse_one_ini(options, "vertical", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); else - parse_one_ini(options,"horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_string); + parse_one_ini(options, "horizont", OPTION_PRIORITY_ORIENTATION_INI, &error_stream); if (cursystem->flags & MACHINE_TYPE_ARCADE) - parse_one_ini(options,"arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_string); + parse_one_ini(options, "arcade", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); else if (cursystem->flags & MACHINE_TYPE_CONSOLE) - parse_one_ini(options,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_string); + parse_one_ini(options ,"console", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); else if (cursystem->flags & MACHINE_TYPE_COMPUTER) - parse_one_ini(options,"computer", OPTION_PRIORITY_SYSTYPE_INI, &error_string); + parse_one_ini(options, "computer", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); else if (cursystem->flags & MACHINE_TYPE_OTHER) - parse_one_ini(options,"othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_string); + parse_one_ini(options, "othersys", OPTION_PRIORITY_SYSTYPE_INI, &error_stream); machine_config config(*cursystem, options); for (const screen_device &device : screen_device_iterator(config.root_device())) @@ -554,35 +64,35 @@ void mame_options::parse_standard_inis(emu_options &options, std::string &error_ // parse "raster.ini" for raster games if (device.screen_type() == SCREEN_TYPE_RASTER) { - parse_one_ini(options,"raster", OPTION_PRIORITY_SCREEN_INI, &error_string); + parse_one_ini(options, "raster", OPTION_PRIORITY_SCREEN_INI, &error_stream); break; } // parse "vector.ini" for vector games if (device.screen_type() == SCREEN_TYPE_VECTOR) { - parse_one_ini(options,"vector", OPTION_PRIORITY_SCREEN_INI, &error_string); + parse_one_ini(options, "vector", OPTION_PRIORITY_SCREEN_INI, &error_stream); break; } // parse "lcd.ini" for lcd games if (device.screen_type() == SCREEN_TYPE_LCD) { - parse_one_ini(options,"lcd", OPTION_PRIORITY_SCREEN_INI, &error_string); + parse_one_ini(options, "lcd", OPTION_PRIORITY_SCREEN_INI, &error_stream); break; } } // next parse "source/.ini" std::string sourcename = core_filename_extract_base(cursystem->type.source(), true).insert(0, "source" PATH_SEPARATOR); - parse_one_ini(options,sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_string); + parse_one_ini(options, sourcename.c_str(), OPTION_PRIORITY_SOURCE_INI, &error_stream); // 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_string); + parse_one_ini(options, driver_list::driver(gparent).name, OPTION_PRIORITY_GPARENT_INI, &error_stream); if (parent != -1) - 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); + 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); } @@ -598,137 +108,90 @@ 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 //------------------------------------------------- -bool mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::string *error_string) +void mame_options::parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream) { // don't parse if it has been disabled if (!options.read_config()) - return false; + return; // 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 false; + return; // parse the file osd_printf_verbose("Parsing %s.ini\n", basename); - std::string error; - bool result = options.parse_ini_file((util::core_file&)file, priority, 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)); + try + { + options.parse_ini_file((util::core_file&)file, priority, priority < OPTION_PRIORITY_DRIVER_INI, 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; + } - return result; } //------------------------------------------------- -// populate_hashpath_from_ini_files +// populate_hashpath_from_args_and_inis //------------------------------------------------- -void mame_options::populate_hashpath_from_ini_files(emu_options &options) +void mame_options::populate_hashpath_from_args_and_inis(emu_options &options, const std::vector &args) { - // create temporary emu_options for the purposes of evaluating the INI files - emu_options temp_options; - std::string temp_error_string; - temp_options.set_value(OPTION_SYSTEMNAME, options.system_name(), OPTION_PRIORITY_MAXIMUM, temp_error_string); - temp_options.set_value(OPTION_INIPATH, options.ini_path(), OPTION_PRIORITY_MAXIMUM, temp_error_string); + // The existence of this function comes from the fact that for softlist options to be properly + // evaluated, we need to have the hashpath variable set. The problem is that the hashpath may + // be set anywhere on the command line, but also in any of the myriad INI files that we parse, some + // of which may be system specific (e.g. - nes.ini) or otherwise influenced by the system (e.g. - vector.ini) + // + // I think that it is terrible that we have to do a completely independent pass on the command line and every + // argument simply because any one of these things might be setting - hashpath.Unless we invest the effort in + // building some sort of "late binding" apparatus for options(e.g. - delay evaluation of softlist options until + // we've scoured all INIs for hashpath) that can completely straddle the command line and the INI worlds, doing + // this is the best that we can do IMO. + + // parse the command line + emu_options temp_options(emu_options::option_support::GENERAL_AND_SYSTEM); + try + { + temp_options.parse_command_line(args, OPTION_PRIORITY_CMDLINE, true); + } + catch (options_exception &) + { + // Something is very long; we have bigger problems than -hashpath possibly + // being in never-never land. Punt and let the main code fail + return; + } + + // if we have an auxillary verb, hashpath is irrelevant + if (!temp_options.command().empty()) + return; - // read the INIs into temp_options - parse_standard_inis(temp_options, temp_error_string); + // read INI files + if (temp_options.read_config()) + { + std::ostringstream error_stream; + parse_standard_inis(temp_options, error_stream); + } // and fish out hashpath const auto entry = temp_options.get_entry(OPTION_HASHPATH); if (entry) - options.set_value(OPTION_HASHPATH, entry->value(), entry->priority(), temp_error_string); + { + try + { + options.set_value(OPTION_HASHPATH, entry->value(), entry->priority()); + } + catch (options_exception &) + { + } + } } diff --git a/src/frontend/mame/mameopts.h b/src/frontend/mame/mameopts.h index f9ee3325e52..2b178fb2ead 100644 --- a/src/frontend/mame/mameopts.h +++ b/src/frontend/mame/mameopts.h @@ -50,45 +50,15 @@ class software_part; class mame_options { - static const uint32_t OPTION_FLAG_DEVICE = 0x80000000; - public: - typedef std::function value_specifier_func; - // parsing wrappers - static bool parse_command_line(emu_options &options, std::vector &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 void parse_standard_inis(emu_options &options, std::ostream &error_stream, const game_driver *driver = nullptr); 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); + static void populate_hashpath_from_args_and_inis(emu_options &options, const std::vector &args); 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 bool parse_one_ini(emu_options &options, const char *basename, int priority, std::string *error_string = nullptr); - - // softlist handling - static std::map evaluate_initial_softlist_options(emu_options &options); - - // special function to fish hashpath out of INI files - needed for softlist processing - static void populate_hashpath_from_ini_files(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 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; + static void parse_one_ini(emu_options &options, const char *basename, int priority, std::ostream *error_stream = nullptr); }; #endif // MAME_FRONTEND_MAMEOPTS_H diff --git a/src/frontend/mame/pluginopts.cpp b/src/frontend/mame/pluginopts.cpp index 779ac6ff709..b9a9e8d3c3b 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(core_strdup(plugin_name.c_str()),core_strdup(description.c_str()), OPTION_BOOLEAN, start ? "1" : "0"); + add_entry({ std::move(plugin_name) }, core_strdup(description.c_str()), option_type::BOOLEAN, start ? "1" : "0"); } } diff --git a/src/frontend/mame/ui/custui.cpp b/src/frontend/mame/ui/custui.cpp index 21b2f196440..8f1614861cb 100644 --- a/src/frontend/mame/ui/custui.cpp +++ b/src/frontend/mame/ui/custui.cpp @@ -64,12 +64,10 @@ menu_custom_ui::menu_custom_ui(mame_ui_manager &mui, render_container &container menu_custom_ui::~menu_custom_ui() { - std::string error_string; - ui().options().set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE, error_string); + ui().options().set_value(OPTION_HIDE_PANELS, ui_globals::panels_status, OPTION_PRIORITY_CMDLINE); if (!m_lang.empty()) { - machine().options().set_value(OPTION_LANGUAGE, m_lang[m_currlang].c_str(), OPTION_PRIORITY_CMDLINE, error_string); - machine().options().mark_changed(OPTION_LANGUAGE); + machine().options().set_value(OPTION_LANGUAGE, m_lang[m_currlang].c_str(), OPTION_PRIORITY_CMDLINE); load_translation(machine().options()); } ui_globals::reset = true; @@ -215,22 +213,10 @@ 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(); - - 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()); - } - } - + 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()); } //------------------------------------------------- @@ -264,11 +250,9 @@ menu_font_ui::~menu_font_ui() name.insert(0, "[B]"); } #endif - 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); + 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); } //------------------------------------------------- @@ -463,11 +447,11 @@ menu_colors_ui::menu_colors_ui(mame_ui_manager &mui, render_container &container menu_colors_ui::~menu_colors_ui() { - std::string error_string, dec_color; + std::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, error_string); + ui().options().set_value(m_color_table[index].option, dec_color.c_str(), OPTION_PRIORITY_CMDLINE); } } diff --git a/src/frontend/mame/ui/dirmenu.cpp b/src/frontend/mame/ui/dirmenu.cpp index baea5973efb..bb1494e37c5 100644 --- a/src/frontend/mame/ui/dirmenu.cpp +++ b/src/frontend/mame/ui/dirmenu.cpp @@ -352,11 +352,10 @@ 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, error_string); + ui().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); 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, error_string); - machine().options().mark_changed(s_folders[m_ref].option); + machine().options().set_value(s_folders[m_ref].option, m_current_path.c_str(), OPTION_PRIORITY_CMDLINE); } } else @@ -371,11 +370,10 @@ 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, error_string); + ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); 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, error_string); - machine().options().mark_changed(s_folders[m_ref].option); + machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); } } @@ -592,11 +590,10 @@ 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, error_string); + ui().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); 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, error_string); - machine().options().mark_changed(s_folders[m_ref].option); + machine().options().set_value(s_folders[m_ref].option, tmppath.c_str(), OPTION_PRIORITY_CMDLINE); } reset_parent(reset_options::REMEMBER_REF); diff --git a/src/frontend/mame/ui/info.cpp b/src/frontend/mame/ui/info.cpp index dfcffcac1fb..e4abb6ce94d 100644 --- a/src/frontend/mame/ui/info.cpp +++ b/src/frontend/mame/ui/info.cpp @@ -326,8 +326,7 @@ std::string machine_info::mandatory_images() { if (image.must_be_loaded()) { - auto iter = m_machine.options().image_options().find(image.instance_name()); - if (iter == m_machine.options().image_options().end() || iter->second.empty()) + if (m_machine.options().image_option(image.instance_name()).value().empty()) { if (is_first) is_first = false; diff --git a/src/frontend/mame/ui/miscmenu.cpp b/src/frontend/mame/ui/miscmenu.cpp index 6d797bc8dda..e48b042b53d 100644 --- a/src/frontend/mame/ui/miscmenu.cpp +++ b/src/frontend/mame/ui/miscmenu.cpp @@ -127,12 +127,10 @@ void menu_bios_selection::handle() if (val>cnt) val=1; dev->set_system_bios(val); if (strcmp(dev->tag(),":")==0) { - std::string error; - machine().options().set_value("bios", val-1, OPTION_PRIORITY_CMDLINE, error); - assert(error.empty()); + machine().options().set_value("bios", val-1, OPTION_PRIORITY_CMDLINE); } else { const char *slot_option_name = dev->owner()->tag() + 1; - machine().options().slot_options()[slot_option_name].set_bios(string_format("%d", val - 1)); + machine().options().slot_option(slot_option_name).set_bios(string_format("%d", val - 1)); } reset(reset_options::REMEMBER_REF); } @@ -671,15 +669,14 @@ 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::string error; - mame_options::parse_standard_inis(m_opts,error, m_drv); + std::ostringstream error; + mame_options::parse_standard_inis(m_opts, error, m_drv); setup_bios(); } @@ -750,9 +747,7 @@ 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; - std::string error; - m_opts.set_value(OPTION_BIOS, m_bios[m_curbios].second, OPTION_PRIORITY_CMDLINE, error); - m_opts.mark_changed(OPTION_BIOS); + m_opts.set_value(OPTION_BIOS, m_bios[m_curbios].second, OPTION_PRIORITY_CMDLINE); reset(reset_options::REMEMBER_POSITION); } } @@ -914,8 +909,7 @@ 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); - std::string error_string; - plugins.set_value((const char*)menu_event->itemref, oldval == 1 ? 0 : 1, OPTION_PRIORITY_CMDLINE, error_string); + plugins.set_value((const char*)menu_event->itemref, oldval == 1 ? 0 : 1, OPTION_PRIORITY_CMDLINE); changed = true; } } @@ -931,13 +925,13 @@ void menu_plugins_configure::populate(float &customtop, float &custombottom) { plugin_options& plugins = mame_machine_manager::instance()->plugins(); - for (auto &curentry : plugins) + for (auto &curentry : plugins.entries()) { - if (!curentry.is_header()) + if (curentry->type() != OPTION_HEADER) { - 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()); + 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()); } } item_append(menu_item_type::SEPARATOR); diff --git a/src/frontend/mame/ui/selgame.cpp b/src/frontend/mame/ui/selgame.cpp index faeb1f33aff..33cebb00083 100644 --- a/src/frontend/mame/ui/selgame.cpp +++ b/src/frontend/mame/ui/selgame.cpp @@ -116,8 +116,7 @@ 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, error_string); - mui.machine().options().set_value(OPTION_SOFTWARENAME, "", OPTION_PRIORITY_CMDLINE, error_string); + mui.machine().options().set_value(OPTION_SNAPNAME, "%g/%i", OPTION_PRIORITY_CMDLINE); ui_globals::curimage_view = FIRST_VIEW; ui_globals::curdats_view = 0; @@ -155,9 +154,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, 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); + 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); ui().save_ui_options(); } @@ -942,11 +941,10 @@ void menu_select_game::inkey_select_favorite(const event *menu_event) return; } - std::string error_string; std::string string_list = string_format("%s:%s:%s:%s", ui_swinfo->listname, ui_swinfo->shortname, ui_swinfo->part, ui_swinfo->instance); - mopt.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + mopt.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); 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, error_string); + mopt.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); 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 5d95d4c9a2e..305cd3b76c1 100644 --- a/src/frontend/mame/ui/selsoft.cpp +++ b/src/frontend/mame/ui/selsoft.cpp @@ -144,9 +144,6 @@ 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); } //------------------------------------------------- @@ -718,11 +715,10 @@ void menu_select_software::inkey_select(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); - machine().options().set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE, error_string); + machine().options().set_system_name(m_driver->name); + machine().options().set_value(OPTION_SOFTWARENAME, ui_swinfo->shortname, OPTION_PRIORITY_CMDLINE); 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, error_string); + machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); reselect_last::driver = drivlist.driver().name; reselect_last::software = ui_swinfo->shortname; reselect_last::swlist = ui_swinfo->listname; @@ -1288,9 +1284,8 @@ 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, error_string); + machine().options().set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); reselect_last::driver = m_uiinfo->driver->name; reselect_last::software = m_uiinfo->shortname; @@ -1298,7 +1293,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, error_string); + machine().options().set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); mame_machine_manager::instance()->schedule_new_driver(*m_uiinfo->driver); machine().schedule_hard_reset(); @@ -1400,8 +1395,7 @@ void bios_selection::handle() reselect_last::set(true); } - std::string error; - moptions.set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE, error); + moptions.set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE); mame_machine_manager::instance()->schedule_new_driver(*s_driver); machine().schedule_hard_reset(); stack_reset(); @@ -1409,8 +1403,7 @@ void bios_selection::handle() else { ui_software_info *ui_swinfo = (ui_software_info *)m_driver; - std::string error; - machine().options().set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE, error); + machine().options().set_value(OPTION_BIOS, elem.second, OPTION_PRIORITY_CMDLINE); 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()); @@ -1431,11 +1424,10 @@ void bios_selection::handle() menu::stack_push(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, error_string); + moptions.set_value(OPTION_SOFTWARENAME, string_list.c_str(), OPTION_PRIORITY_CMDLINE); 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, error_string); + moptions.set_value(OPTION_SNAPNAME, snap_list.c_str(), OPTION_PRIORITY_CMDLINE); 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 1e3d8faff88..71507bf426f 100644 --- a/src/frontend/mame/ui/slotopt.cpp +++ b/src/frontend/mame/ui/slotopt.cpp @@ -100,10 +100,10 @@ device_slot_option *menu_slot_devices::get_current_option(device_slot_interface { std::string current; - const char *slot_option_name = slot.slot_name(); - if (!slot.fixed() && machine().options().slot_options().count(slot_option_name) > 0) + if (!slot.fixed()) { - current = machine().options().slot_options()[slot_option_name].value(); + const char *slot_option_name = slot.slot_name(); + current = machine().options().slot_option(slot_option_name).value(); } else { @@ -185,9 +185,82 @@ const char *menu_slot_devices::get_previous_slot(device_slot_interface &slot) co void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char *val) { - std::string error; - machine().options().set_value(slot.slot_name(), val, OPTION_PRIORITY_CMDLINE, error); - assert(error.empty()); + // we might change slot options; in the spirit of user friendliness, we should record all current + // options + record_current_options(); + + // find the slot option + slot_option &opt(machine().options().slot_option(slot.slot_name())); + + // specify it + opt.specify(val); + + // erase this from our recorded options list - this is the slot we're trying to change! + m_slot_options.erase(slot.slot_name()); + + // refresh any options that we might have annotated earlier + while (try_refresh_current_options()) + ; + + // changing the options may result in options changing; we need to reset + reset(reset_options::REMEMBER_POSITION); +} + + +//------------------------------------------------- +// record_current_options +//------------------------------------------------- + +void menu_slot_devices::record_current_options() +{ + for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) + { + // we're doing this out of a desire to honor user-selectable options; therefore it only + // makes sense to record values for selectable options + if (slot.has_selectable_options()) + { + // get the slot option + const slot_option &opt(machine().options().slot_option(slot.slot_name())); + + // and record the value in our local cache + m_slot_options[slot.slot_name()] = opt.specified_value(); + } + } +} + + +//------------------------------------------------- +// try_refresh_current_options +//------------------------------------------------- + +bool menu_slot_devices::try_refresh_current_options() +{ + // enumerate through all slot options that we've tracked + for (const auto &opt : m_slot_options) + { + // do we have a value different than what we're tracking? + slot_option *slotopt = machine().options().find_slot_option(opt.first); + if (slotopt && slotopt->specified_value() != opt.second) + { + // specify this option (but catch errors) + try + { + slotopt->specify(opt.second); + + // the option was successfully specified; it isn't safe to continue + // checking slots as the act of specifying the slot may have drastically + // changed the options list + return true; + } + catch (options_exception &) + { + // this threw an exception - that is fine; we can just proceed + } + } + } + + // we've went through all options without changing anything + return false; } @@ -197,8 +270,12 @@ void menu_slot_devices::set_slot_device(device_slot_interface &slot, const char void menu_slot_devices::populate(float &customtop, float &custombottom) { + // we need to keep our own copy of the machine_config because we + // can change this out from under the caller + m_config = std::make_unique(machine().system(), machine().options()); + // cycle through all devices for this system - for (device_slot_interface &slot : slot_interface_iterator(machine().root_device())) + for (device_slot_interface &slot : slot_interface_iterator(m_config->root_device())) { // does this slot have any selectable options? bool has_selectable_options = slot.has_selectable_options(); @@ -238,7 +315,6 @@ void menu_slot_devices::handle() { if (menu_event->itemref == ITEMREF_RESET && 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) @@ -246,7 +322,6 @@ void menu_slot_devices::handle() device_slot_interface *slot = (device_slot_interface *)menu_event->itemref; const char *val = (menu_event->iptkey == IPT_UI_LEFT) ? get_previous_slot(*slot) : get_next_slot(*slot); set_slot_device(*slot, val); - reset(reset_options::REMEMBER_REF); } else if (menu_event->iptkey == IPT_UI_SELECT) { diff --git a/src/frontend/mame/ui/slotopt.h b/src/frontend/mame/ui/slotopt.h index a3a08bb9590..eabec934c33 100644 --- a/src/frontend/mame/ui/slotopt.h +++ b/src/frontend/mame/ui/slotopt.h @@ -15,6 +15,7 @@ #include "ui/menu.h" +#include namespace ui { class menu_slot_devices : public menu @@ -32,6 +33,12 @@ private: const char *get_next_slot(device_slot_interface &slot) const; const char *get_previous_slot(device_slot_interface &slot) const; void set_slot_device(device_slot_interface &slot, const char *val); + void record_current_options(); + bool try_refresh_current_options(); + + // variables + std::unique_ptr m_config; + std::unordered_map m_slot_options; }; } // namespace ui diff --git a/src/frontend/mame/ui/sndmenu.cpp b/src/frontend/mame/ui/sndmenu.cpp index 0a0d1c019ed..f2304b529ea 100644 --- a/src/frontend/mame/ui/sndmenu.cpp +++ b/src/frontend/mame/ui/sndmenu.cpp @@ -45,23 +45,19 @@ 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, error_string); - machine().options().mark_changed(OSDOPTION_SOUND); + moptions.set_value(OSDOPTION_SOUND, m_sound ? OSDOPTVAL_AUTO : OSDOPTVAL_NONE, OPTION_PRIORITY_CMDLINE); } 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, error_string); - machine().options().mark_changed(OPTION_SAMPLERATE); + moptions.set_value(OPTION_SAMPLERATE, m_sound_rate[m_cur_rates], OPTION_PRIORITY_CMDLINE); } if (moptions.bool_value(OPTION_SAMPLES)!=m_samples) { - moptions.set_value(OPTION_SAMPLES, m_samples, OPTION_PRIORITY_CMDLINE, error_string); - machine().options().mark_changed(OPTION_SAMPLES); + moptions.set_value(OPTION_SAMPLES, m_samples, OPTION_PRIORITY_CMDLINE); } } diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index e28b0c58125..01d3d58027e 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -240,8 +240,7 @@ 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, error_string); - sm_option.entry->mark_changed(); + sm_option.options->set_value(sm_option.name, !strcmp(sm_option.entry->value(),"1") ? "0" : "1", OPTION_PRIORITY_CMDLINE); break; case OPTION_INTEGER: if (menu_event->iptkey == IPT_UI_LEFT || menu_event->iptkey == IPT_UI_RIGHT) @@ -249,8 +248,7 @@ 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, error_string); - sm_option.entry->mark_changed(); + sm_option.options->set_value(sm_option.name, i_cur, OPTION_PRIORITY_CMDLINE); } break; case OPTION_FLOAT: @@ -260,17 +258,19 @@ void submenu::handle() f_cur = atof(sm_option.entry->value()); if (sm_option.entry->has_range()) { - f_step = atof(sm_option.entry->minimum()); + const char *minimum = sm_option.entry->minimum(); + const char *maximum = sm_option.entry->maximum(); + f_step = atof(minimum); if (f_step <= 0.0f) { - int pmin = getprecisionchr(sm_option.entry->minimum()); - int pmax = getprecisionchr(sm_option.entry->maximum()); + int pmin = getprecisionchr(minimum); + int pmax = getprecisionchr(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()); + int precision = getprecisionchr(sm_option.entry->default_value().c_str()); tmptxt = '1' + std::string(precision, '0'); f_step = 1 / atof(tmptxt.c_str()); } @@ -279,8 +279,7 @@ 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, error_string); - sm_option.entry->mark_changed(); + sm_option.options->set_value(sm_option.name, tmptxt.c_str(), OPTION_PRIORITY_CMDLINE); } break; case OPTION_STRING: @@ -293,10 +292,11 @@ 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, error_string); - sm_option.entry->mark_changed(); + sm_option.options->set_value(sm_option.name, v_cur.c_str(), OPTION_PRIORITY_CMDLINE); } break; + default: + break; } break; default: @@ -391,7 +391,7 @@ void submenu::populate(float &customtop, float &custombottom) break; case OPTION_STRING: { - std::string const v_cur(sm_option->entry->value()); + std::string 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 acaf28d90e4..f17b6fe53ff 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 *entry; + core_options::entry::shared_ptr entry; core_options *options; std::vector value; }; diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 7ea8b4c71bb..f669321092f 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -2117,14 +2117,18 @@ 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) { - bool result = options().parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, error); - if (!result) + try + { + options().parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, true); + } + catch (options_exception &) + { osd_printf_error("**Error loading ui.ini**\n"); + } } } @@ -2155,28 +2159,37 @@ void mame_ui_manager::save_main_option() { // parse the file std::string error; - emu_options options(machine().options()); // This way we make sure that all OSD parts are in - std::string error_string; + emu_options options(emu_options::option_support::GENERAL_ONLY); // This way we make sure that all OSD parts are in + + options.copy_from(machine().options()); // 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) { - bool result = options.parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, error); - if (!result) + try + { + options.parse_ini_file((util::core_file&)file, OPTION_PRIORITY_MAME_INI, OPTION_PRIORITY_MAME_INI < OPTION_PRIORITY_DRIVER_INI, true); + } + catch(options_error_exception &) { osd_printf_error("**Error loading %s.ini**\n", emulator_info::get_configname()); return; } + catch (options_exception &) + { + // ignore other exceptions related to options + } } } - for (emu_options::entry &f_entry : machine().options()) + for (const auto &f_entry : machine().options().entries()) { - if (f_entry.is_changed()) + const char *value = f_entry->value(); + if (value && strcmp(value, options.value(f_entry->name().c_str()))) { - options.set_value(f_entry.name(), f_entry.value(), OPTION_PRIORITY_CMDLINE, error_string); + options.set_value(f_entry->name(), *f_entry->value(), OPTION_PRIORITY_CMDLINE); } } diff --git a/src/lib/util/options.cpp b/src/lib/util/options.cpp index 2c56843947a..e989d6e4204 100644 --- a/src/lib/util/options.cpp +++ b/src/lib/util/options.cpp @@ -64,244 +64,479 @@ namespace } }; +//************************************************************************** +// 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 +// CORE OPTIONS ENTRY BASE CLASS //************************************************************************** //------------------------------------------------- // entry - constructor //------------------------------------------------- -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) +core_options::entry::entry(std::vector &&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) { - // copy in the name(s) as appropriate - if (name != nullptr) + assert(m_names.empty() == (m_type == option_type::HEADER)); +} + +core_options::entry::entry(std::string &&name, core_options::option_type type, const char *description) + : entry(std::vector({ std::move(name) }), type, description) +{ +} + + +//------------------------------------------------- +// entry - destructor +//------------------------------------------------- + +core_options::entry::~entry() +{ +} + + +//------------------------------------------------- +// 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()) { - // 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) + 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; + + switch (type()) + { + 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()) { - 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); + int minimum_integer = atoi(minimum()); + int 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()); } + break; - // then chop up any semicolon-separated names - int semi; - int nameindex = 0; - while ((semi = namestr.find_first_of(';')) != -1 && nameindex < ARRAY_LENGTH(m_name)) + 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()) { - m_name[nameindex++].assign(namestr.substr(0, semi)); - namestr.erase(0, semi + 1); + float minimum_float = atof(minimum()); + float 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()); } + break; - // finally add the last item - if (nameindex < ARRAY_LENGTH(m_name)) - m_name[nameindex++] = namestr; + 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()); } +} - // set the default value - if (defvalue != nullptr) - m_defdata = defvalue; - m_data = m_defdata; + +//------------------------------------------------- +// entry::minimum +//------------------------------------------------- + +const char *core_options::entry::minimum() const +{ + return nullptr; } //------------------------------------------------- -// set_value - update our data value +// entry::maximum //------------------------------------------------- -void core_options::entry::set_value(const char *newdata, int priority) +const char *core_options::entry::maximum() const { - // ignore if we don't have priority - if (priority < m_priority) - return; + return nullptr; +} + - // set the data and priority, then bump the sequence - m_data = newdata; - m_priority = priority; +//------------------------------------------------- +// entry::has_range +//------------------------------------------------- + +bool core_options::entry::has_range() const +{ + return minimum() && maximum(); } //------------------------------------------------- -// set_default_value - set the default value of -// an option, and reset the current value to it +// entry::default_value //------------------------------------------------- -void core_options::entry::set_default_value(const char *defvalue) +const std::string &core_options::entry::default_value() const { - m_data = defvalue; - m_defdata = defvalue; - m_priority = OPTION_PRIORITY_DEFAULT; + // I don't really want this generally available, but MewUI seems to need it. Please + // do not use + throw false; } +//************************************************************************** +// CORE OPTIONS SIMPLE ENTRYCLASS +//************************************************************************** + //------------------------------------------------- -// set_description - set the description of -// an option +// simple_entry - constructor //------------------------------------------------- -void core_options::entry::set_description(const char *description) +core_options::simple_entry::simple_entry(std::vector &&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)) { - m_description = description; + m_data = m_defdata; } -void core_options::entry::set_flag(uint32_t mask, uint32_t flag) +//------------------------------------------------- +// simple_entry - destructor +//------------------------------------------------- + +core_options::simple_entry::~simple_entry() { - m_flags = ( m_flags & mask ) | flag; } //------------------------------------------------- -// revert - revert back to our default if we are -// within the given priority range +// simple_entry::value //------------------------------------------------- -void core_options::entry::revert(int priority_hi, int priority_lo) +const char *core_options::simple_entry::value() const { - // if our priority is within the range, revert to the default - if (m_priority <= priority_hi && m_priority >= priority_lo) + const char *result; + switch (type()) { - m_data = m_defdata; - m_priority = OPTION_PRIORITY_DEFAULT; + 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: + // this is an option type for which returning a value is + // a meaningless operation (e.g. - core_options::option_type::COMMAND) + result = nullptr; + break; } + return result; } - -//************************************************************************** -// CORE OPTIONS -//************************************************************************** - //------------------------------------------------- -// core_options - constructor +// simple_entry::default_value //------------------------------------------------- -core_options::core_options() +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::core_options(const options_entry *entrylist) + +//------------------------------------------------- +// internal_set_value +//------------------------------------------------- + +void core_options::simple_entry::internal_set_value(std::string &&newvalue) { - add_entries(entrylist); + m_data = std::move(newvalue); } -core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2) + +//------------------------------------------------- +// set_default_value +//------------------------------------------------- + +void core_options::simple_entry::set_default_value(std::string &&newvalue) { - add_entries(entrylist1); - add_entries(entrylist2); + m_defdata = std::move(newvalue); } -core_options::core_options(const options_entry *entrylist1, const options_entry *entrylist2, const options_entry *entrylist3) + +//------------------------------------------------- +// minimum +//------------------------------------------------- + +const char *core_options::simple_entry::minimum() const { - add_entries(entrylist1); - add_entries(entrylist2); - add_entries(entrylist3); + return m_minimum.c_str(); } -core_options::core_options(const core_options &src) + +//------------------------------------------------- +// maximum +//------------------------------------------------- + +const char *core_options::simple_entry::maximum() const { - copyfrom(src); + return m_maximum.c_str(); } +//************************************************************************** +// CORE OPTIONS +//************************************************************************** + //------------------------------------------------- -// ~core_options - destructor +// core_options - constructor //------------------------------------------------- -core_options::~core_options() +core_options::core_options() { } //------------------------------------------------- -// operator= - assignment operator +// ~core_options - destructor //------------------------------------------------- -core_options &core_options::operator=(const core_options &rhs) +core_options::~core_options() { - // ignore self-assignment - if (this != &rhs) - copyfrom(rhs); - return *this; } //------------------------------------------------- -// operator== - compare two sets of options +// add_entry - adds an entry //------------------------------------------------- -bool core_options::operator==(const core_options &rhs) +void core_options::add_entry(entry::shared_ptr &&entry, const char *after_header) { - // 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; - } + // 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); + } - return true; + // and add the entry to the vector + m_entries.emplace_back(std::move(entry)); } //------------------------------------------------- -// operator!= - compare two sets of options +// add_to_entry_map - adds an entry to the entry +// map //------------------------------------------------- -bool core_options::operator!=(const core_options &rhs) +void core_options::add_to_entry_map(std::string &&name, entry::shared_ptr &entry) { - return !operator==(rhs); + // it is illegal to call this method for something that already ex0ists + assert(m_entrymap.find(name) == m_entrymap.end()); + + // append the entry + m_entrymap.emplace(std::make_pair(name, entry::weak_ptr(entry))); } //------------------------------------------------- -// add_entry - add an entry to the current -// options set +// add_entry - adds an entry based on an +// options_entry //------------------------------------------------- -void core_options::add_entry(const char *name, const char *description, uint32_t flags, const char *defvalue, bool override_existing) +void core_options::add_entry(const options_entry &opt, bool override_existing) { - // allocate a new entry - auto newentry = global_alloc(entry(name, description, flags, defvalue)); - if (newentry->name() != nullptr) + std::vector names; + std::string minimum, maximum; + + // copy in the name(s) as appropriate + if (opt.name) { - // see if we match an existing entry - auto checkentry = m_entrymap.find(newentry->name()); - if (checkentry != m_entrymap.end()) + // 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) { - entry *existing = checkentry->second; - // if we're overriding existing entries, then remove the old one - if (override_existing) - m_entrylist.remove(*existing); + 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); + } + + // 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)); + } - // otherwise, just override the default and current values and throw out the new entry + // we might be called with an existing entry + entry::shared_ptr existing_entry; + do + { + for (const std::string &name : names) + { + existing_entry = get_entry(name.c_str()); + if (existing_entry) + break; + } + + if (existing_entry) + { + if (override_existing) + remove_entry(*existing_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 &&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( + 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 us to the list and maps - append_entry(*newentry); +//------------------------------------------------- +// add_header +//------------------------------------------------- + +void core_options::add_header(const char *description) +{ + add_entry(std::vector(), description, option_type::HEADER); } @@ -313,8 +548,8 @@ void core_options::add_entry(const char *name, const char *description, uint32_t void core_options::add_entries(const options_entry *entrylist, bool override_existing) { // loop over entries until we hit a nullptr name - for ( ; entrylist->name != nullptr || (entrylist->flags & OPTION_HEADER) != 0; entrylist++) - add_entry(*entrylist, override_existing); + for (int i = 0; entrylist[i].name || entrylist[i].type == option_type::HEADER; i++) + add_entry(entrylist[i], override_existing); } @@ -325,13 +560,8 @@ 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 - curentry->second->set_default_value(defvalue); + get_entry(name)->set_default_value(defvalue); } @@ -342,13 +572,8 @@ 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 - curentry->second->set_description(description); + get_entry(name)->set_description(description); } @@ -357,10 +582,12 @@ void core_options::set_description(const char *name, const char *description) // command line arguments //------------------------------------------------- -bool core_options::parse_command_line(std::vector &args, int priority, std::string &error_string) +void core_options::parse_command_line(const std::vector &args, int priority, bool ignore_unknown_options) { + std::ostringstream error_stream; + condition_type condition = condition_type::NONE; + // reset the errors and the command - error_string.clear(); m_command.clear(); // we want to identify commands first @@ -368,23 +595,20 @@ bool core_options::parse_command_line(std::vector &args, int priori { if (!args[arg].empty() && args[arg][0] == '-') { - auto curentry = m_entrymap.find(&args[arg][1]); - if (curentry != m_entrymap.end() && curentry->second->type() == OPTION_COMMAND) + auto curentry = get_entry(&args[arg][1]); + if (curentry && curentry->type() == OPTION_COMMAND) { // can only have one command if (!m_command.empty()) - { - error_string.append(string_format("Error: multiple commands specified -%s and %s\n", m_command, args[arg])); - return false; - } - m_command = curentry->second->name(); + throw options_error_exception("Error: multiple commands specified -%s and %s\n", m_command, args[arg]); + + m_command = curentry->name(); } } } // iterate through arguments int unadorned_index = 0; - size_t new_argc = 1; for (size_t arg = 1; arg < args.size(); arg++) { // determine the entry name to search for @@ -396,39 +620,26 @@ bool core_options::parse_command_line(std::vector &args, int priori if (is_unadorned && !m_command.empty()) { m_command_arguments.push_back(std::move(args[arg])); - args[arg].clear(); + command_argument_processed(); continue; } // find our entry; if not found, continue - auto curentry = m_entrymap.find(optionname); - if (curentry == m_entrymap.end()) + auto curentry = get_entry(optionname); + if (!curentry) { - // we need to relocate this option - if (new_argc != arg) - args[new_argc] = std::move(args[arg]); - new_argc++; - - if (!is_unadorned) - { - arg++; - if (arg < args.size()) - { - if (new_argc != arg) - args[new_argc] = std::move(args[arg]); - new_argc++; - } - } + if (!ignore_unknown_options) + throw options_error_exception("Error: unknown option: -%s\n", optionname); continue; } // at this point, we've already processed commands - if (curentry->second->type() == OPTION_COMMAND) + if (curentry->type() == OPTION_COMMAND) continue; // get the data for this argument, special casing booleans std::string newdata; - if (curentry->second->type() == OPTION_BOOLEAN) + if (curentry->type() == option_type::BOOLEAN) { newdata = (strncmp(&curarg[1], "no", 2) == 0) ? "0" : "1"; } @@ -438,22 +649,19 @@ bool core_options::parse_command_line(std::vector &args, int priori } else if (arg + 1 < args.size()) { - args[arg++].clear(); - newdata = std::move(args[arg]); + newdata = args[++arg]; } else { - error_string.append(string_format("Error: option %s expected a parameter\n", curarg)); - return false; + throw options_error_exception("Error: option %s expected a parameter\n", curarg); } - args[arg].clear(); // set the new data - validate_and_set_data(*curentry->second, std::move(newdata), priority, error_string); + do_set_value(*curentry, std::move(newdata), priority, error_stream, condition); } - args.resize(new_argc); - return true; + // did we have any errors that may need to be aggregated? + throw_options_exception_if_appropriate(condition, error_stream); } @@ -462,8 +670,11 @@ bool core_options::parse_command_line(std::vector &args, int priori // an INI file //------------------------------------------------- -bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, std::string &error_string) +void core_options::parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, bool always_override) { + 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) @@ -487,7 +698,8 @@ bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool i // if we hit the end early, print a warning and continue if (*temp == 0) { - error_string.append(string_format("Warning: invalid line in INI: %s", buffer)); + condition = std::max(condition, condition_type::WARN); + util::stream_format(error_stream, "Warning: invalid line in INI: %s", buffer); continue; } @@ -507,79 +719,73 @@ bool core_options::parse_ini_file(util::core_file &inifile, int priority, bool i *temp = 0; // find our entry - auto curentry = m_entrymap.find(optionname); - if (curentry == m_entrymap.end()) + entry::shared_ptr curentry = get_entry(optionname); + if (!curentry) { if (!ignore_unknown_options) - error_string.append(string_format("Warning: unknown option in INI: %s\n", optionname)); + { + condition = std::max(condition, condition_type::WARN); + util::stream_format(error_stream, "Warning: unknown option in INI: %s\n", optionname); + } continue; } // set the new data std::string data = optiondata; trim_spaces_and_quotes(data); - validate_and_set_data(*curentry->second, std::move(data), priority, error_string); + do_set_value(*curentry, std::move(data), priority, error_stream, condition); } - return true; + + // did we have any errors that may need to be aggregated? + throw_options_exception_if_appropriate(condition, error_stream); } //------------------------------------------------- -// pluck_from_command_line - finds a specific -// value from within a command line +// throw_options_exception_if_appropriate //------------------------------------------------- -bool core_options::pluck_from_command_line(std::vector &args, const std::string &optionname, std::string &result) +void core_options::throw_options_exception_if_appropriate(core_options::condition_type condition, std::ostringstream &error_stream) { - // 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()); - - // build a vector with potential targets - std::vector targets; - const char *potential_target; - int index = 0; - while ((potential_target = curentry->second->name(index++)) != nullptr) + switch(condition) { - // not supporting unadorned options for now - targets.push_back(std::string("-") + potential_target); - } + case condition_type::NONE: + // do nothing + break; - // 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]); + case condition_type::WARN: + throw options_warning_exception(error_stream.str()); - // remove this arguments from the list - auto const pos = std::next(args.begin(), i); - args.erase(pos, std::next(pos, 2)); - return true; - } - } + case condition_type::ERR: + throw options_error_exception(error_stream.str()); - result.clear(); - return false; + default: + // should not get here + throw false; + } } //------------------------------------------------- -// revert - revert options at or below a certain -// priority back to their defaults +// copy_from //------------------------------------------------- -void core_options::revert(int priority_hi, int priority_lo) +void core_options::copy_from(const core_options &that) { - // iterate over options and revert to defaults if below the given priority - for (entry &curentry : m_entrylist) - curentry.revert(priority_hi, 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); + } + } + } } @@ -600,47 +806,34 @@ std::string core_options::output_ini(const core_options *diff) const std::string overridden_value; // loop over all items - for (entry &curentry : m_entrylist) + for (auto &curentry : m_entries) { - const char *name = curentry.name(); - const char *value; - switch (override_get_value(name, overridden_value)) + if (curentry->type() == option_type::HEADER) { - 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; + // header: record description + last_header = curentry->description(); } - bool is_unadorned = false; - - // check if it's unadorned - if (name && strlen(name) && !strcmp(name, core_options::unadorned(unadorned_index))) + else { - unadorned_index++; - is_unadorned = true; - } + const std::string &name(curentry->name()); + const char *value(curentry->value()); - // header: record description - if (curentry.is_header()) - last_header = curentry.description(); + // check if it's unadorned + bool is_unadorned = false; + if (name == core_options::unadorned(unadorned_index)) + { + unadorned_index++; + is_unadorned = true; + } - // otherwise, output entries for all non-command items - else if (!curentry.is_command()) - { - if (!curentry.is_internal()) + // output entries for all non-command items (items with value) + if (value) { // look up counterpart in diff, if diff is specified - if (diff == nullptr || strcmp(value, diff->value(name)) != 0) + if (!diff || strcmp(value, diff->value(name.c_str()))) { // output header, if we have one - if (last_header != nullptr) + if (last_header) { if (num_valid_headers++) buffer << '\n'; @@ -651,7 +844,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, ' ') != nullptr) + if (strchr(value, ' ')) util::stream_format(buffer, "%-25s \"%s\"\n", name, value); else util::stream_format(buffer, "%-25s %s\n", name, value); @@ -674,15 +867,15 @@ std::string core_options::output_help() const std::ostringstream buffer; // loop over all items - for (entry &curentry : m_entrylist) + for (auto &curentry : m_entries) { // header: just print - if (curentry.is_header()) - util::stream_format(buffer, "\n#\n# %s\n#\n", curentry.description()); + if (curentry->type() == option_type::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(); } @@ -692,10 +885,9 @@ std::string core_options::output_help() const // value - return the raw option value //------------------------------------------------- -const char *core_options::value(const char *name) const +const char *core_options::value(const char *option) const { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->value() : ""; + return get_entry(option)->value(); } @@ -703,241 +895,133 @@ const char *core_options::value(const char *name) const // description - return description of option //------------------------------------------------- -const char *core_options::description(const char *name) const +const char *core_options::description(const char *option) const { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->description() : ""; + return get_entry(option)->description(); } +//************************************************************************** +// LEGACY +//************************************************************************** + //------------------------------------------------- -// priority - return the priority of option +// set_value - set the raw option value //------------------------------------------------- -int core_options::priority(const char *name) const +void core_options::set_value(const std::string &name, const std::string &value, int priority) { - auto curentry = m_entrymap.find(name); - return (curentry != m_entrymap.end()) ? curentry->second->priority() : 0; + set_value(name, std::string(value), priority); } +void core_options::set_value(const std::string &name, std::string &&value, int priority) +{ + get_entry(name)->set_value(std::move(value), priority); +} -//------------------------------------------------- -// exists - return if option exists in list -//------------------------------------------------- +void core_options::set_value(const std::string &name, int value, int priority) +{ + set_value(name, string_format("%d", value), priority); +} -bool core_options::exists(const char *name) const +void core_options::set_value(const std::string &name, float value, int priority) { - return (m_entrymap.find(name) != m_entrymap.end()); + set_value(name, string_format("%f", value), priority); } //------------------------------------------------- -// set_value - set the raw option value +// remove_entry - remove an entry from our list +// and map //------------------------------------------------- -bool core_options::set_value(const char *name, const char *value, int priority, std::string &error_string) -{ - // 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; - } - - // validate and set the item normally - return validate_and_set_data(*curentry->second, value, priority, error_string); -} - -bool core_options::set_value(const char *name, int value, int priority, std::string &error_string) +void core_options::remove_entry(core_options::entry &delentry) { - return set_value(name, string_format("%d", value).c_str(), priority, 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()); + + // erase each of the items out of the entry map + for (const std::string &name : delentry.names()) + m_entrymap.erase(name); + + // finally erase it + m_entries.erase(iter); } -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); -} +//------------------------------------------------- +// do_set_value +//------------------------------------------------- -void core_options::set_flag(const char *name, uint32_t mask, uint32_t flag) +void core_options::do_set_value(entry &curentry, std::string &&data, int priority, std::ostream &error_stream, condition_type &condition) { - // find the entry first - auto curentry = m_entrymap.find(name); - if ( curentry == m_entrymap.end()) + // 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 { - return; + curentry.set_value(std::move(data), priority); } - curentry->second->set_flag(mask, flag); -} - -void core_options::mark_changed(const char* name) -{ - // find the entry first - auto curentry = m_entrymap.find(name); - if (curentry == m_entrymap.end()) + catch (options_warning_exception &ex) { - return; + // we want to aggregate option exceptions + error_stream << ex.message(); + condition = std::max(condition, condition_type::WARN); + } + catch (options_error_exception &ex) + { + // we want to aggregate option exceptions + error_stream << ex.message(); + condition = std::max(condition, condition_type::ERR); } - curentry->second->mark_changed(); } + //------------------------------------------------- -// reset - reset the options state, removing -// everything +// get_entry //------------------------------------------------- -void core_options::reset() +const core_options::entry::shared_ptr core_options::get_entry(const std::string &name) const +{ + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; +} + +core_options::entry::shared_ptr core_options::get_entry(const std::string &name) { - m_entrylist.reset(); - m_entrymap.clear(); + auto curentry = m_entrymap.find(name); + return (curentry != m_entrymap.end()) ? curentry->second.lock() : nullptr; } //------------------------------------------------- -// append_entry - append an entry to our list -// and index it in the map +// set_value_changed_handler //------------------------------------------------- -void core_options::append_entry(core_options::entry &newentry) +void core_options::set_value_changed_handler(const std::string &name, std::function &&handler) { - 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)); - } + get_entry(name)->set_value_changed_handler(std::move(handler)); } //------------------------------------------------- -// remove_entry - remove an entry from our list -// and map +// header_exists //------------------------------------------------- -void core_options::remove_entry(core_options::entry &delentry) +bool core_options::header_exists(const char *description) const { - // 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. - */ - -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()))); -} - -/** - * @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) -{ - // 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()))) + auto iter = std::find_if( + m_entries.begin(), + m_entries.end(), + [description](const auto &entry) { - 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 entry->type() == option_type::HEADER + && entry->description() + && !strcmp(entry->description(), description); + }); - // 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; + return iter != m_entries.end(); } diff --git a/src/lib/util/options.h b/src/lib/util/options.h index 71431ce870c..533f5cf4628 100644 --- a/src/lib/util/options.h +++ b/src/lib/util/options.h @@ -13,26 +13,13 @@ #include "corefile.h" #include - +#include //************************************************************************** // 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 @@ -40,20 +27,55 @@ 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 //************************************************************************** -// static structure describing a single option with its description and default value -struct options_entry +struct options_entry; + +// exception thrown by core_options when an illegal request is made +class options_exception : public std::exception { - 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 +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 options_warning_exception(const char *fmt, Params &&...args) + : options_warning_exception(util::string_format(fmt, std::forward(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 +{ +public: + template options_error_exception(const char *fmt, Params &&...args) + : options_error_exception(util::string_format(fmt, std::forward(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; }; @@ -63,95 +85,97 @@ 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 { - friend class core_options; - friend class simple_list; - - // construction/destruction - entry(const char *name, const char *description, uint32_t flags = 0, const char *defvalue = nullptr); - 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; } + typedef std::shared_ptr shared_ptr; + typedef std::weak_ptr weak_ptr; + + // constructor/destructor + entry(std::vector &&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 &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; } + void set_priority(int priority) { m_priority = priority; } + option_type type() const { return m_type; } 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); + virtual const std::string &default_value() const; + virtual const char *minimum() const; + virtual const char *maximum() const; + bool has_range() const; + + // 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 &&handler) { m_value_changed_handler = std::move(handler); } + + protected: + virtual void internal_set_value(std::string &&newvalue) = 0; private: - // 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 + void validate(const std::string &value); + + std::vector m_names; + int m_priority; + core_options::option_type m_type; + const char * m_description; + std::function m_value_changed_handler; }; // construction/destruction core_options(); - 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); + core_options(const core_options &) = delete; + core_options(core_options &&) = delete; + core_options& operator=(const core_options &) = delete; + core_options& operator=(core_options &&) = delete; 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 std::vector &command_arguments() const { assert(!m_command.empty()); return m_command_arguments; } - entry *get_entry(const char *name) const; - - // range iterators - using auto_iterator = simple_list::auto_iterator; - auto_iterator begin() const { return m_entrylist.begin(); } - auto_iterator end() const { return m_entrylist.end(); } + const entry::shared_ptr get_entry(const std::string &name) const; + entry::shared_ptr get_entry(const std::string &name); + const std::vector &entries() const { return m_entries; } + bool exists(const std::string &name) const { return get_entry(name) != nullptr; } + bool header_exists(const char *description) const; // configuration - 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_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 &&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_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 &&handler); // parsing/input - bool parse_command_line(std::vector &args, int priority, std::string &error_string); - bool parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, std::string &error_string); - bool pluck_from_command_line(std::vector &args, const std::string &name, std::string &result); - - // reverting - void revert(int priority_hi = OPTION_PRIORITY_MAXIMUM, int priority_lo = OPTION_PRIORITY_DEFAULT); + void parse_command_line(const std::vector &args, int priority, bool ignore_unknown_options = false); + void parse_ini_file(util::core_file &inifile, int priority, bool ignore_unknown_options, bool always_override); + void copy_from(const core_options &that); // output std::string output_ini(const core_options *diff = nullptr) const; @@ -160,52 +184,92 @@ public: // reading const char *value(const char *option) const; const char *description(const char *option) const; - 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; + 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)); } // setting - 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); + 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); // 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(); } 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 + virtual void command_argument_processed() { } + +private: + class simple_entry : public entry { - NONE, - OVERRIDE, - SKIP + public: + // construction/destruction + simple_entry(std::vector &&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 }; - 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; } + // used internally in core_options + enum class condition_type + { + NONE, + WARN, + ERR + }; -private: // internal helpers - 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); + void add_to_entry_map(std::string &&name, entry::shared_ptr &entry); + void do_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); // internal state - simple_list m_entrylist; // head of list of entries - std::unordered_map m_entrymap; // map for fast lookup - std::string m_command; // command found - std::vector m_command_arguments; // command arguments - static const char *const s_option_unadorned[]; // array of unadorned option "names" + std::vector m_entries; // cannonical list of entries + std::unordered_map m_entrymap; // map for fast lookup + std::string m_command; // command found + std::vector m_command_arguments; // command arguments + 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 }; +// 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 df8096840cf..2c2f750fdbe 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.value(opt_name.c_str()); + std::string opt_val = opts.exists(opt_name) ? 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 2fb0d0b3a1e..31ddc58c145 100644 --- a/src/osd/modules/render/drawbgfx.cpp +++ b/src/osd/modules/render/drawbgfx.cpp @@ -85,7 +85,6 @@ renderer_bgfx::renderer_bgfx(std::shared_ptr w) , m_avi_writer(nullptr) , m_avi_target(nullptr) { - m_options = downcast(assert_window()->machine().options()); } //============================================================ diff --git a/src/osd/sdl/sdlmain.cpp b/src/osd/sdl/sdlmain.cpp index 31f65e2ba66..10e57ec5766 100644 --- a/src/osd/sdl/sdlmain.cpp +++ b/src/osd/sdl/sdlmain.cpp @@ -408,14 +408,12 @@ 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, 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); + 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); } // Some driver options - must be before audio init! diff --git a/src/osd/windows/winmain.cpp b/src/osd/windows/winmain.cpp index efec6700849..805a769d1b8 100644 --- a/src/osd/windows/winmain.cpp +++ b/src/osd/windows/winmain.cpp @@ -515,23 +515,20 @@ 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, 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()); + 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); } // 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, error_string); - options.set_value(OSDOPTION_NUMPROCESSORS, 1, OPTION_PRIORITY_MAXIMUM, error_string); - assert(error_string.empty()); + options.set_value(OPTION_THROTTLE, false, OPTION_PRIORITY_MAXIMUM); + options.set_value(OSDOPTION_NUMPROCESSORS, 1, OPTION_PRIORITY_MAXIMUM); } // thread priority -- cgit v1.2.3