From 27dc49a1357c2078d2305f0859efd3ccc3de8639 Mon Sep 17 00:00:00 2001 From: scrawl Date: Tue, 3 Feb 2015 00:53:30 +0100 Subject: [PATCH] Rewrite game settings manager Removes the abhorrent dependency on Ogre for this code and improves the error handling. --- apps/openmw/engine.cpp | 6 +- apps/openmw/mwbase/inputmanager.hpp | 4 +- apps/openmw/mwbase/soundmanager.hpp | 4 +- apps/openmw/mwbase/windowmanager.hpp | 2 +- apps/openmw/mwbase/world.hpp | 3 +- components/settings/settings.cpp | 224 +++++++++++++++------------ components/settings/settings.hpp | 13 +- 7 files changed, 137 insertions(+), 119 deletions(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 859baebe4..a9a7f0632 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -292,14 +292,10 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) else throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); - // load user settings if they exist, otherwise just load the default settings as user settings + // load user settings if they exist const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); - else if (boost::filesystem::exists(localdefault)) - settings.loadUser(localdefault); - else if (boost::filesystem::exists(globaldefault)) - settings.loadUser(globaldefault); mFpsLevel = settings.getInt("fps", "HUD"); diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index 11eb1721f..a53d8d9dc 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -2,6 +2,8 @@ #define GAME_MWBASE_INPUTMANAGER_H #include +#include +#include namespace MWBase { @@ -27,7 +29,7 @@ namespace MWBase virtual void changeInputMode(bool guiMode) = 0; - virtual void processChangedSettings(const std::vector< std::pair >& changed) = 0; + virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void setDragDrop(bool dragDrop) = 0; diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index a4c08f06f..f3381a8fd 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -2,7 +2,7 @@ #define GAME_MWBASE_SOUNDMANAGER_H #include - +#include #include #include "../mwworld/ptr.hpp" @@ -72,7 +72,7 @@ namespace MWBase virtual ~SoundManager() {} - virtual void processChangedSettings(const std::vector< std::pair >& settings) = 0; + virtual void processChangedSettings(const std::set< std::pair >& settings) = 0; virtual void stopMusic() = 0; ///< Stops music if it's playing diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index fc745d3e6..c984b5e54 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -271,7 +271,7 @@ namespace MWBase */ virtual std::string getGameSettingString(const std::string &id, const std::string &default_) = 0; - virtual void processChangedSettings(const std::vector< std::pair >& changed) = 0; + virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void windowResized(int x, int y) = 0; diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 3af2a2775..154d96f7d 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -386,7 +387,7 @@ namespace MWBase virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location - virtual void processChangedSettings (const std::vector< std::pair >& settings) = 0; + virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 5fc2ca3c1..a9a78d035 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,95 +1,144 @@ #include "settings.hpp" #include -#include -#include -#include #include -#include -#include - -using namespace Settings; +#include +#include +#include -namespace bfs = boost::filesystem; +namespace Settings +{ -Ogre::ConfigFile Manager::mFile = Ogre::ConfigFile(); -Ogre::ConfigFile Manager::mDefaultFile = Ogre::ConfigFile(); +CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); +CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); -CategorySettingValueMap Manager::mNewSettings = CategorySettingValueMap(); - -void Manager::loadUser (const std::string& file) -{ - Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); - mFile.load(stream); -} -void Manager::loadDefault (const std::string& file) -{ - Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); - mDefaultFile.load(stream); -} -void Manager::saveUser(const std::string& file) +class SettingsFileParser { - bfs::ofstream fout((bfs::path(file))); +public: + SettingsFileParser() : mLine(0) {} - Ogre::ConfigFile::SectionIterator seci = mFile.getSectionIterator(); - - while (seci.hasMoreElements()) + void loadSettingsFile (const std::string& file, CategorySettingValueMap& settings) { - Ogre::String sectionName = seci.peekNextKey(); + mFile = file; + boost::filesystem::ifstream stream; + stream.open(boost::filesystem::path(file)); + std::string currentCategory; + mLine = 0; + while (!stream.eof() && !stream.fail()) + { + ++mLine; + std::string line; + std::getline( stream, line ); - if (sectionName.length() > 0) - fout << '\n' << '[' << seci.peekNextKey() << ']' << '\n'; + size_t i = 0; + if (!skipWhiteSpace(i, line)) + continue; - Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); - Ogre::ConfigFile::SettingsMultiMap::iterator i; - for (i = settings->begin(); i != settings->end(); ++i) - { - fout << i->first.c_str() << " = " << i->second.c_str() << '\n'; - } + if (line[i] == '#') // skip comment + continue; - CategorySettingValueMap::iterator it = mNewSettings.begin(); - while (it != mNewSettings.end()) - { - if (it->first.first == sectionName) + if (line[i] == '[') { - fout << it->first.second << " = " << it->second << '\n'; - mNewSettings.erase(it++); + size_t end = line.find(']', i); + if (end == std::string::npos) + fail("unterminated category"); + + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + i = end+1; } - else - ++it; + + if (!skipWhiteSpace(i, line)) + continue; + + if (currentCategory.empty()) + fail("empty category name"); + + size_t settingEnd = line.find('=', i); + if (settingEnd == std::string::npos) + fail("unterminated setting name"); + + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) + fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } } - std::string category = ""; - for (CategorySettingValueMap::iterator it = mNewSettings.begin(); - it != mNewSettings.end(); ++it) +private: + /// Increment i until it longer points to a whitespace character + /// in the string or has reached the end of the string. + /// @return false if we have reached the end of the string + bool skipWhiteSpace(size_t& i, std::string& str) { - if (category != it->first.first) + while (i < str.size() && std::isspace(str[i], std::locale::classic())) { - category = it->first.first; - fout << '\n' << '[' << category << ']' << '\n'; + ++i; } - fout << it->first.second << " = " << it->second << '\n'; + return i < str.size(); } - fout.close(); + void fail(const std::string& message) + { + std::stringstream error; + error << "Error on line " << mLine << " in " << mFile << ":\n" << message; + throw std::runtime_error(error.str()); + } + + std::string mFile; + int mLine; +}; + +void Manager::loadDefault(const std::string &file) +{ + SettingsFileParser parser; + parser.loadSettingsFile(file, mDefaultSettings); } -std::string Manager::getString (const std::string& setting, const std::string& category) +void Manager::loadUser(const std::string &file) { - if (mNewSettings.find(std::make_pair(category, setting)) != mNewSettings.end()) - return mNewSettings[std::make_pair(category, setting)]; + SettingsFileParser parser; + parser.loadSettingsFile(file, mUserSettings); +} + +void Manager::saveUser(const std::string &file) +{ + boost::filesystem::ofstream stream; + stream.open(boost::filesystem::path(file)); + std::string currentCategory; + for (CategorySettingValueMap::iterator it = mUserSettings.begin(); it != mUserSettings.end(); ++it) + { + if (it->first.first != currentCategory) + { + currentCategory = it->first.first; + stream << "\n[" << currentCategory << "]\n"; + } + stream << it->first.second << " = " << it->second << "\n"; + } +} + +std::string Manager::getString(const std::string &setting, const std::string &category) +{ + CategorySettingValueMap::key_type key = std::make_pair(category, setting); + CategorySettingValueMap::iterator it = mUserSettings.find(key); + if (it != mUserSettings.end()) + return it->second; - std::string defaultval = mDefaultFile.getSetting(setting, category, "NOTFOUND"); - std::string val = mFile.getSetting(setting, category, defaultval); + it = mDefaultSettings.find(key); + if (it != mDefaultSettings.end()) + return it->second; - if (val == "NOTFOUND") - throw std::runtime_error("Trying to retrieve a non-existing setting: " + setting + " Make sure the settings-default.cfg file was properly installed."); - return val; + throw std::runtime_error(std::string("Trying to retrieve a non-existing setting: ") + setting + + ".\nMake sure the settings-default.cfg file file was properly installed."); } float Manager::getFloat (const std::string& setting, const std::string& category) @@ -107,51 +156,20 @@ bool Manager::getBool (const std::string& setting, const std::string& category) return Ogre::StringConverter::parseBool( getString(setting, category) ); } -void Manager::setString (const std::string& setting, const std::string& category, const std::string& value) +void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) { - CategorySetting s = std::make_pair(category, setting); + CategorySettingValueMap::key_type key = std::make_pair(category, setting); - bool found=false; - try + CategorySettingValueMap::iterator found = mUserSettings.find(key); + if (found != mUserSettings.end()) { - Ogre::ConfigFile::SettingsIterator it = mFile.getSettingsIterator(category); - while (it.hasMoreElements()) - { - Ogre::ConfigFile::SettingsMultiMap::iterator i = it.current(); - - if ((*i).first == setting) - { - if ((*i).second != value) - { - mChangedSettings.push_back(std::make_pair(category, setting)); - (*i).second = value; - } - found = true; - } - - it.getNext(); - } + if (found->second == value) + return; } - catch (Ogre::Exception&) - {} - if (!found) - { - if (mNewSettings.find(s) != mNewSettings.end()) - { - if (mNewSettings[s] != value) - { - mChangedSettings.push_back(std::make_pair(category, setting)); - mNewSettings[s] = value; - } - } - else - { - if (mDefaultFile.getSetting(setting, category) != value) - mChangedSettings.push_back(std::make_pair(category, setting)); - mNewSettings[s] = value; - } - } + mUserSettings[key] = value; + + mChangedSettings.insert(key); } void Manager::setInt (const std::string& setting, const std::string& category, const int value) @@ -159,12 +177,12 @@ void Manager::setInt (const std::string& setting, const std::string& category, c setString(setting, category, Ogre::StringConverter::toString(value)); } -void Manager::setFloat (const std::string& setting, const std::string& category, const float value) +void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { setString(setting, category, Ogre::StringConverter::toString(value)); } -void Manager::setBool (const std::string& setting, const std::string& category, const bool value) +void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { setString(setting, category, Ogre::StringConverter::toString(value)); } @@ -175,3 +193,5 @@ const CategorySettingVector Manager::apply() mChangedSettings.clear(); return vec; } + +} diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 03b0b517c..c16ff5a1e 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -1,12 +1,14 @@ #ifndef COMPONENTS_SETTINGS_H #define COMPONENTS_SETTINGS_H -#include +#include +#include +#include namespace Settings { typedef std::pair < std::string, std::string > CategorySetting; - typedef std::vector< std::pair > CategorySettingVector; + typedef std::set< std::pair > CategorySettingVector; typedef std::map < CategorySetting, std::string > CategorySettingValueMap; /// @@ -15,15 +17,12 @@ namespace Settings class Manager { public: - static Ogre::ConfigFile mFile; - static Ogre::ConfigFile mDefaultFile; + static CategorySettingValueMap mDefaultSettings; + static CategorySettingValueMap mUserSettings; static CategorySettingVector mChangedSettings; ///< tracks all the settings that were changed since the last apply() call - static CategorySettingValueMap mNewSettings; - ///< tracks all the settings that are in the default file, but not in user file yet - void loadDefault (const std::string& file); ///< load file as the default settings (can be overridden by user settings)