Sign in to follow this  

Using LUA for configuration files?

This topic is 4684 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello! Sorry for my poor english... I decided to use LUA for loading and saving configuration files, but how it´s possible to SAVE such a configuration file from the LUA globals stack? I tried it this way:
lua_pushvalue(L, LUA_GLOBALSINDEX);

lua_pushnil(L);
while (lua_next(L, -2) != 0) {
  if (!isInMyFilterValues(L)) {
    // save them to the file
  }

  // removes ‘value’; keeps ‘key’ for next iteration
  lua_pop(L, 1);
}


But this sounds IMHO a little bit dirty?! Isn´t it? But there are two problems to solve... 1.) If I save my config file to disk the tables are saved in the wrong direction. 2.) How can I save comments? ("-- comment") Thanks for your help! ;) Cheers, Mike

Share this post


Link to post
Share on other sites
I've implemented a system that stores configuration options as a map<string, string> table (C++). Each option is a key/value pair, and the value of a given key must be converted from string form to int/float/etc... as necessary when queried by the engine. I can iterate the map of key/values and construct a Lua file which when executed will reverse the process, setting key/value pairs through the configuration interface. As an example of such a file:


ConfigTable={}

ConfigTable["AnimInterval"]="0.04"
ConfigTable["BitsPerColorComponent"]="8"
ConfigTable["Check"]="1"
ConfigTable["FullScreen"]="0"
ConfigTable["MapHeight"]="257"
ConfigTable["MapWidth"]="257"
ConfigTable["ScreenHeight"]="768"
ConfigTable["ScreenWidth"]="1024"
ConfigTable["ScriptPath"]="data/"
ConfigTable["UseDepthBuffer"]="1"

for i,j in ConfigTable do
ConfigurationInterface:SetParam(i, j)
end



I can load configurations simply be executing this file as a Lua script, and I can generate this file exactly as is from the engine by iterating the map and building a string, then saving the string as the above file. I usually include a fall-back function that is called if the Config file does not exist, which will set default parameters and save a default Config file.

Share this post


Link to post
Share on other sites
Wow!

That sounds quite good!!! Thank you very much!!!

*veryquiet*...But in think in such an implementation it isn´t possible that a key can be a map<string, string>?!*veryquiet*
So I wouldn´t be flexible enought to write a LUA-Script this way:

TestSettings1 = {
SubSettings1 = {
foo1 = "bla1";
foo2 = "bla2";
}

SubSettings2 = {
foo1 = "bla1";
foo2 = "bla2";
}
}



How would you solve this problem?

Thank you for help! ;)

Cheers, Mike

Share this post


Link to post
Share on other sites
I'll offer two solutions to the problem and a little code that might offer some light. You'll need to recursively read each of the tables and collect the data into an appropriate container:

1) A Tree, there are no std::trees in C++, so this will get a little ugly and expensive to copy if not done carefully.

2) Build a maplike structure which contains keys which can express the nesting within the tables. If you know the structure of the table before-hand, which you'll almost certainly have to in one way or another - this is probably the easiest way to go.

dict["\bob"] = "fred";
dict["\SubSettings1\foo1"] = "bar1"
dict["\SubSettings1\foo2"] = "bar2"
dict["\SubSettings2\foo1"] = "car1"
dict["\SubSettings2\foo2"] = "car2"

My solution uses a tree, and offers a traversal to convert the tree data to a map. It's not fully tested - use at your own risk, but it seemed to work OK on a simple test file. Note that the efficiency of this solution is terrible, and it could be improved by minimising the number of copies of Table objects. It was compiled and tested using VC7.1


extern "C"
{
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
}
#include <string>
#include <iostream>
#include <map>

using namespace std;

class ScriptEngine
{
public:
ScriptEngine()
{
L_ = lua_open();

lua_baselibopen(L_);
lua_iolibopen(L_);
lua_strlibopen(L_);
lua_mathlibopen(L_);
}

~ScriptEngine()
{
lua_close(L_);
}

void doFile(const std::string& filename)
{
if(luaL_loadfile(L_, filename.c_str()) != 0)
{
std::string errmsg = "Unknown loading error";
if(lua_isstring(L_,-1))
{
errmsg = lua_tostring( L_, -1 );
}
throw std::runtime_error(errmsg);
}
if(lua_pcall(L_, 0, LUA_MULTRET, 0) != 0)
{
std::string errmsg = "Unknown call message";
if(lua_isstring(L_,-1))
{
errmsg = lua_tostring( L_, -1 );
}
throw std::runtime_error(errmsg);
}
}

lua_State* getState()
{
return L_;
}

private:
lua_State* L_;
};

struct Table
{
typedef std::map<std::string,std::string>::const_iterator DataIterConst;
typedef std::map<std::string,Table>::const_iterator SubtableIterConst;
typedef std::map<std::string,std::string>::iterator DataIter;
typedef std::map<std::string,Table>::iterator SubtableIter;
std::map<std::string, std::string> data;
std::map<std::string, Table> subTables;
};

void traverse(const Table& t, std::map<std::string,std::string>& output, const std::string& str = "")
{
Table::DataIterConst diter = t.data.begin();
Table::DataIterConst dend = t.data.end();
for( ; diter != dend; ++diter)
{
std::string key = str + "\\" + (*diter).first;
std::string value = (*diter).second;
output[ key ] = value;
}
Table::SubtableIterConst titer = t.subTables.begin();
Table::SubtableIterConst tend = t.subTables.end();
for( ; titer != tend; ++titer)
{
traverse((*titer).second, output, str + "\\" + (*titer).first);
}
}

Table loadConfigTable(lua_State* L)
{
Table t;

// Loop for all items in table code from the Lua manual
lua_pushnil(L);
while(lua_next(L, -2) != 0)
{
if(!lua_isstring(L, -2))
throw std::runtime_error("keys must be strings");
std::string key( lua_tostring(L, -2) );

if( lua_isstring(L, -1) )
{
std::string value( lua_tostring(L, -1) );
t.data[ key ] = value;
}
else if( lua_istable(L, -1) )
{
// recursively scan all subtables
t.subTables[key] = loadConfigTable(L);
}
else
{
throw std::runtime_error("values must be strings or tables");
}
lua_pop(L,1);
}
return t;
}

Table loadConfigTable(lua_State* L, const std::string& tableName)
{
lua_getglobal( L, tableName.c_str() ); // push the requested table
if (!lua_istable(L, -1))
{
throw std::runtime_error("not a table");
}
Table t = loadConfigTable(L);

lua_pop(L, 1); // pop off the table

return t;
}



int main()
{
try
{
ScriptEngine scriptEngine;
scriptEngine.doFile("script.lua");
lua_State* L = scriptEngine.getState();
Table t = loadConfigTable(L, "TestSettings1");
std::map<std::string,std::string> dict;

traverse(t,dict);
for(std::map<std::string,std::string>::iterator iter = dict.begin();
iter != dict.end(); ++iter)
{
std::string key = (*iter).first;
std::string value = (*iter).second;
cout << key << " " << value << endl;
}
}
catch(std::exception& ex)
{
cerr << ex.what() << endl;
}

}


Share this post


Link to post
Share on other sites
Hey, great work Andrew! Thank you!

That sounds really good! The problem of how to save a table is now solved through containers, but the second prob is that I don´t know the name of each table! :o(
So I´ve to implement that feature through the LUA_GLOBALSINDEX in some way.

So should I decide to implement a kind of a filter which sorts out the LUA stuff and then dump the config file content back?

Cheers, Mike

Share this post


Link to post
Share on other sites
The filter idea sounds great, but for simplicities sake I recommend doing load and filter as two separate steps - it'll be much easier to debug if it's not recursive. Another thing you might even consider making the filter a lua function.

The modified code that works with all tables is included. Note that it will now ignore any types of value in the table which it doesn't like. This means that in the case of _G most of the information is lost, which is good because it's all of the code for the standard librarys etc.

If you need a better system than this still, you may want to look at Pluto


Table loadConfigTable(lua_State* L)
{
Table t;

// Loop for all items in table code from the Lua manual
lua_pushnil(L);
while(lua_next(L, -2) != 0)
{
if(lua_isstring(L, -2))
{
// don't permit cycles
std::string key( lua_tostring(L, -2) );
if(key != "_G")
{
if( lua_isstring(L, -1) )
{
std::string value( lua_tostring(L, -1) );
t.data[ key ] = value;
}
else if( lua_istable(L, -1) )
{
// recursively scan all subtables
t.subTables[key] = loadConfigTable(L);
}
}
}
lua_pop(L,1);
}
return t;
}

// _G is the globals table
Table loadConfigTable(lua_State* L, const std::string& tableName = "_G")
{
lua_getglobal( L, tableName.c_str() ); // push the requested table
if (!lua_istable(L, -1))
{
throw std::runtime_error("not a table");
}
Table t = loadConfigTable(L);

lua_pop(L, 1); // pop off the table

return t;
}

Share this post


Link to post
Share on other sites
Wow! Excellent work, Andrew!!! That sounds amazing!!! Thank you so much!!! ;o)
I think now all the obscurities are solved! The idea of pluto also sounds quite good!

Thanks!

Cheers, Mike

Share this post


Link to post
Share on other sites
VertexNormal: I do the same in my game with two differences.

1) I use this for the C++ table:

typedef std::map<std::string,boost::any> TTable;

This makes it easy to add strings, floats and even other TTable-objects to a table!

2) When saving, I actually generate a lua source file, that when run _actively_ calls functions to re-create the game state.

Hope this helps


/Marcus

Share this post


Link to post
Share on other sites

This topic is 4684 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this