settings manager: better default setting handling and updating config file and command line parsing

This commit is contained in:
Perttu Ahola 2010-12-14 15:16:49 +02:00
parent f501cfd799
commit 385dd9917f
7 changed files with 382 additions and 56 deletions

@ -32,7 +32,8 @@ Controls:
Configuration file:
- An optional configuration file can be used. See minetest.conf.example.
- Path to file can be passed as a parameter to the executable.
- Path to file can be passed as a parameter to the executable:
--config <path-to-file>
- If not given as a parameter, these are checked, in order:
../minetest.conf
../../minetest.conf

@ -7,8 +7,6 @@
# By default, all the settings are commented and not functional.
# Uncomment settings by removing the preceding #.
#dedicated_server =
# Client side stuff
#wanted_fps = 30

@ -124,6 +124,14 @@ public:
{}
};
class CommandLineError : public BaseException
{
public:
CommandLineError(const char *s):
BaseException(s)
{}
};
/*
Some "old-style" interrupts:
*/

@ -306,38 +306,36 @@ Settings g_settings;
// Sets default settings
void set_default_settings()
{
g_settings.set("dedicated_server", "");
// Client stuff
g_settings.set("wanted_fps", "30");
g_settings.set("fps_max", "60");
g_settings.set("viewing_range_nodes_max", "300");
g_settings.set("viewing_range_nodes_min", "35");
g_settings.set("screenW", "");
g_settings.set("screenH", "");
g_settings.set("host_game", "");
g_settings.set("port", "");
g_settings.set("address", "");
g_settings.set("name", "");
g_settings.set("random_input", "false");
g_settings.set("client_delete_unused_sectors_timeout", "1200");
g_settings.set("max_block_send_distance", "8");
g_settings.set("max_block_generate_distance", "6");
g_settings.setDefault("wanted_fps", "30");
g_settings.setDefault("fps_max", "60");
g_settings.setDefault("viewing_range_nodes_max", "300");
g_settings.setDefault("viewing_range_nodes_min", "35");
g_settings.setDefault("screenW", "");
g_settings.setDefault("screenH", "");
g_settings.setDefault("host_game", "");
g_settings.setDefault("port", "");
g_settings.setDefault("address", "");
g_settings.setDefault("name", "");
g_settings.setDefault("random_input", "false");
g_settings.setDefault("client_delete_unused_sectors_timeout", "1200");
g_settings.setDefault("max_block_send_distance", "8");
g_settings.setDefault("max_block_generate_distance", "6");
// Server stuff
g_settings.set("creative_mode", "false");
g_settings.set("heightmap_blocksize", "32");
g_settings.set("height_randmax", "constant 50.0");
g_settings.set("height_randfactor", "constant 0.6");
g_settings.set("height_base", "linear 0 0 0");
g_settings.set("plants_amount", "1.0");
g_settings.set("ravines_amount", "1.0");
g_settings.set("objectdata_interval", "0.2");
g_settings.set("active_object_range", "2");
g_settings.set("max_simultaneous_block_sends_per_client", "1");
g_settings.set("max_simultaneous_block_sends_server_total", "4");
g_settings.set("disable_water_climb", "true");
g_settings.set("endless_water", "true");
g_settings.setDefault("creative_mode", "false");
g_settings.setDefault("heightmap_blocksize", "32");
g_settings.setDefault("height_randmax", "constant 50.0");
g_settings.setDefault("height_randfactor", "constant 0.6");
g_settings.setDefault("height_base", "linear 0 0 0");
g_settings.setDefault("plants_amount", "1.0");
g_settings.setDefault("ravines_amount", "1.0");
g_settings.setDefault("objectdata_interval", "0.2");
g_settings.setDefault("active_object_range", "2");
g_settings.setDefault("max_simultaneous_block_sends_per_client", "1");
g_settings.setDefault("max_simultaneous_block_sends_server_total", "4");
g_settings.setDefault("disable_water_climb", "true");
g_settings.setDefault("endless_water", "true");
}
/*
@ -962,6 +960,51 @@ int main(int argc, char *argv[])
try
{
/*
Parse command line
TODO
*/
core::map<std::string, ValueSpec> allowed_options;
allowed_options.insert("help", ValueSpec(VALUETYPE_FLAG));
allowed_options.insert("server", ValueSpec(VALUETYPE_FLAG,
"Run server directly"));
allowed_options.insert("config", ValueSpec(VALUETYPE_STRING,
"Load configuration from specified file"));
allowed_options.insert("port", ValueSpec(VALUETYPE_STRING));
Settings cmd_args;
bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
if(ret == false || cmd_args.getFlag("help"))
{
dstream<<"Allowed options:"<<std::endl;
for(core::map<std::string, ValueSpec>::Iterator
i = allowed_options.getIterator();
i.atEnd() == false; i++)
{
dstream<<" --"<<i.getNode()->getKey();
if(i.getNode()->getValue().type == VALUETYPE_FLAG)
{
}
else
{
dstream<<" <value>";
}
dstream<<std::endl;
if(i.getNode()->getValue().help != NULL)
{
dstream<<" "<<i.getNode()->getValue().help
<<std::endl;
}
}
return cmd_args.getFlag("help") ? 0 : 1;
}
/*
Basic initialization
*/
@ -999,11 +1042,23 @@ int main(int argc, char *argv[])
Initialization
*/
// Read config file
/*
Read config file
*/
if(argc >= 2)
// Path of configuration file in use
std::string configpath = "";
if(cmd_args.exists("config"))
{
g_settings.readConfigFile(argv[1]);
bool r = g_settings.readConfigFile(cmd_args.get("config").c_str());
if(r == false)
{
dstream<<"Could not read configuration from \""
<<cmd_args.get("config")<<"\""<<std::endl;
return 1;
}
configpath = cmd_args.get("config");
}
else
{
@ -1017,7 +1072,10 @@ int main(int argc, char *argv[])
{
bool r = g_settings.readConfigFile(filenames[i]);
if(r)
{
configpath = filenames[i];
break;
}
}
}
@ -1059,16 +1117,19 @@ int main(int argc, char *argv[])
std::cout<<std::endl;
char templine[100];
// Dedicated?
bool dedicated = g_settings.getBoolAsk
("dedicated_server", "Dedicated server?", false);
std::cout<<"dedicated = "<<dedicated<<std::endl;
// Port?
u16 port = g_settings.getU16Ask("port", "Port", 30000);
std::cout<<"-> "<<port<<std::endl;
u16 port = 30000;
if(cmd_args.exists("port"))
{
port = cmd_args.getU16("port");
}
else
{
port = g_settings.getU16Ask("port", "Port", 30000);
std::cout<<"-> "<<port<<std::endl;
}
if(dedicated)
if(cmd_args.getFlag("server"))
{
DSTACK("Dedicated server branch");
@ -2208,7 +2269,8 @@ int main(int argc, char *argv[])
{
TimeTaker timer("beginScene", device);
driver->beginScene(true, true, bgcolor);
//driver->beginScene(true, true, bgcolor);
driver->beginScene(false, true, bgcolor);
beginscenetime = timer.stop(true);
}
@ -2307,6 +2369,14 @@ int main(int argc, char *argv[])
*/
device->drop();
/*
Update configuration file
*/
if(configpath != "")
{
g_settings.updateConfigFile(configpath.c_str());
}
} //try
catch(con::PeerNotFoundException &e)
{

@ -205,7 +205,7 @@ void UDPSocket::Send(const Address & destination, const void * data, int size)
destination.print();
dstream<<", size="<<size<<", data=";
for(int i=0; i<size && i<20; i++){
if(i%2==0) printf(" ");
if(i%2==0) DEBUGPRINT(" ");
DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
}
if(size>20)
@ -267,7 +267,7 @@ int UDPSocket::Receive(Address & sender, void * data, int size)
//dstream<<", received="<<received<<std::endl;
dstream<<", size="<<received<<", data=";
for(int i=0; i<received && i<20; i++){
if(i%2==0) printf(" ");
if(i%2==0) DEBUGPRINT(" ");
DEBUGPRINT("%.2X", ((int)((const char*)data)[i])&0xff);
}
if(received>20)

@ -1070,8 +1070,8 @@ struct TestConnection
dstream<<"Sending data (size="<<1100<<"):";
for(int i=0; i<1100 && i<20; i++){
if(i%2==0) printf(" ");
printf("%.2X", ((int)((const char*)*data1)[i])&0xff);
if(i%2==0) DEBUGPRINT(" ");
DEBUGPRINT("%.2X", ((int)((const char*)*data1)[i])&0xff);
}
if(1100>20)
dstream<<"...";
@ -1091,8 +1091,8 @@ struct TestConnection
dstream<<"Received data (size="<<size<<"):";
for(int i=0; i<size && i<20; i++){
if(i%2==0) printf(" ");
printf("%.2X", ((int)((const char*)recvdata)[i])&0xff);
if(i%2==0) DEBUGPRINT(" ");
DEBUGPRINT("%.2X", ((int)((const char*)recvdata)[i])&0xff);
}
if(size>20)
dstream<<"...";

@ -668,6 +668,23 @@ inline s32 stoi(std::string s)
Config stuff
*/
enum ValueType
{
VALUETYPE_STRING,
VALUETYPE_FLAG // Doesn't take any arguments
};
struct ValueSpec
{
ValueSpec(ValueType a_type, const char *a_help=NULL)
{
type = a_type;
help = a_help;
}
ValueType type;
const char *help;
};
class Settings
{
public:
@ -710,36 +727,255 @@ public:
return true;
}
// Returns true on success
/*
Read configuration file
Returns true on success
*/
bool readConfigFile(const char *filename)
{
std::ifstream is(filename);
if(is.good() == false)
{
dstream<<"Error opening configuration file: "
<<filename<<std::endl;
dstream<<"Error opening configuration file \""
<<filename<<"\""<<std::endl;
return false;
}
dstream<<"Parsing configuration file: "
<<filename<<std::endl;
dstream<<"Parsing configuration file: \""
<<filename<<"\""<<std::endl;
while(parseConfigObject(is));
return true;
}
/*
Reads a configuration object from stream (usually a single line)
and adds it to dst.
Preserves comments and empty lines.
Settings that were added to dst are also added to updated.
key of updated is setting name, value of updated is dummy.
Returns false on EOF
*/
bool getUpdatedConfigObject(std::istream &is,
core::list<std::string> &dst,
core::map<std::string, bool> &updated)
{
if(is.eof())
return false;
// NOTE: This function will be expanded to allow multi-line settings
std::string line;
std::getline(is, line);
std::string trimmedline = trim(line);
std::string line_end = "";
if(is.eof() == false)
line_end = "\n";
// Ignore comments
if(trimmedline[0] == '#')
{
dst.push_back(line+line_end);
return true;
}
Strfnd sf(trim(line));
std::string name = sf.next("=");
name = trim(name);
if(name == "")
{
dst.push_back(line+line_end);
return true;
}
std::string value = sf.next("\n");
value = trim(value);
if(m_settings.find(name))
{
std::string newvalue = m_settings[name];
if(newvalue != value)
{
dstream<<"Changing value of \""<<name<<"\" = \""
<<value<<"\" -> \""<<newvalue<<"\""
<<std::endl;
}
dst.push_back(name + " = " + newvalue + line_end);
updated[name] = true;
}
return true;
}
/*
Updates configuration file
Returns true on success
*/
bool updateConfigFile(const char *filename)
{
dstream<<"Updating configuration file: \""
<<filename<<"\""<<std::endl;
core::list<std::string> objects;
core::map<std::string, bool> updated;
// Read and modify stuff
{
std::ifstream is(filename);
if(is.good() == false)
{
dstream<<"Error opening configuration file"
" for reading: \""
<<filename<<"\""<<std::endl;
return false;
}
while(getUpdatedConfigObject(is, objects, updated));
}
// Write stuff back
{
std::ofstream os(filename);
if(os.good() == false)
{
dstream<<"Error opening configuration file"
" for writing: \""
<<filename<<"\""<<std::endl;
return false;
}
/*
Write updated stuff
*/
for(core::list<std::string>::Iterator
i = objects.begin();
i != objects.end(); i++)
{
os<<(*i);
}
/*
Write stuff that was not already in the file
*/
for(core::map<std::string, std::string>::Iterator
i = m_settings.getIterator();
i.atEnd() == false; i++)
{
if(updated.find(i.getNode()->getKey()))
continue;
std::string name = i.getNode()->getKey();
std::string value = i.getNode()->getValue();
dstream<<"Adding \""<<name<<"\" = \""<<value<<"\""
<<std::endl;
os<<name<<" = "<<value<<"\n";
}
}
return true;
}
/*
NOTE: Types of allowed_options are ignored
returns true on success
*/
bool parseCommandLine(int argc, char *argv[],
core::map<std::string, ValueSpec> &allowed_options)
{
int i=1;
for(;;)
{
if(i >= argc)
break;
std::string argname = argv[i];
if(argname.substr(0, 2) != "--")
{
dstream<<"Invalid command-line parameter \""
<<argname<<"\": --<option> expected."<<std::endl;
return false;
}
i++;
std::string name = argname.substr(2);
core::map<std::string, ValueSpec>::Node *n;
n = allowed_options.find(name);
if(n == NULL)
{
dstream<<"Unknown command-line parameter \""
<<argname<<"\""<<std::endl;
return false;
}
ValueType type = n->getValue().type;
std::string value = "";
if(type == VALUETYPE_FLAG)
{
value = "true";
}
else
{
if(i >= argc)
{
dstream<<"Invalid command-line parameter \""
<<name<<"\": missing value"<<std::endl;
return false;
}
value = argv[i];
i++;
}
dstream<<"Valid command-line parameter: \""
<<name<<"\" = \""<<value<<"\""
<<std::endl;
set(name, value);
}
return true;
}
void set(std::string name, std::string value)
{
m_settings[name] = value;
}
void setDefault(std::string name, std::string value)
{
m_defaults[name] = value;
}
bool exists(std::string name)
{
return (m_settings.find(name) || m_defaults.find(name));
}
std::string get(std::string name)
{
core::map<std::string, std::string>::Node *n;
n = m_settings.find(name);
if(n == NULL)
throw SettingNotFoundException("Setting not found");
{
n = m_defaults.find(name);
if(n == NULL)
{
throw SettingNotFoundException("Setting not found");
}
}
return n->getValue();
}
@ -749,6 +985,18 @@ public:
return is_yes(get(name));
}
bool getFlag(std::string name)
{
try
{
return getBool(name);
}
catch(SettingNotFoundException &e)
{
return false;
}
}
// Asks if empty
bool getBoolAsk(std::string name, std::string question, bool def)
{
@ -809,6 +1057,7 @@ public:
private:
core::map<std::string, std::string> m_settings;
core::map<std::string, std::string> m_defaults;
};
/*