Introduction to Lua in C++ with Sol2
by Hristo Iliev | Last Updated:
C / C++, Lua
If you are working on a custom engine or a batebones game you would probably want to include a less time-consuming coding language than C++. Lua is one such great option. Lua is a language standard that can be implemented easily in many languages and executed from them. In this article we will explore how to use Lua in C++.
The Lua standard is maintained by https://www.lua.org/.
Table of Contents
The lua language is quite simple and has most of the concepts that can be found in other script languages like python, ruby, php, etc. You will have your standard IFs and LOOPs as well as variables, functions, objects, etc. Because Lua is embedable you will also have something called an user type which is a type of data that comes from the language that Lua is being embeded in. For the case of C++ if you define your own class you will be able to use this class in Lua and lua will refer to it as an user type data.
So here is some example lua code:
-- Single line comment with two dashes -- Defining a variable and assigning a value answer_to_universe = 42 -- Loops while answer_to_universe > 0 do answer_to_universe = answer_to_universe - 1 end -- If clause if answer_to_universe == 0 then -- prints to console using print print("answer_to_universe is 0") elseif answer_to_universe == 42 then print("answer_to_universe is 0") else pritn("answer_to_universe is anything else") end -- Booleans x = true opposite_of_x = not x if opposite_of_x == false then print("x = true") end -- Functions function speed(distance, time) speed = distance / time return speed end -- Etc.
I will not get more into details about the lua code. If you want to learn more about lua you can check out Learn Lua in Y minutes. You can also use the repl below to test out some syntaxis and rules from the lua language:
As you may have noticed already Lua is a scripting language. Scripting languages are evaluated at runtime and are converted into instructions then. This is different compared to C++ where the application has to be compiled first. The compilation process in bigger C++ applications can take a lot of time ranging from seconds to hours. This is why you would like to separate some of your gameplay logic and code that can change a lot into Lua scripts.
Raw Lua code in C++
Lua is based on a stack machine. What this means is that it will read the script and execute line by line. It will put the result of each line on top of each other and you can then read the value that is on top. You will need to define your Lua stack machine by creating a new Lua state:
// Creates our lua state machine lua_State * L = lua_open(); // Includes some default libraries like for math to our state luaL_openlibs(L); //...other code... // Don't forget to clean the state in the end lua_close(L);
After we have the state created we would like to execute code inside it. We would essentially fetch some Lua code from a file and pass it on the Lua state. To do this we write the following lines:
// Our script in lua const char* lua_script = "print('Hello World!')"; // Load buffer will load the lua text into the lua state int load_stat = luaL_loadbuffer(L, lua_script, strlen(lua_script), lua_script); // Will call the buffer code that we just loaded lua_pcall(L, 0, 0, 0);
This approach though useful is actually quite hard to scale easily. You would have to keep track of the Lua state object and also pass it around so that you can call other functions on it. There is also the inconvenience that you have to fully understand the stack operations that Lua does because you are loading your operations and providing values to that stack and then getting your results back from it.
To explain this better I will give you an example of creating a simple add function in Lua and then calling it from C++. You will notice that we need to push manually using a C++ command each argument for the function and then also get the result back ourselves after we execute it.
const char* lua_function_script = R"LUA( function add(x, y) return x + y end )LUA"; // execute script int load_stat = luaL_loadbuffer(L, lua_function_script, strlen(lua_function_script), lua_function_script); lua_pcall(L, 0, 0, 0); int a = 5; int b = 6; int sum; // fetch the function lua_getglobal(L, "add"); // pass two numbers to lua as arguemnt 1 and 2 lua_pushnumber(L, a); lua_pushnumber(L, b); // call the function with 2 arguments and receive 1 result lua_call(L, 2, 1); // read the restul from the top of the stack and cast to int sum = (int)lua_tointeger(L, -1); lua_pop(L, 1); std::cout << "Result: " << sum << std::endl;
You can take a look of the full example in the Repl below. The project below is configured using CMake and manages the Lua dependency as I’ve described in other articles in this blog. I’ve downloaded Lua from its official github repository which is only a mirror of their real source control system.
Lua code using Sol2
You can of course make it easier by using a library called sol2. This library makes it extremely easy to create and manage lua states, push new code to execute, query functions defined in lua and execute them from C++ and register your own C++ types and functions inside of Lua
What sol2 will do is wrap all Lua calls into handy functions that you will better understand. You will also be able to go deeper if needed as sol2 will expose the lua_State* object to you. That way you will be able to call the raw lua functions if needed.
To provide an example compared to the raw version of Lua in C++ you would write the following code using sol2:
// Create the state sol::state lua; // Our script in lua const char* lua_script = "print('Hello World!')"; // ...we just execute it lua.script(lua_script); // ...and the real magic... const char* lua_function_script = R"LUA( function add(x, y) return x + y end )LUA"; lua.script(lua_script); sol::function addFunction = lua["add"]; int sum = addFunction(5, 6); std::cout << "Result: " << sum << std::endl;
As you can see the approach with using the library produces much cleaner and simpler code. Sol2 is a lot more powerful than that but that is a topic for another article.
Now you yield the power to embed Lua in your C++ application. I am using this approach extensively in my own private engines to simplify scene setups and settings where I don’t always want to wait for compile times. It is much simpler and faster to write Lua code than C++ code.
There are some minor drawbacks though. Mainly it is that you would have to register every class defined in C++ that you would like to use in Lua. There is no easy reflection in C++ and that results in you having to implement some design patterns to allow every class to register its members to the Lua state.
Even with this drawback in mind Lua is an extremely simple and fast option. It will improve the quality of life of every game developer that is working in custom C++ code.