Browse Source

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
SmallJoker 3 years ago
parent
commit
37a05ec8d6

+ 13 - 11
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_defaults;
-	getGameMinetestConfig(gamespec.path, game_defaults);
-	game_defaults.removeSecureSettings();
+	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);
+	}
 
-	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;
 }

+ 7 - 8
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();

+ 2 - 2
src/database/database-files.h

@@ -38,8 +38,8 @@ public:
 	void listPlayers(std::vector<std::string> &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

+ 3 - 1
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", "");

+ 1 - 8
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();

+ 1 - 1
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());

+ 5 - 1
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) {

+ 1 - 1
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<<FUNCTION_NAME<<std::endl;

+ 29 - 38
src/map_settings_manager.cpp

@@ -25,17 +25,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include "map_settings_manager.h"
 
-MapSettingsManager::MapSettingsManager(Settings *user_settings,
-		const std::string &map_meta_path):
-	m_map_meta_path(map_meta_path),
-	m_map_settings(new Settings()),
-	m_user_settings(user_settings)
+MapSettingsManager::MapSettingsManager(const std::string &map_meta_path):
+	m_map_meta_path(map_meta_path)
 {
-	assert(m_user_settings != NULL);
-
-	Mapgen::setDefaultSettings(m_map_settings);
-	// This inherits the combined defaults provided by loadGameConfAndInitWorld.
-	m_map_settings->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 " <<

+ 2 - 3
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;
 };

+ 0 - 1
src/remoteplayer.h

@@ -140,7 +140,6 @@ public:
 	void onSuccessfulSave();
 
 private:
-
 	PlayerSAO *m_sao = nullptr;
 	bool m_dirty = false;
 

+ 1 - 1
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<bool>(L, 3);
 
-	g_settings->setNoiseParams(name, np, set_default);
+	Settings::getLayer(set_default ? SL_DEFAULTS : SL_GLOBAL)->setNoiseParams(name, np);
 
 	return 0;
 }

+ 1 - 1
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;
 }

+ 1 - 1
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
 
 

+ 3 - 0
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,

+ 1 - 0
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<std::string> m_async_fatal_error;

+ 3 - 4
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!");
 	}

+ 143 - 157
src/settings.cpp

@@ -33,27 +33,50 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include <cctype>
 #include <algorithm>
 
-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<std::string, const FlagDesc *> 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<std::string> 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);
-
-	flags &= ~mask_user;
-	flags |=  flags_user;
-
-	if (flagmask)
-		*flagmask = mask_default | 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_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<std::string> Settings::getNames() const
 {
+	MutexAutoLock lock(m_mutex);
+
 	std::vector<std::string> 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, &copy, 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, &copy, true, true);
+	return setEntry(name, &copy, 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,

+ 25 - 18
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<std::string, SettingsEntry> 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<std::string, ValueSpec> &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<std::string, const FlagDesc *> 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<std::string, const FlagDesc *> s_flags;
 };

+ 55 - 31
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());
+	}
 }

+ 62 - 11
src/unittest/test_settings.cpp

@@ -21,6 +21,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 #include <cmath>
 #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;
 }