Using LUA for configuration files?

Started by
6 comments, last by marcusz 19 years, 2 months ago
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
Advertisement
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.
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
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;    }}
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
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 tableTable 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;}
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
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

This topic is closed to new replies.

Advertisement