Refactoring of settings load/save functionality

- commandline parsing is now done in main function
- loading of settings is handled by OSystem
- settings that are used only in main are not accessible globally
- beginnings of converting Settings class to use map instead of linear arrays
This commit is contained in:
Stephen Anthony 2019-02-20 23:43:29 -03:30
parent 092c32cda5
commit 904821cff9
6 changed files with 173 additions and 129 deletions

View File

@ -55,16 +55,16 @@ class Variant
Variant(const GUI::Size& s) { buf().str(""); buf() << s; data = buf().str(); } Variant(const GUI::Size& s) { buf().str(""); buf() << s; data = buf().str(); }
// Conversion methods // Conversion methods
const string& toString() const { return data; } const string& toString() const { return data; }
const char* const toCString() const { return data.c_str(); } const char* const toCString() const { return data.c_str(); }
const Int32 toInt() const { const Int32 toInt() const {
istringstream ss(data); istringstream ss(data);
Int32 parsed; Int32 parsed;
ss >> parsed; ss >> parsed;
return parsed; return parsed;
} }
const float toFloat() const { const float toFloat() const {
istringstream ss(data); istringstream ss(data);
float parsed; float parsed;
ss >> parsed; ss >> parsed;
@ -72,7 +72,7 @@ class Variant
return parsed; return parsed;
} }
const bool toBool() const { return data == "1" || data == "true"; } const bool toBool() const { return data == "1" || data == "true"; }
const GUI::Size toSize() const { return GUI::Size(data); } const GUI::Size toSize() const { return GUI::Size(data); }
// Comparison // Comparison
bool operator==(const Variant& v) const { return data == v.data; } bool operator==(const Variant& v) const { return data == v.data; }

View File

@ -42,24 +42,103 @@
#include "CheatManager.hxx" #include "CheatManager.hxx"
#endif #endif
/**
Parse the commandline arguments and store into the appropriate hashmap.
Keys without a corresponding value are assumed to be boolean, and set to true.
Some keys are used only by the main function; these are placed in localOpts.
The rest are needed globally, and are placed in globalOpts.
*/
void parseCommandLine(int ac, char* av[],
Settings::Options& globalOpts, Settings::Options& localOpts);
/** /**
Checks the commandline for special settings that are used by various ports Checks the commandline for special settings that are used by various ports
to use a specific 'base directory'. to use a specific 'base directory'.
This needs to be done separately from the main commandline and settings This needs to be done separately, before either an OSystem or Settings
functionality, since they both depend on the settings file being already object can be created, since they both depend on each other, and a
available. However, since a variabe basedir implies a different location variable basedir implies a different location for the settings file.
for the settings file, it *must* be processed first.
This function will set the environment variables STELLA_BASEDIR and This function will call OSystem::overrideBaseDir() when either of the
STELLA_USECURRENTASBASEDIR; it is up to each port to use these as they wish. applicable arguments are found, and then remove them from the argument
list.
*/ */
void checkForCustomBaseDir(int ac, char* av[]); void checkForCustomBaseDir(Settings::Options& options);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void checkForCustomBaseDir(int ac, char* av[]) void parseCommandLine(int ac, char* av[],
Settings::Options& globalOpts, Settings::Options& localOpts)
{ {
localOpts["ROMFILE"] = ""; // make sure we have an entry for this
for(int i = 1; i < ac; ++i)
{
string key = av[i];
if(key[0] == '-')
{
key = key.substr(1);
// Certain options are used only in the main function
// We detect these now, and remove them from further consideration
if(key == "help" || key == "listrominfo" || key == "rominfo" || key == "takesnapshot")
{
localOpts[key] = true;
continue;
}
// Take care of arguments without an option that are needed outside
// be saved to the config file
if(key == "debug" || key == "holdselect" || key == "holdreset")
{
globalOpts[key] = true;
continue;
}
// Some ports have the ability to override the base directory where all
// configuration files are stored; we check for those next
if(key == "baseinappdir")
{
globalOpts[key] = true;
continue;
}
if(++i >= ac)
{
cerr << "Missing argument for '" << key << "'" << endl;
continue;
}
if(key == "basedir" || key == "break")
localOpts[key] = av[i];
else
globalOpts[key] = av[i];
}
else
localOpts["ROMFILE"] = key;
}
#if 0
cout << "Global opts:" << endl;
for(const auto& x: globalOpts)
cout << " -> " << x.first << ": " << x.second << endl;
cout << "Local opts:" << endl;
for(const auto& x: localOpts)
cout << " -> " << x.first << ": " << x.second << endl;
#endif
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void checkForCustomBaseDir(Settings::Options& options)
{
// If both of these are activated, the 'base in app dir' takes precedence
auto it = options.find("baseinappdir");
if(it != options.end())
OSystem::overrideBaseDir();
else
{
it = options.find("basedir");
if(it != options.end())
OSystem::overrideBaseDir(it->second.toString());
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -73,34 +152,32 @@ int main(int ac, char* av[])
std::ios_base::sync_with_stdio(false); std::ios_base::sync_with_stdio(false);
// Check for custom base directory unique_ptr<OSystem> theOSystem;
// This needs to be done separately from the normal startup, since it
// queries information from the commandline that both OSystem and Settings
// need *before* they are created
checkForCustomBaseDir(ac, av);
// Create the parent OSystem object
unique_ptr<OSystem> theOSystem = MediaFactory::createOSystem();
theOSystem->loadConfig();
theOSystem->logMessage("Loading config options ...", 2);
auto Cleanup = [&theOSystem]() { auto Cleanup = [&theOSystem]() {
theOSystem->logMessage("Cleanup from main", 2); if(theOSystem)
theOSystem->saveConfig(); {
theOSystem.reset(); // Force delete of object theOSystem->logMessage("Cleanup from main", 2);
theOSystem->saveConfig();
theOSystem.reset(); // Force delete of object
}
MediaFactory::cleanUp(); // Finish any remaining cleanup MediaFactory::cleanUp(); // Finish any remaining cleanup
return 0; return 0;
}; };
// Take care of commandline arguments // Parse the commandline arguments
theOSystem->logMessage("Loading commandline arguments ...", 2); // They are placed in different maps depending on whether they're used
string romfile = theOSystem->settings().loadCommandLine(ac, av); // locally or globally
Settings::Options globalOpts, localOpts;
parseCommandLine(ac, av, globalOpts, localOpts);
// Finally, make sure the settings are valid // Check for custom base directory; some ports make use of this
// We do it once here, so the rest of the program can assume valid settings checkForCustomBaseDir(localOpts);
theOSystem->logMessage("Validating config options ...", 2);
theOSystem->settings().validate(); // Create the parent OSystem object and initialize settings
theOSystem = MediaFactory::createOSystem();
theOSystem->loadConfig(globalOpts);
// Create the full OSystem after the settings, since settings are // Create the full OSystem after the settings, since settings are
// probably needed for defaults // probably needed for defaults
@ -114,13 +191,14 @@ int main(int ac, char* av[])
// Check to see if the user requested info about a specific ROM, // Check to see if the user requested info about a specific ROM,
// or the list of internal ROMs // or the list of internal ROMs
// If so, show the information and immediately exit // If so, show the information and immediately exit
if(theOSystem->settings().getBool("listrominfo")) string romfile = localOpts["ROMFILE"].toString();
if(localOpts["listrominfo"].toBool())
{ {
theOSystem->logMessage("Showing output from 'listrominfo' ...", 2); theOSystem->logMessage("Showing output from 'listrominfo' ...", 2);
theOSystem->propSet().print(); theOSystem->propSet().print();
return Cleanup(); return Cleanup();
} }
else if(theOSystem->settings().getBool("rominfo")) else if(localOpts["rominfo"].toBool())
{ {
theOSystem->logMessage("Showing output from 'rominfo' ...", 2); theOSystem->logMessage("Showing output from 'rominfo' ...", 2);
FilesystemNode romnode(romfile); FilesystemNode romnode(romfile);
@ -128,7 +206,7 @@ int main(int ac, char* av[])
return Cleanup(); return Cleanup();
} }
else if(theOSystem->settings().getBool("help")) else if(localOpts["help"].toBool())
{ {
theOSystem->logMessage("Displaying usage", 2); theOSystem->logMessage("Displaying usage", 2);
theOSystem->settings().usage(); theOSystem->settings().usage();
@ -165,7 +243,7 @@ int main(int ac, char* av[])
#if 0 #if 0
TODO: Fix this to use functionality from OSystem::mainLoop TODO: Fix this to use functionality from OSystem::mainLoop
if(theOSystem->settings().getBool("takesnapshot")) if(localOpts["takesnapshot"].toBool())
{ {
for(int i = 0; i < 30; ++i) theOSystem->frameBuffer().update(); for(int i = 0; i < 30; ++i) theOSystem->frameBuffer().update();
// theOSystem->frameBuffer().tiaSurface().saveSnapShot(); // theOSystem->frameBuffer().tiaSurface().saveSnapShot();
@ -182,14 +260,11 @@ int main(int ac, char* av[])
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
// Set up any breakpoint that was on the command line // Set up any breakpoint that was on the command line
// (and remove the key from the settings, so they won't get set again) if(localOpts["break"].toString() != "")
const string& initBreak = theOSystem->settings().getString("break");
if(initBreak != "")
{ {
Debugger& dbg = theOSystem->debugger(); Debugger& dbg = theOSystem->debugger();
uInt16 bp = uInt16(dbg.stringToValue(initBreak)); uInt16 bp = uInt16(dbg.stringToValue(localOpts["break"].toString()));
dbg.setBreakPoint(bp, true); dbg.setBreakPoint(bp, true);
theOSystem->settings().setValue("break", "");
} }
#endif #endif
} }

View File

@ -167,9 +167,17 @@ bool OSystem::create()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void OSystem::loadConfig() void OSystem::loadConfig(const Settings::Options& options)
{ {
logMessage("Loading config options ...", 2);
mySettings->loadConfig(); mySettings->loadConfig();
logMessage("Applying commandline config options ...", 2);
for(const auto& opt: options)
mySettings->setValue(opt.first, opt.second);
logMessage("Validating config options ...", 2);
mySettings->validate();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -215,6 +223,7 @@ void OSystem::setConfigPaths()
myPaletteFile = FilesystemNode(myBaseDir + "stella.pal").getPath(); myPaletteFile = FilesystemNode(myBaseDir + "stella.pal").getPath();
myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro").getPath(); myPropertiesFile = FilesystemNode(myBaseDir + "stella.pro").getPath();
#if 0
// TODO - remove this // TODO - remove this
auto dbgPath = [](const string& desc, const string& location) auto dbgPath = [](const string& desc, const string& location)
{ {
@ -230,6 +239,7 @@ void OSystem::setConfigPaths()
dbgPath("pal file ", myPaletteFile); dbgPath("pal file ", myPaletteFile);
dbgPath("pro file ", myPropertiesFile); dbgPath("pro file ", myPropertiesFile);
dbgPath("INI file ", myConfigFile); dbgPath("INI file ", myConfigFile);
#endif
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -722,3 +732,6 @@ void OSystem::mainLoop()
myCheatManager->saveCheatDatabase(); myCheatManager->saveCheatDatabase();
#endif #endif
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string OSystem::ourOverrideBaseDir = "";

View File

@ -34,7 +34,6 @@ class Properties;
class PropertiesSet; class PropertiesSet;
class Random; class Random;
class SerialPort; class SerialPort;
class Settings;
class Sound; class Sound;
class StateManager; class StateManager;
class TimerManager; class TimerManager;
@ -48,6 +47,7 @@ class AudioSettings;
#include "FrameBufferConstants.hxx" #include "FrameBufferConstants.hxx"
#include "EventHandlerConstants.hxx" #include "EventHandlerConstants.hxx"
#include "FpsMeter.hxx" #include "FpsMeter.hxx"
#include "Settings.hxx"
#include "bspf.hxx" #include "bspf.hxx"
/** /**
@ -188,16 +188,16 @@ class OSystem
PNGLibrary& png() const { return *myPNGLib; } PNGLibrary& png() const { return *myPNGLib; }
/** /**
This method should be called to load the current settings from an rc file. This method should be called to initiate the process of loading settings
It first loads the settings from the config file, then informs subsystems from the config file. It takes care of loading settings, applying
about the new settings. commandline overrides, and finally validating all settings.
*/ */
void loadConfig(); void loadConfig(const Settings::Options& options);
/** /**
This method should be called to save the current settings to an rc file. This method should be called to save the current settings. It first asks
It first asks each subsystem to update its settings, then it saves all each subsystem to update its settings, then it saves all settings to the
settings to the config file. config file.
*/ */
void saveConfig(); void saveConfig();
@ -382,9 +382,23 @@ class OSystem
/** /**
Reset FPS measurement. Reset FPS measurement.
*/ */
void resetFps(); void resetFps();
/**
Attempt to override the base directory that will be used by derived
classes, and use this one instead. Note that this is only a hint;
derived classes are free to ignore this, as some can't use an
alternate base directory.
If the default path is used, this indicates to attempt to use the
application directory directly. Again, this is not supported on
all systems, so it may be simply ignored.
*/
static void overrideBaseDir(const string& path = "USE_APP_DIR") {
ourOverrideBaseDir = path;
}
public: public:
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// The following methods are system-specific and can be overrided in // The following methods are system-specific and can be overrided in
@ -505,6 +519,11 @@ class OSystem
// Indicates whether to stop the main loop // Indicates whether to stop the main loop
bool myQuitLoop; bool myQuitLoop;
// If not empty, a hint for derived classes to use this as the
// base directory (where all settings are stored)
// Derived classes are free to ignore it and use their own defaults
static string ourOverrideBaseDir;
private: private:
string myBaseDir; string myBaseDir;
string myStateDir; string myStateDir;

View File

@ -232,68 +232,6 @@ void Settings::loadConfig()
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Settings::loadCommandLine(int argc, char** argv)
{
for(int i = 1; i < argc; ++i)
{
// strip off the '-' character
string key = argv[i];
if(key[0] == '-')
{
key = key.substr(1, key.length());
// Take care of the arguments which are meant to be executed immediately
// (and then Stella should exit)
if(key == "help" || key == "listrominfo")
{
setExternal(key, "true");
return "";
}
// Take care of arguments without an option or ones that shouldn't
// be saved to the config file
if(key == "rominfo" || key == "debug" || key == "holdreset" ||
key == "holdselect" || key == "takesnapshot")
{
setExternal(key, "true");
continue;
}
ostringstream buf;
if(++i >= argc)
{
buf << "Missing argument for '" << key << "'" << endl;
myOSystem.logMessage(buf.str(), 0);
return "";
}
string value = argv[i];
buf.str("");
buf << " key = '" << key << "', value = '" << value << "'";
// Settings read from the commandline must not be saved to
// the rc-file, unless they were previously set
if(int idx = getInternalPos(key) != -1)
{
setInternal(key, value, idx); // don't set initialValue here
buf << "(I)\n";
}
else
{
setExternal(key, value);
buf << "(E)\n";
}
myOSystem.logMessage(buf.str(), 2);
}
else
return key;
}
return EmptyString;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Settings::validate() void Settings::validate()
{ {

View File

@ -20,6 +20,8 @@
class OSystem; class OSystem;
#include <map>
#include "Variant.hxx" #include "Variant.hxx"
#include "bspf.hxx" #include "bspf.hxx"
@ -39,20 +41,9 @@ class Settings
explicit Settings(OSystem& osystem); explicit Settings(OSystem& osystem);
virtual ~Settings() = default; virtual ~Settings() = default;
using Options = std::map<string, Variant>;
public: public:
/**
This method should be called to load the arguments from the commandline.
@return Name of the ROM to load, otherwise empty string
*/
string loadCommandLine(int argc, char** argv);
/**
This method should be called *after* settings have been read,
to validate (and change, if necessary) any improper settings.
*/
void validate();
/** /**
This method should be called to display usage information. This method should be called to display usage information.
*/ */
@ -88,15 +79,23 @@ class Settings
protected: protected:
/** /**
This method will be called to load the current settings from an rc file. This method will be called to load the settings from the
platform-specific settings file.
*/ */
virtual void loadConfig(); virtual void loadConfig();
/** /**
This method will be called to save the current settings to an rc file. This method will be called to save the current settings to the
platform-specific settings file.
*/ */
virtual void saveConfig(); virtual void saveConfig();
/**
This method must be called *after* settings have been fully loaded
to validate (and change, if necessary) any improper settings.
*/
void validate();
// Trim leading and following whitespace from a string // Trim leading and following whitespace from a string
static string trim(const string& str) static string trim(const string& str)
{ {