300 changes: 143 additions & 157 deletions src/settings.cpp

Large diffs are not rendered by default.

43 changes: 25 additions & 18 deletions src/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand All @@ -110,7 +123,7 @@ class Settings {
// 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;

/***********
Expand Down Expand Up @@ -146,7 +159,6 @@ class Settings {

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;
Expand All @@ -170,11 +182,10 @@ class Settings {
// 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);
Expand All @@ -185,21 +196,16 @@ class Settings {
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,
Expand All @@ -215,9 +221,9 @@ class Settings {
***********************/

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);
Expand All @@ -228,9 +234,9 @@ class Settings {
/***********
* 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;
Expand All @@ -242,14 +248,15 @@ class Settings {
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;
};
86 changes: 55 additions & 31 deletions src/unittest/test_map_settings_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TestMapSettingsManager : public TestBase {
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);
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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());
}
}
73 changes: 62 additions & 11 deletions src/unittest/test_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -31,6 +32,7 @@ class TestSettings : public TestBase {
void runTests(IGameDef *gamedef);

void testAllSettings();
void testDefaults();
void testFlagDesc();

static const char *config_text_before;
Expand All @@ -42,6 +44,7 @@ static TestSettings g_test_instance;
void TestSettings::runTests(IGameDef *gamedef)
{
TEST(testAllSettings);
TEST(testDefaults);
TEST(testFlagDesc);
}

Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -242,4 +291,6 @@ void TestSettings::testFlagDesc()
// Enabled: tables
s.set("test_flags", "16");
UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == 0x10);

delete &s;
}