📜 ⬆️ ⬇️

Using Lua and C ++ to process and store data

Article code can be found here .
What is so good about Lua?

Once I was developing my game and wondered: which data format should I use for configuration files?
It is convenient, when you create an object, to set different initial parameters not in the code itself, but in separate files. This allows you to change some of the parameters of objects without recompilation, and indeed makes it possible to change them to people far from programming.
Developers use different formats: some use JSON , others - XML, or other data formats. Well, some generally store data in .txt files or write their parsers. After reviewing the various formats, I settled on Lua.

Lua can be used not only for games, but in general for any programs that use data stored in other files.

This is what distinguishes Lua from other formats:

Let's start with a simple example, and then I will go to the class implementation.
Example

Suppose there is a file Player.lua
 player = { pos = { X = 20, Y = 30, }, filename = "res/images/player.png", HP = 20, --      } 

With a simple class data can be obtained as follows:
 LuaScript script("player.lua"); std::string filename = script.get<std::string>("player.filename"); int posX = script.get<std::string>("player.pos.X"); 

')
Attention, so that the code is understandable, it is recommended to read the information on how the Lua stack works and look at the simplest examples.
You can read here .

Let's start by creating a class:
 #ifndef LUASCRIPT_H #define LUASCRIPT_H #include <string> #include <vector> #include <iostream> // Lua   C,    ,        C extern "C" { # include "lua.h" # include "lauxlib.h" # include "lualib.h" } class LuaScript { public: LuaScript(const std::string& filename); ~LuaScript(); void printError(const std::string& variableName, const std::string& reason); template<typename T> T get(const std::string& variableName) { //       } bool lua_gettostack(const std::string& variableName) { //     .   -  true //   } //  0   template<typename T> T lua_get(const std::string& variableName) { return 0; } //     ,          - //    template<typename T> T lua_getdefault(const std::string& variableName) { return 0; } private: lua_State* L; int level; // .  lug_gettostack }; #endif 

Constructor:
 LuaScript::LuaScript(const std::string& filename) { L = luaL_newstate(); if (luaL_loadfile(L, filename.c_str()) || lua_pcall(L, 0, 0, 0)) { std::cout<<"Error: script not loaded ("<<filename<<")"<<std::endl; L = 0; } } 
.
Create a lua_State, in case the file was not found, or some other error occurred, we display a message about it.
Destructor:
 LuaScript::~LuaScript() { if(L) lua_close(L); } 


The printError method was created to display error messages:
 void LuaScript::printError(const std::string& variableName, const std::string& reason) { std::cout<<"Error: can't get ["<<variableName<<"]. "<<reason<<std::endl; } 

lua_getdefault is used to return a null value if an error has occurred. And if for numbers it is possible to return zero, then for strings, for example, this will not work, so we do specialization of the template (this code will be in the header).
 template<> inline std::string LuaScript::lua_getdefault<std::string>() { return "null"; } 

Now let's write the get template function.
First, we write the function lua_gettostack, which puts the variable at the top of the stack
Let us analyze the algorithm by example. Suppose you need to get the variable "player.pos.X" from the file Player.lua
We loop through to the first point, while adding the read characters to the variable “var”.
“Player” is a table that is global, so we get it using lua_getglobal .
"Pos" and "X" are already data that are not global, but can be obtained using lua_getfield , since the player table itself is at the top of the stack.
Then, in the sample function get , the data type-specific function is executed, the stack is cleared and the desired value is returned, and in case of an error, the function lua_getdefault is called.


 bool lua_gettostack(const std::string& variableName) { level = 0; std::string var = ""; for(unsigned int i = 0; i < variableName.size(); i++) { if(variableName.at(i) == '.') { if(level == 0) { lua_getglobal(L, var.c_str()); } else { lua_getfield(L, -1, var.c_str()); } if(lua_isnil(L, -1)) { printError(variableName, var + " is not defined"); return false; } else { var = ""; level++; } } else { var += variableName.at(i); } } if(level == 0) { lua_getglobal(L, var.c_str()); } else { lua_getfield(L, -1, var.c_str()); } if(lua_isnil(L, -1)) { printError(variableName, var + " is not defined"); return false; } //  ,  true return true; } 

Go back to the get method:
 template <typename T> T get(const std::string& variableName) { if(!L) { printError(variableName, "Script is not loaded"); return lua_getdefault<T>(); } T result; if(lua_gettostack(variableName)) { //  ,     result = lua_get<T>(variableName); } else { result = lua_getdefault<T>(); } lua_pop(L, level + 1); //   return result; } } 

It only remains to add template specializations (example for some data types):
 template <> inline bool LuaScript::lua_get<bool>(const std::string& variableName) { return (bool)lua_toboolean(L, -1); } template <> inline float LuaScript::lua_get<float>(const std::string& variableName) { if(!lua_isnumber(L, -1)) { printError(variableName, "Not a number"); } return (float)lua_tonumber(L, -1); } template <> inline int LuaScript::lua_get<int>(const std::string& variableName) { if(!lua_isnumber(L, -1)) { printError(variableName, "Not a number"); } return (int)lua_tonumber(L, -1); } template <> inline std::string LuaScript::lua_get<std::string>(const std::string& variableName) { std::string s = "null"; if(lua_isstring(L, -1)) { s = std::string(lua_tostring(L, -1)); } else { printError(variableName, "Not a string"); } return s; } 


That's all. I remind you, all the code in the article is here . There you can also find an example of using the class.

What's next?

Lua has many more features that I will describe in the second part of the article in the near future. For example, getting an array of data of indefinite length, as well as getting a list of table keys (for example, for the Player table from the example, it would be: [“pos”, “filename”, “HP”])
And from Lua, you can call C ++ functions, as well as from C ++, you can call Lua functions, which I will write about in the third part.
Successful scripting!

Source: https://habr.com/ru/post/197300/


All Articles