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
This commit is contained in:
SmallJoker 2020-11-22 17:49:30 +01:00 committed by sfan5
parent 5e9dd1667b
commit 37a05ec8d6
21 changed files with 358 additions and 298 deletions

@ -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;
}

@ -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();

@ -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

@ -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", "");

@ -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();

@ -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());

@ -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) {

@ -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;

@ -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 " <<

@ -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;
};

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

@ -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;
}

@ -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;
}

@ -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

@ -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,

@ -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;

@ -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!");
}

@ -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);
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<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,

@ -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;
};

@ -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());
}
}

@ -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;
}