From 37a05ec8d6cbf9ff4432225cffe78c16fdd0647d Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Sun, 22 Nov 2020 17:49:30 +0100 Subject: [PATCH] Settings: Proper priority hierarchy Remove old defaults system Introduce priority-based fallback list Use new functions for map_meta special functions Change groups to use end tags Unittest changes: * Adapt unittest to the new code * Compare Settings objects --- src/content/subgames.cpp | 24 +- src/database/database-files.cpp | 15 +- src/database/database-files.h | 4 +- src/defaultsettings.cpp | 4 +- src/defaultsettings.h | 9 +- src/gui/guiKeyChangeMenu.cpp | 2 +- src/main.cpp | 6 +- src/map.cpp | 2 +- src/map_settings_manager.cpp | 67 ++--- src/map_settings_manager.h | 5 +- src/remoteplayer.h | 1 - src/script/lua_api/l_mapgen.cpp | 2 +- src/script/lua_api/l_settings.cpp | 2 +- src/script/scripting_mainmenu.cpp | 2 +- src/server.cpp | 3 + src/server.h | 1 + src/serverenvironment.cpp | 7 +- src/settings.cpp | 298 ++++++++++----------- src/settings.h | 43 +-- src/unittest/test_map_settings_manager.cpp | 86 +++--- src/unittest/test_settings.cpp | 73 ++++- 21 files changed, 358 insertions(+), 298 deletions(-) diff --git a/src/content/subgames.cpp b/src/content/subgames.cpp index c6350f2dd..e9dc609b0 100644 --- a/src/content/subgames.cpp +++ b/src/content/subgames.cpp @@ -329,18 +329,16 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, } } - // Override defaults with those provided by the game. - // We clear and reload the defaults because the defaults - // might have been overridden by other subgame config - // files that were loaded before. - g_settings->clearDefaults(); - set_default_settings(g_settings); + Settings *game_settings = Settings::getLayer(SL_GAME); + const bool new_game_settings = (game_settings == nullptr); + if (new_game_settings) { + // Called by main-menu without a Server instance running + // -> create and free manually + game_settings = Settings::createLayer(SL_GAME); + } - Settings game_defaults; - getGameMinetestConfig(gamespec.path, game_defaults); - game_defaults.removeSecureSettings(); - - g_settings->overrideDefaults(&game_defaults); + getGameMinetestConfig(gamespec.path, *game_settings); + game_settings->removeSecureSettings(); infostream << "Initializing world at " << final_path << std::endl; @@ -381,4 +379,8 @@ void loadGameConfAndInitWorld(const std::string &path, const std::string &name, fs::safeWriteToFile(map_meta_path, oss.str()); } + + // The Settings object is no longer needed for created worlds + if (new_game_settings) + delete game_settings; } diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index 529fb8763..d9e8f24ea 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -122,18 +122,17 @@ void PlayerDatabaseFiles::serialize(RemotePlayer *p, std::ostream &os) args.set("name", p->m_name); // This should not happen - assert(m_sao); - args.setU16("hp", p->m_sao->getHP()); - args.setV3F("position", p->m_sao->getBasePosition()); - args.setFloat("pitch", p->m_sao->getLookPitch()); - args.setFloat("yaw", p->m_sao->getRotation().Y); - args.setU16("breath", p->m_sao->getBreath()); + PlayerSAO *sao = p->getPlayerSAO(); + assert(sao); + args.setU16("hp", sao->getHP()); + args.setV3F("position", sao->getBasePosition()); + args.setFloat("pitch", sao->getLookPitch()); + args.setFloat("yaw", sao->getRotation().Y); + args.setU16("breath", sao->getBreath()); std::string extended_attrs; { // serializeExtraAttributes - PlayerSAO *sao = p->getPlayerSAO(); - assert(sao); Json::Value json_root; const StringMap &attrs = sao->getMeta().getStrings(); diff --git a/src/database/database-files.h b/src/database/database-files.h index a041cb1ff..e647a2e24 100644 --- a/src/database/database-files.h +++ b/src/database/database-files.h @@ -38,8 +38,8 @@ public: void listPlayers(std::vector &res); private: - void deSerialize(RemotePlayer *p, std::istream &is, - const std::string &playername, PlayerSAO *sao); + void deSerialize(RemotePlayer *p, std::istream &is, const std::string &playername, + PlayerSAO *sao); /* serialize() writes a bunch of text that can contain any characters except a '\0', and such an ending that diff --git a/src/defaultsettings.cpp b/src/defaultsettings.cpp index 114351d86..d34ec324b 100644 --- a/src/defaultsettings.cpp +++ b/src/defaultsettings.cpp @@ -27,8 +27,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mapgen.h" // Mapgen::setDefaultSettings #include "util/string.h" -void set_default_settings(Settings *settings) +void set_default_settings() { + Settings *settings = Settings::createLayer(SL_DEFAULTS); + // Client and server settings->setDefault("language", ""); settings->setDefault("name", ""); diff --git a/src/defaultsettings.h b/src/defaultsettings.h index c81e88502..c239b3c12 100644 --- a/src/defaultsettings.h +++ b/src/defaultsettings.h @@ -25,11 +25,4 @@ class Settings; * initialize basic default settings * @param settings pointer to settings */ -void set_default_settings(Settings *settings); - -/** - * override a default settings by settings from another settings element - * @param settings target settings pointer - * @param from source settings pointer - */ -void override_default_settings(Settings *settings, Settings *from); +void set_default_settings(); diff --git a/src/gui/guiKeyChangeMenu.cpp b/src/gui/guiKeyChangeMenu.cpp index eb641d952..4dcb47779 100644 --- a/src/gui/guiKeyChangeMenu.cpp +++ b/src/gui/guiKeyChangeMenu.cpp @@ -248,7 +248,7 @@ bool GUIKeyChangeMenu::acceptInput() { for (key_setting *k : key_settings) { std::string default_key; - g_settings->getDefaultNoEx(k->setting_name, default_key); + Settings::getLayer(SL_DEFAULTS)->getNoEx(k->setting_name, default_key); if (k->key.sym() != default_key) g_settings->set(k->setting_name, k->key.sym()); diff --git a/src/main.cpp b/src/main.cpp index f7238176b..57768dbb2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -487,12 +487,15 @@ static bool create_userdata_path() static bool init_common(const Settings &cmd_args, int argc, char *argv[]) { startup_message(); - set_default_settings(g_settings); + set_default_settings(); // Initialize sockets sockets_init(); atexit(sockets_cleanup); + // Initialize g_settings + Settings::createLayer(SL_GLOBAL); + if (!read_config_file(cmd_args)) return false; @@ -524,6 +527,7 @@ static bool read_config_file(const Settings &cmd_args) // Path of configuration file in use sanity_check(g_settings_path == ""); // Sanity check + if (cmd_args.exists("config")) { bool r = g_settings->readConfigFile(cmd_args.get("config").c_str()); if (!r) { diff --git a/src/map.cpp b/src/map.cpp index aff545921..7c59edbaa 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1184,7 +1184,7 @@ bool Map::isBlockOccluded(MapBlock *block, v3s16 cam_pos_nodes) ServerMap::ServerMap(const std::string &savedir, IGameDef *gamedef, EmergeManager *emerge, MetricsBackend *mb): Map(gamedef), - settings_mgr(g_settings, savedir + DIR_DELIM + "map_meta.txt"), + settings_mgr(savedir + DIR_DELIM + "map_meta.txt"), m_emerge(emerge) { verbosestream<overrideDefaults(user_settings); + m_map_settings = Settings::createLayer(SL_MAP, "[end_of_params]"); + Mapgen::setDefaultSettings(Settings::getLayer(SL_DEFAULTS)); } @@ -49,22 +43,23 @@ MapSettingsManager::~MapSettingsManager() bool MapSettingsManager::getMapSetting( const std::string &name, std::string *value_out) { + // Get from map_meta.txt, then try from all other sources if (m_map_settings->getNoEx(name, *value_out)) return true; // Compatibility kludge - if (m_user_settings == g_settings && name == "seed") - return m_user_settings->getNoEx("fixed_map_seed", *value_out); + if (name == "seed") + return Settings::getLayer(SL_GLOBAL)->getNoEx("fixed_map_seed", *value_out); - return m_user_settings->getNoEx(name, *value_out); + return false; } bool MapSettingsManager::getMapSettingNoiseParams( const std::string &name, NoiseParams *value_out) { - return m_map_settings->getNoiseParams(name, *value_out) || - m_user_settings->getNoiseParams(name, *value_out); + // TODO: Rename to "getNoiseParams" + return m_map_settings->getNoiseParams(name, *value_out); } @@ -77,7 +72,7 @@ bool MapSettingsManager::setMapSetting( if (override_meta) m_map_settings->set(name, value); else - m_map_settings->setDefault(name, value); + Settings::getLayer(SL_GLOBAL)->set(name, value); return true; } @@ -89,7 +84,11 @@ bool MapSettingsManager::setMapSettingNoiseParams( if (mapgen_params) return false; - m_map_settings->setNoiseParams(name, *value, !override_meta); + if (override_meta) + m_map_settings->setNoiseParams(name, *value); + else + Settings::getLayer(SL_GLOBAL)->setNoiseParams(name, *value); + return true; } @@ -104,8 +103,8 @@ bool MapSettingsManager::loadMapMeta() return false; } - if (!m_map_settings->parseConfigLines(is, "[end_of_params]")) { - errorstream << "loadMapMeta: [end_of_params] not found!" << std::endl; + if (!m_map_settings->parseConfigLines(is)) { + errorstream << "loadMapMeta: Format error. '[end_of_params]' missing?" << std::endl; return false; } @@ -116,28 +115,22 @@ bool MapSettingsManager::loadMapMeta() bool MapSettingsManager::saveMapMeta() { // If mapgen params haven't been created yet; abort - if (!mapgen_params) + if (!mapgen_params) { + errorstream << "saveMapMeta: mapgen_params not present!" << std::endl; return false; + } + // Paths set up by subgames.cpp, but not in unittests if (!fs::CreateAllDirs(fs::RemoveLastPathComponent(m_map_meta_path))) { errorstream << "saveMapMeta: could not create dirs to " << m_map_meta_path; return false; } - std::ostringstream oss(std::ios_base::binary); - Settings conf; + mapgen_params->MapgenParams::writeParams(m_map_settings); + mapgen_params->writeParams(m_map_settings); - mapgen_params->MapgenParams::writeParams(&conf); - mapgen_params->writeParams(&conf); - conf.writeLines(oss); - - // NOTE: If there are ever types of map settings other than - // those relating to map generation, save them here - - oss << "[end_of_params]\n"; - - if (!fs::safeWriteToFile(m_map_meta_path, oss.str())) { + if (!m_map_settings->updateConfigFile(m_map_meta_path.c_str())) { errorstream << "saveMapMeta: could not write " << m_map_meta_path << std::endl; return false; @@ -152,23 +145,21 @@ MapgenParams *MapSettingsManager::makeMapgenParams() if (mapgen_params) return mapgen_params; - assert(m_user_settings != NULL); assert(m_map_settings != NULL); // At this point, we have (in order of precedence): - // 1). m_mapgen_settings->m_settings containing map_meta.txt settings or + // 1). SL_MAP containing map_meta.txt settings or // explicit overrides from scripts - // 2). m_mapgen_settings->m_defaults containing script-set mgparams without - // overrides - // 3). g_settings->m_settings containing all user-specified config file + // 2). SL_GLOBAL containing all user-specified config file // settings - // 4). g_settings->m_defaults containing any low-priority settings from + // 3). SL_DEFAULTS containing any low-priority settings from // scripts, e.g. mods using Lua as an enhanced config file) // Now, get the mapgen type so we can create the appropriate MapgenParams std::string mg_name; MapgenType mgtype = getMapSetting("mg_name", &mg_name) ? Mapgen::getMapgenType(mg_name) : MAPGEN_DEFAULT; + if (mgtype == MAPGEN_INVALID) { errorstream << "EmergeManager: mapgen '" << mg_name << "' not valid; falling back to " << diff --git a/src/map_settings_manager.h b/src/map_settings_manager.h index 5baa38455..9258d3032 100644 --- a/src/map_settings_manager.h +++ b/src/map_settings_manager.h @@ -44,8 +44,7 @@ struct MapgenParams; */ class MapSettingsManager { public: - MapSettingsManager(Settings *user_settings, - const std::string &map_meta_path); + MapSettingsManager(const std::string &map_meta_path); ~MapSettingsManager(); // Finalized map generation parameters @@ -71,6 +70,6 @@ public: private: std::string m_map_meta_path; + // TODO: Rename to "m_settings" Settings *m_map_settings; - Settings *m_user_settings; }; diff --git a/src/remoteplayer.h b/src/remoteplayer.h index b82cbc08e..8d086fc5a 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -140,7 +140,6 @@ public: void onSuccessfulSave(); private: - PlayerSAO *m_sao = nullptr; bool m_dirty = false; diff --git a/src/script/lua_api/l_mapgen.cpp b/src/script/lua_api/l_mapgen.cpp index fad08e1f6..183f20540 100644 --- a/src/script/lua_api/l_mapgen.cpp +++ b/src/script/lua_api/l_mapgen.cpp @@ -982,7 +982,7 @@ int ModApiMapgen::l_set_noiseparams(lua_State *L) bool set_default = !lua_isboolean(L, 3) || readParam(L, 3); - g_settings->setNoiseParams(name, np, set_default); + Settings::getLayer(set_default ? SL_DEFAULTS : SL_GLOBAL)->setNoiseParams(name, np); return 0; } diff --git a/src/script/lua_api/l_settings.cpp b/src/script/lua_api/l_settings.cpp index 33eb02392..bcbaf15fa 100644 --- a/src/script/lua_api/l_settings.cpp +++ b/src/script/lua_api/l_settings.cpp @@ -197,7 +197,7 @@ int LuaSettings::l_set_np_group(lua_State *L) SET_SECURITY_CHECK(L, key); - o->m_settings->setNoiseParams(key, value, false); + o->m_settings->setNoiseParams(key, value); return 0; } diff --git a/src/script/scripting_mainmenu.cpp b/src/script/scripting_mainmenu.cpp index 0f672f917..9b377366e 100644 --- a/src/script/scripting_mainmenu.cpp +++ b/src/script/scripting_mainmenu.cpp @@ -31,7 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., extern "C" { #include "lualib.h" } - +#include "settings.h" #define MAINMENU_NUM_ASYNC_THREADS 4 diff --git a/src/server.cpp b/src/server.cpp index b5352749c..aba7b6401 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -351,6 +351,7 @@ Server::~Server() // Deinitialize scripting infostream << "Server: Deinitializing scripting" << std::endl; delete m_script; + delete m_game_settings; while (!m_unsent_map_edit_queue.empty()) { delete m_unsent_map_edit_queue.front(); @@ -368,6 +369,8 @@ void Server::init() infostream << "- world: " << m_path_world << std::endl; infostream << "- game: " << m_gamespec.path << std::endl; + m_game_settings = Settings::createLayer(SL_GAME); + // Create world if it doesn't exist try { loadGameConfAndInitWorld(m_path_world, diff --git a/src/server.h b/src/server.h index a7e85d0e1..1dd181794 100644 --- a/src/server.h +++ b/src/server.h @@ -524,6 +524,7 @@ private: u16 m_max_chatmessage_length; // For "dedicated" server list flag bool m_dedicated; + Settings *m_game_settings = nullptr; // Thread can set; step() will throw as ServerError MutexedVariable m_async_fatal_error; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 56dbb0632..3d9ba132b 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -632,7 +632,7 @@ void ServerEnvironment::saveMeta() // Open file and serialize std::ostringstream ss(std::ios_base::binary); - Settings args; + Settings args("EnvArgsEnd"); args.setU64("game_time", m_game_time); args.setU64("time_of_day", getTimeOfDay()); args.setU64("last_clear_objects_time", m_last_clear_objects_time); @@ -641,7 +641,6 @@ void ServerEnvironment::saveMeta() m_lbm_mgr.createIntroductionTimesString()); args.setU64("day_count", m_day_count); args.writeLines(ss); - ss<<"EnvArgsEnd\n"; if(!fs::safeWriteToFile(path, ss.str())) { @@ -676,9 +675,9 @@ void ServerEnvironment::loadMeta() throw SerializationError("Couldn't load env meta"); } - Settings args; + Settings args("EnvArgsEnd"); - if (!args.parseConfigLines(is, "EnvArgsEnd")) { + if (!args.parseConfigLines(is)) { throw SerializationError("ServerEnvironment::loadMeta(): " "EnvArgsEnd not found!"); } diff --git a/src/settings.cpp b/src/settings.cpp index f30ef34e9..cf2a16aa6 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -33,27 +33,50 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include -static Settings main_settings; -Settings *g_settings = &main_settings; +Settings *g_settings = nullptr; std::string g_settings_path; -Settings::~Settings() +Settings *Settings::s_layers[SL_TOTAL_COUNT] = {0}; // Zeroed by compiler +std::unordered_map Settings::s_flags; + + +Settings *Settings::createLayer(SettingsLayer sl, const std::string &end_tag) { - clear(); + if ((int)sl < 0 || sl >= SL_TOTAL_COUNT) + throw new BaseException("Invalid settings layer"); + + Settings *&pos = s_layers[(size_t)sl]; + if (pos) + throw new BaseException("Setting layer " + std::to_string(sl) + " already exists"); + + pos = new Settings(end_tag); + pos->m_settingslayer = sl; + + if (sl == SL_GLOBAL) + g_settings = pos; + return pos; } -Settings & Settings::operator += (const Settings &other) +Settings *Settings::getLayer(SettingsLayer sl) { - if (&other == this) - return *this; + sanity_check((int)sl >= 0 && sl < SL_TOTAL_COUNT); + return s_layers[(size_t)sl]; +} + +Settings::~Settings() +{ MutexAutoLock lock(m_mutex); - MutexAutoLock lock2(other.m_mutex); - updateNoLock(other); + if (m_settingslayer < SL_TOTAL_COUNT) + s_layers[(size_t)m_settingslayer] = nullptr; - return *this; + // Compatibility + if (m_settingslayer == SL_GLOBAL) + g_settings = nullptr; + + clearNoLock(); } @@ -62,11 +85,15 @@ Settings & Settings::operator = (const Settings &other) if (&other == this) return *this; + FATAL_ERROR_IF(m_settingslayer != SL_TOTAL_COUNT && other.m_settingslayer != SL_TOTAL_COUNT, + ("Tried to copy unique Setting layer " + std::to_string(m_settingslayer)).c_str()); + MutexAutoLock lock(m_mutex); MutexAutoLock lock2(other.m_mutex); clearNoLock(); - updateNoLock(other); + m_settings = other.m_settings; + m_callbacks = other.m_callbacks; return *this; } @@ -130,11 +157,11 @@ bool Settings::readConfigFile(const char *filename) if (!is.good()) return false; - return parseConfigLines(is, ""); + return parseConfigLines(is); } -bool Settings::parseConfigLines(std::istream &is, const std::string &end) +bool Settings::parseConfigLines(std::istream &is) { MutexAutoLock lock(m_mutex); @@ -142,7 +169,7 @@ bool Settings::parseConfigLines(std::istream &is, const std::string &end) while (is.good()) { std::getline(is, line); - SettingsParseEvent event = parseConfigObject(line, end, name, value); + SettingsParseEvent event = parseConfigObject(line, name, value); switch (event) { case SPE_NONE: @@ -155,8 +182,8 @@ bool Settings::parseConfigLines(std::istream &is, const std::string &end) case SPE_END: return true; case SPE_GROUP: { - Settings *group = new Settings; - if (!group->parseConfigLines(is, "}")) { + Settings *group = new Settings("}"); + if (!group->parseConfigLines(is)) { delete group; return false; } @@ -169,7 +196,8 @@ bool Settings::parseConfigLines(std::istream &is, const std::string &end) } } - return end.empty(); + // false (failure) if end tag not found + return m_end_tag.empty(); } @@ -179,6 +207,13 @@ void Settings::writeLines(std::ostream &os, u32 tab_depth) const for (const auto &setting_it : m_settings) printEntry(os, setting_it.first, setting_it.second, tab_depth); + + if (!m_end_tag.empty()) { + for (u32 i = 0; i < tab_depth; i++) + os << "\t"; + + os << m_end_tag << "\n"; + } } @@ -193,9 +228,7 @@ void Settings::printEntry(std::ostream &os, const std::string &name, entry.group->writeLines(os, tab_depth + 1); - for (u32 i = 0; i != tab_depth; i++) - os << "\t"; - os << "}\n"; + // Closing bracket handled by writeLines } else { os << name << " = "; @@ -207,8 +240,7 @@ void Settings::printEntry(std::ostream &os, const std::string &name, } -bool Settings::updateConfigObject(std::istream &is, std::ostream &os, - const std::string &end, u32 tab_depth) +bool Settings::updateConfigObject(std::istream &is, std::ostream &os, u32 tab_depth) { SettingEntries::const_iterator it; std::set present_entries; @@ -220,11 +252,11 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, // in the object if existing while (is.good() && !end_found) { std::getline(is, line); - SettingsParseEvent event = parseConfigObject(line, end, name, value); + SettingsParseEvent event = parseConfigObject(line, name, value); switch (event) { case SPE_END: - os << line << (is.eof() ? "" : "\n"); + // Skip end tag. Append later. end_found = true; break; case SPE_MULTILINE: @@ -252,14 +284,13 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, if (it != m_settings.end() && it->second.is_group) { os << line << "\n"; sanity_check(it->second.group != NULL); - was_modified |= it->second.group->updateConfigObject(is, os, - "}", tab_depth + 1); + was_modified |= it->second.group->updateConfigObject(is, os, tab_depth + 1); } else if (it == m_settings.end()) { // Remove by skipping was_modified = true; - Settings removed_group; // Move 'is' to group end + Settings removed_group("}"); // Move 'is' to group end std::stringstream ss; - removed_group.updateConfigObject(is, ss, "}", tab_depth + 1); + removed_group.updateConfigObject(is, ss, tab_depth + 1); break; } else { printEntry(os, name, it->second, tab_depth); @@ -273,6 +304,9 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, } } + if (!line.empty() && is.eof()) + os << "\n"; + // Add any settings in the object that don't exist in the config file yet for (it = m_settings.begin(); it != m_settings.end(); ++it) { if (present_entries.find(it->first) != present_entries.end()) @@ -282,6 +316,12 @@ bool Settings::updateConfigObject(std::istream &is, std::ostream &os, was_modified = true; } + // Append ending tag + if (!m_end_tag.empty()) { + os << m_end_tag << "\n"; + was_modified |= !end_found; + } + return was_modified; } @@ -293,7 +333,7 @@ bool Settings::updateConfigFile(const char *filename) std::ifstream is(filename); std::ostringstream os(std::ios_base::binary); - bool was_modified = updateConfigObject(is, os, ""); + bool was_modified = updateConfigObject(is, os); is.close(); if (!was_modified) @@ -366,29 +406,37 @@ bool Settings::parseCommandLine(int argc, char *argv[], * Getters * ***********/ - -const SettingsEntry &Settings::getEntry(const std::string &name) const +Settings *Settings::getParent() const { - MutexAutoLock lock(m_mutex); + // If the Settings object is within the hierarchy structure, + // iterate towards the origin (0) to find the next fallback layer + if (m_settingslayer >= SL_TOTAL_COUNT) + return nullptr; - SettingEntries::const_iterator n; - if ((n = m_settings.find(name)) == m_settings.end()) { - if ((n = m_defaults.find(name)) == m_defaults.end()) - throw SettingNotFoundException("Setting [" + name + "] not found."); + for (int i = (int)m_settingslayer - 1; i >= 0; --i) { + if (s_layers[i]) + return s_layers[i]; } - return n->second; + + // No parent + return nullptr; } -const SettingsEntry &Settings::getEntryDefault(const std::string &name) const +const SettingsEntry &Settings::getEntry(const std::string &name) const { - MutexAutoLock lock(m_mutex); + { + MutexAutoLock lock(m_mutex); - SettingEntries::const_iterator n; - if ((n = m_defaults.find(name)) == m_defaults.end()) { - throw SettingNotFoundException("Setting [" + name + "] not found."); + SettingEntries::const_iterator n; + if ((n = m_settings.find(name)) != m_settings.end()) + return n->second; } - return n->second; + + if (auto parent = getParent()) + return parent->getEntry(name); + + throw SettingNotFoundException("Setting [" + name + "] not found."); } @@ -412,10 +460,15 @@ const std::string &Settings::get(const std::string &name) const const std::string &Settings::getDefault(const std::string &name) const { - const SettingsEntry &entry = getEntryDefault(name); - if (entry.is_group) + const SettingsEntry *entry; + if (auto parent = getParent()) + entry = &parent->getEntry(name); + else + entry = &getEntry(name); // Bottom of the chain + + if (entry->is_group) throw SettingNotFoundException("Setting [" + name + "] is a group."); - return entry.value; + return entry->value; } @@ -491,29 +544,25 @@ u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc, u32 *flagmask) const { u32 flags = 0; - u32 mask_default = 0; - std::string value; // Read default value (if there is any) - if (getDefaultNoEx(name, value)) { - flags = std::isdigit(value[0]) - ? stoi(value) - : readFlagString(value, flagdesc, &mask_default); - } + if (auto parent = getParent()) + flags = parent->getFlagStr(name, flagdesc, flagmask); // Apply custom flags "on top" - value = get(name); - u32 flags_user; - u32 mask_user = U32_MAX; - flags_user = std::isdigit(value[0]) - ? stoi(value) // Override default - : readFlagString(value, flagdesc, &mask_user); + if (m_settings.find(name) != m_settings.end()) { + std::string value = get(name); + u32 flags_user; + u32 mask_user = U32_MAX; + flags_user = std::isdigit(value[0]) + ? stoi(value) // Override default + : readFlagString(value, flagdesc, &mask_user); - flags &= ~mask_user; - flags |= flags_user; - - if (flagmask) - *flagmask = mask_default | mask_user; + flags &= ~mask_user; + flags |= flags_user; + if (flagmask) + *flagmask |= mask_user; + } return flags; } @@ -521,7 +570,12 @@ u32 Settings::getFlagStr(const std::string &name, const FlagDesc *flagdesc, bool Settings::getNoiseParams(const std::string &name, NoiseParams &np) const { - return getNoiseParamsFromGroup(name, np) || getNoiseParamsFromValue(name, np); + if (getNoiseParamsFromGroup(name, np) || getNoiseParamsFromValue(name, np)) + return true; + if (auto parent = getParent()) + return parent->getNoiseParams(name, np); + + return false; } @@ -583,13 +637,18 @@ bool Settings::exists(const std::string &name) const { MutexAutoLock lock(m_mutex); - return (m_settings.find(name) != m_settings.end() || - m_defaults.find(name) != m_defaults.end()); + if (m_settings.find(name) != m_settings.end()) + return true; + if (auto parent = getParent()) + return parent->exists(name); + return false; } std::vector Settings::getNames() const { + MutexAutoLock lock(m_mutex); + std::vector names; for (const auto &settings_it : m_settings) { names.push_back(settings_it.first); @@ -625,17 +684,6 @@ bool Settings::getNoEx(const std::string &name, std::string &val) const } -bool Settings::getDefaultNoEx(const std::string &name, std::string &val) const -{ - try { - val = getDefault(name); - return true; - } catch (SettingNotFoundException &e) { - return false; - } -} - - bool Settings::getFlag(const std::string &name) const { try { @@ -746,24 +794,25 @@ bool Settings::getFlagStrNoEx(const std::string &name, u32 &val, ***********/ bool Settings::setEntry(const std::string &name, const void *data, - bool set_group, bool set_default) + bool set_group) { - Settings *old_group = NULL; - if (!checkNameValid(name)) return false; if (!set_group && !checkValueValid(*(const std::string *)data)) return false; + Settings *old_group = NULL; { MutexAutoLock lock(m_mutex); - SettingsEntry &entry = set_default ? m_defaults[name] : m_settings[name]; + SettingsEntry &entry = m_settings[name]; old_group = entry.group; entry.value = set_group ? "" : *(const std::string *)data; entry.group = set_group ? *(Settings **)data : NULL; entry.is_group = set_group; + if (set_group) + entry.group->m_end_tag = "}"; } delete old_group; @@ -774,7 +823,7 @@ bool Settings::setEntry(const std::string &name, const void *data, bool Settings::set(const std::string &name, const std::string &value) { - if (!setEntry(name, &value, false, false)) + if (!setEntry(name, &value, false)) return false; doCallbacks(name); @@ -782,9 +831,10 @@ bool Settings::set(const std::string &name, const std::string &value) } +// TODO: Remove this function bool Settings::setDefault(const std::string &name, const std::string &value) { - return setEntry(name, &value, false, true); + return getLayer(SL_DEFAULTS)->set(name, value); } @@ -794,17 +844,7 @@ bool Settings::setGroup(const std::string &name, const Settings &group) // avoid double-free by copying the source Settings *copy = new Settings(); *copy = group; - return setEntry(name, ©, true, false); -} - - -bool Settings::setGroupDefault(const std::string &name, const Settings &group) -{ - // Settings must own the group pointer - // avoid double-free by copying the source - Settings *copy = new Settings(); - *copy = group; - return setEntry(name, ©, true, true); + return setEntry(name, ©, true); } @@ -874,8 +914,7 @@ bool Settings::setFlagStr(const std::string &name, u32 flags, } -bool Settings::setNoiseParams(const std::string &name, - const NoiseParams &np, bool set_default) +bool Settings::setNoiseParams(const std::string &name, const NoiseParams &np) { Settings *group = new Settings; @@ -888,7 +927,7 @@ bool Settings::setNoiseParams(const std::string &name, group->setFloat("lacunarity", np.lacunarity); group->setFlagStr("flags", np.flags, flagdesc_noiseparams, np.flags); - return setEntry(name, &group, true, set_default); + return setEntry(name, &group, true); } @@ -912,20 +951,8 @@ bool Settings::remove(const std::string &name) } -void Settings::clear() -{ - MutexAutoLock lock(m_mutex); - clearNoLock(); -} - -void Settings::clearDefaults() -{ - MutexAutoLock lock(m_mutex); - clearDefaultsNoLock(); -} - SettingsParseEvent Settings::parseConfigObject(const std::string &line, - const std::string &end, std::string &name, std::string &value) + std::string &name, std::string &value) { std::string trimmed_line = trim(line); @@ -933,7 +960,7 @@ SettingsParseEvent Settings::parseConfigObject(const std::string &line, return SPE_NONE; if (trimmed_line[0] == '#') return SPE_COMMENT; - if (trimmed_line == end) + if (trimmed_line == m_end_tag) return SPE_END; size_t pos = trimmed_line.find('='); @@ -952,67 +979,26 @@ SettingsParseEvent Settings::parseConfigObject(const std::string &line, } -void Settings::updateNoLock(const Settings &other) -{ - m_settings.insert(other.m_settings.begin(), other.m_settings.end()); - m_defaults.insert(other.m_defaults.begin(), other.m_defaults.end()); -} - - void Settings::clearNoLock() { - for (SettingEntries::const_iterator it = m_settings.begin(); it != m_settings.end(); ++it) delete it->second.group; m_settings.clear(); - - clearDefaultsNoLock(); } -void Settings::clearDefaultsNoLock() -{ - for (SettingEntries::const_iterator it = m_defaults.begin(); - it != m_defaults.end(); ++it) - delete it->second.group; - m_defaults.clear(); -} void Settings::setDefault(const std::string &name, const FlagDesc *flagdesc, u32 flags) { - m_flags[name] = flagdesc; + s_flags[name] = flagdesc; setDefault(name, writeFlagString(flags, flagdesc, U32_MAX)); } -void Settings::overrideDefaults(Settings *other) -{ - for (const auto &setting : other->m_settings) { - if (setting.second.is_group) { - setGroupDefault(setting.first, *setting.second.group); - continue; - } - const FlagDesc *flagdesc = getFlagDescFallback(setting.first); - if (flagdesc) { - // Flags cannot be copied directly. - // 1) Get the current set flags - u32 flags = getFlagStr(setting.first, flagdesc, nullptr); - // 2) Set the flags as defaults - other->setDefault(setting.first, flagdesc, flags); - // 3) Get the newly set flags and override the default setting value - setDefault(setting.first, flagdesc, - other->getFlagStr(setting.first, flagdesc, nullptr)); - continue; - } - // Also covers FlagDesc settings - setDefault(setting.first, setting.second.value); - } -} - const FlagDesc *Settings::getFlagDescFallback(const std::string &name) const { - auto it = m_flags.find(name); - return it == m_flags.end() ? nullptr : it->second; + auto it = s_flags.find(name); + return it == s_flags.end() ? nullptr : it->second; } void Settings::registerChangedCallback(const std::string &name, diff --git a/src/settings.h b/src/settings.h index 6db2f9481..af4cae3cd 100644 --- a/src/settings.h +++ b/src/settings.h @@ -30,7 +30,7 @@ class Settings; struct NoiseParams; // Global objects -extern Settings *g_settings; +extern Settings *g_settings; // Same as Settings::getLayer(SL_GLOBAL); extern std::string g_settings_path; // Type for a settings changed callback function @@ -60,6 +60,14 @@ enum SettingsParseEvent { SPE_MULTILINE, }; +enum SettingsLayer { + SL_DEFAULTS, + SL_GAME, + SL_GLOBAL, + SL_MAP, + SL_TOTAL_COUNT +}; + struct ValueSpec { ValueSpec(ValueType a_type, const char *a_help=NULL) { @@ -92,8 +100,13 @@ typedef std::unordered_map SettingEntries; class Settings { public: - Settings() = default; + static Settings *createLayer(SettingsLayer sl, const std::string &end_tag = ""); + static Settings *getLayer(SettingsLayer sl); + SettingsLayer getLayerType() const { return m_settingslayer; } + Settings(const std::string &end_tag = "") : + m_end_tag(end_tag) + {} ~Settings(); Settings & operator += (const Settings &other); @@ -110,7 +123,7 @@ public: // NOTE: Types of allowed_options are ignored. Returns success. bool parseCommandLine(int argc, char *argv[], std::map &allowed_options); - bool parseConfigLines(std::istream &is, const std::string &end = ""); + bool parseConfigLines(std::istream &is); void writeLines(std::ostream &os, u32 tab_depth=0) const; /*********** @@ -146,7 +159,6 @@ public: bool getGroupNoEx(const std::string &name, Settings *&val) const; bool getNoEx(const std::string &name, std::string &val) const; - bool getDefaultNoEx(const std::string &name, std::string &val) const; bool getFlag(const std::string &name) const; bool getU16NoEx(const std::string &name, u16 &val) const; bool getS16NoEx(const std::string &name, s16 &val) const; @@ -170,11 +182,10 @@ public: // N.B. Groups not allocated with new must be set to NULL in the settings // tree before object destruction. bool setEntry(const std::string &name, const void *entry, - bool set_group, bool set_default); + bool set_group); bool set(const std::string &name, const std::string &value); bool setDefault(const std::string &name, const std::string &value); bool setGroup(const std::string &name, const Settings &group); - bool setGroupDefault(const std::string &name, const Settings &group); bool setBool(const std::string &name, bool value); bool setS16(const std::string &name, s16 value); bool setU16(const std::string &name, u16 value); @@ -185,21 +196,16 @@ public: bool setV3F(const std::string &name, v3f value); bool setFlagStr(const std::string &name, u32 flags, const FlagDesc *flagdesc = nullptr, u32 flagmask = U32_MAX); - bool setNoiseParams(const std::string &name, const NoiseParams &np, - bool set_default=false); + bool setNoiseParams(const std::string &name, const NoiseParams &np); // remove a setting bool remove(const std::string &name); - void clear(); - void clearDefaults(); /************** * Miscellany * **************/ void setDefault(const std::string &name, const FlagDesc *flagdesc, u32 flags); - // Takes the provided setting values and uses them as new defaults - void overrideDefaults(Settings *other); const FlagDesc *getFlagDescFallback(const std::string &name) const; void registerChangedCallback(const std::string &name, @@ -215,9 +221,9 @@ private: ***********************/ SettingsParseEvent parseConfigObject(const std::string &line, - const std::string &end, std::string &name, std::string &value); + std::string &name, std::string &value); bool updateConfigObject(std::istream &is, std::ostream &os, - const std::string &end, u32 tab_depth=0); + u32 tab_depth=0); static bool checkNameValid(const std::string &name); static bool checkValueValid(const std::string &value); @@ -228,9 +234,9 @@ private: /*********** * Getters * ***********/ + Settings *getParent() const; const SettingsEntry &getEntry(const std::string &name) const; - const SettingsEntry &getEntryDefault(const std::string &name) const; // Allow TestSettings to run sanity checks using private functions. friend class TestSettings; @@ -242,14 +248,15 @@ private: void doCallbacks(const std::string &name) const; SettingEntries m_settings; - SettingEntries m_defaults; - std::unordered_map m_flags; - SettingsCallbackMap m_callbacks; + std::string m_end_tag; mutable std::mutex m_callback_mutex; // All methods that access m_settings/m_defaults directly should lock this. mutable std::mutex m_mutex; + static Settings *s_layers[SL_TOTAL_COUNT]; + SettingsLayer m_settingslayer = SL_TOTAL_COUNT; + static std::unordered_map s_flags; }; diff --git a/src/unittest/test_map_settings_manager.cpp b/src/unittest/test_map_settings_manager.cpp index 3d642a9b4..81ca68705 100644 --- a/src/unittest/test_map_settings_manager.cpp +++ b/src/unittest/test_map_settings_manager.cpp @@ -30,7 +30,7 @@ public: TestMapSettingsManager() { TestManager::registerTestModule(this); } const char *getName() { return "TestMapSettingsManager"; } - void makeUserConfig(Settings *conf); + void makeUserConfig(); std::string makeMetaFile(bool make_corrupt); void runTests(IGameDef *gamedef); @@ -65,8 +65,11 @@ void check_noise_params(const NoiseParams *np1, const NoiseParams *np2) } -void TestMapSettingsManager::makeUserConfig(Settings *conf) +void TestMapSettingsManager::makeUserConfig() { + delete Settings::getLayer(SL_GLOBAL); + Settings *conf = Settings::createLayer(SL_GLOBAL); + conf->set("mg_name", "v7"); conf->set("seed", "5678"); conf->set("water_level", "20"); @@ -103,12 +106,11 @@ std::string TestMapSettingsManager::makeMetaFile(bool make_corrupt) void TestMapSettingsManager::testMapSettingsManager() { - Settings user_settings; - makeUserConfig(&user_settings); + makeUserConfig(); std::string test_mapmeta_path = makeMetaFile(false); - MapSettingsManager mgr(&user_settings, test_mapmeta_path); + MapSettingsManager mgr(test_mapmeta_path); std::string value; UASSERT(mgr.getMapSetting("mg_name", &value)); @@ -140,6 +142,12 @@ void TestMapSettingsManager::testMapSettingsManager() mgr.setMapSettingNoiseParams("mgv5_np_height", &script_np_height); mgr.setMapSettingNoiseParams("mgv5_np_factor", &script_np_factor); + { + NoiseParams dummy; + mgr.getMapSettingNoiseParams("mgv5_np_factor", &dummy); + check_noise_params(&dummy, &script_np_factor); + } + // Now make our Params and see if the values are correctly sourced MapgenParams *params = mgr.makeMapgenParams(); UASSERT(params->mgtype == MAPGEN_V5); @@ -188,50 +196,66 @@ void TestMapSettingsManager::testMapSettingsManager() void TestMapSettingsManager::testMapMetaSaveLoad() { - Settings conf; std::string path = getTestTempDirectory() + DIR_DELIM + "foobar" + DIR_DELIM + "map_meta.txt"; + makeUserConfig(); + Settings &conf = *Settings::getLayer(SL_GLOBAL); + + // There cannot be two MapSettingsManager + // copy the mapgen params to compare them + MapgenParams params1, params2; // Create a set of mapgen params and save them to map meta - conf.set("seed", "12345"); - conf.set("water_level", "5"); - MapSettingsManager mgr1(&conf, path); - MapgenParams *params1 = mgr1.makeMapgenParams(); - UASSERT(params1); - UASSERT(mgr1.saveMapMeta()); + { + conf.set("seed", "12345"); + conf.set("water_level", "5"); + MapSettingsManager mgr(path); + MapgenParams *params = mgr.makeMapgenParams(); + UASSERT(params); + params1 = *params; + params1.bparams = nullptr; // No double-free + UASSERT(mgr.saveMapMeta()); + } // Now try loading the map meta to mapgen params - conf.set("seed", "67890"); - conf.set("water_level", "32"); - MapSettingsManager mgr2(&conf, path); - UASSERT(mgr2.loadMapMeta()); - MapgenParams *params2 = mgr2.makeMapgenParams(); - UASSERT(params2); + { + conf.set("seed", "67890"); + conf.set("water_level", "32"); + MapSettingsManager mgr(path); + UASSERT(mgr.loadMapMeta()); + MapgenParams *params = mgr.makeMapgenParams(); + UASSERT(params); + params2 = *params; + params2.bparams = nullptr; // No double-free + } // Check that both results are correct - UASSERTEQ(u64, params1->seed, 12345); - UASSERTEQ(s16, params1->water_level, 5); - UASSERTEQ(u64, params2->seed, 12345); - UASSERTEQ(s16, params2->water_level, 5); + UASSERTEQ(u64, params1.seed, 12345); + UASSERTEQ(s16, params1.water_level, 5); + UASSERTEQ(u64, params2.seed, 12345); + UASSERTEQ(s16, params2.water_level, 5); } void TestMapSettingsManager::testMapMetaFailures() { std::string test_mapmeta_path; - Settings conf; // Check to see if it'll fail on a non-existent map meta file - test_mapmeta_path = "woobawooba/fgdfg/map_meta.txt"; - UASSERT(!fs::PathExists(test_mapmeta_path)); + { + test_mapmeta_path = "woobawooba/fgdfg/map_meta.txt"; + UASSERT(!fs::PathExists(test_mapmeta_path)); - MapSettingsManager mgr1(&conf, test_mapmeta_path); - UASSERT(!mgr1.loadMapMeta()); + MapSettingsManager mgr1(test_mapmeta_path); + UASSERT(!mgr1.loadMapMeta()); + } // Check to see if it'll fail on a corrupt map meta file - test_mapmeta_path = makeMetaFile(true); - UASSERT(fs::PathExists(test_mapmeta_path)); + { + test_mapmeta_path = makeMetaFile(true); + UASSERT(fs::PathExists(test_mapmeta_path)); - MapSettingsManager mgr2(&conf, test_mapmeta_path); - UASSERT(!mgr2.loadMapMeta()); + MapSettingsManager mgr2(test_mapmeta_path); + UASSERT(!mgr2.loadMapMeta()); + } } diff --git a/src/unittest/test_settings.cpp b/src/unittest/test_settings.cpp index f91ba5b67..d2d35c357 100644 --- a/src/unittest/test_settings.cpp +++ b/src/unittest/test_settings.cpp @@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include "settings.h" +#include "defaultsettings.h" #include "noise.h" class TestSettings : public TestBase { @@ -31,6 +32,7 @@ public: void runTests(IGameDef *gamedef); void testAllSettings(); + void testDefaults(); void testFlagDesc(); static const char *config_text_before; @@ -42,6 +44,7 @@ static TestSettings g_test_instance; void TestSettings::runTests(IGameDef *gamedef) { TEST(testAllSettings); + TEST(testDefaults); TEST(testFlagDesc); } @@ -70,7 +73,8 @@ const char *TestSettings::config_text_before = " with leading whitespace!\n" "\"\"\"\n" "np_terrain = 5, 40, (250, 250, 250), 12341, 5, 0.7, 2.4\n" - "zoop = true"; + "zoop = true\n" + "[dummy_eof_end_tag]\n"; const std::string TestSettings::config_text_after = "leet = 1337\n" @@ -111,12 +115,34 @@ const std::string TestSettings::config_text_after = " animals = cute\n" " num_apples = 4\n" " num_oranges = 53\n" - "}\n"; + "}\n" + "[dummy_eof_end_tag]"; + +void compare_settings(const std::string &name, Settings *a, Settings *b) +{ + auto keys = a->getNames(); + Settings *group1, *group2; + std::string value1, value2; + for (auto &key : keys) { + if (a->getGroupNoEx(key, group1)) { + UASSERT(b->getGroupNoEx(key, group2)); + + compare_settings(name + "->" + key, group1, group2); + continue; + } + + UASSERT(b->getNoEx(key, value1)); + // For identification + value1 = name + "->" + key + "=" + value1; + value2 = name + "->" + key + "=" + a->get(key); + UASSERTCMP(std::string, ==, value2, value1); + } +} void TestSettings::testAllSettings() { try { - Settings s; + Settings s("[dummy_eof_end_tag]"); // Test reading of settings std::istringstream is(config_text_before); @@ -197,21 +223,44 @@ void TestSettings::testAllSettings() is.clear(); is.seekg(0); - UASSERT(s.updateConfigObject(is, os, "", 0) == true); - //printf(">>>> expected config:\n%s\n", TEST_CONFIG_TEXT_AFTER); - //printf(">>>> actual config:\n%s\n", os.str().c_str()); -#if __cplusplus < 201103L - // This test only works in older C++ versions than C++11 because we use unordered_map - UASSERT(os.str() == config_text_after); -#endif + UASSERT(s.updateConfigObject(is, os, 0) == true); + + { + // Confirm settings + Settings s2("[dummy_eof_end_tag]"); + std::istringstream is(config_text_after, std::ios_base::binary); + s2.parseConfigLines(is); + + compare_settings("(main)", &s, &s2); + } + } catch (SettingNotFoundException &e) { UASSERT(!"Setting not found!"); } } +void TestSettings::testDefaults() +{ + Settings *game = Settings::createLayer(SL_GAME); + Settings *def = Settings::getLayer(SL_DEFAULTS); + + def->set("name", "FooBar"); + UASSERT(def->get("name") == "FooBar"); + UASSERT(game->get("name") == "FooBar"); + + game->set("name", "Baz"); + UASSERT(game->get("name") == "Baz"); + + delete game; + + // Restore default settings + delete Settings::getLayer(SL_DEFAULTS); + set_default_settings(); +} + void TestSettings::testFlagDesc() { - Settings s; + Settings &s = *Settings::createLayer(SL_GAME); FlagDesc flagdesc[] = { { "biomes", 0x01 }, { "trees", 0x02 }, @@ -242,4 +291,6 @@ void TestSettings::testFlagDesc() // Enabled: tables s.set("test_flags", "16"); UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == 0x10); + + delete &s; }