Using Lua with C++ in practice. Part 2. Implementing the basics

Hello, this is a second part of “Using Lua with C++ in Practice” series. I recommend to read the first part to know what’s going on and why using Lua with C++ is awesome! (You’ll see it in this arcticle too, of course).

This tutorial will implement a simple ECS model which I’ll improve and use in other parts of tutorials. Note that this is not the best way to implement ECS. I’m making the most basic ECS which can be used to show the main principles of how things work in my game and how you can use them in general. This is not a tutorial about C++, so I won’t spend too much time discussing C++ parts and I’ll mostly focus on Lua/C++ interaction and binding. Let’s start!

Here’s how we’ll create an entity using this script with LuaBridge (or a binding of your choice).

Get a list of component names. In our case it would be {“GraphicsComponent”, “NpcComponent”}.

Create each component, passing corresponding component table reference in component’s constructor. Component constructors will get data from those tables.

Add components to entity.

Geting a list of table keys

To create components, we need to get a list of entity’s table keys and iterate over it, creating components.

This is not as easy as it sounds. Most bindings I’ve used don’t have something like getTableKeys function. If your binding has it, lucky for you! Skip to the “Creating components by string” section.
(But I recommend you not to do this, this section will show you some cool stuff you can do with Lua and will remind you of some basic stuff of Lua/C++ interaction, mainly of Lua’s stack.).

You can also skip this section if you are okay with using less convenient syntax for entities. The table may look like this:

This function gets table t as the parameter and then returns a table which contains its keys.
Note, that Lua tables can have any non-nil value as a key. So a returned table may contain integers, strings and even other tables! We’ll just assume that the tables which we’ll get keys from would only contain strings for now (I’ll later show how you can ignore non-string keys)

Pops a key from the stack, and pushes a key–value pair from the table at the given index (the “next” pair after the given key). If there are no more elements in the table, then lua_next returns 0 (and pushes nothing).

We also have to check if the value we just got is a string before we convert it to a string.
Okay, now we only need to remove s table which is still on the stack and return the vector:

lua_settop(L, 0); // remove s table from stack
return keys;
}

Okay, this should work. But this only works with global tables. What if you need to get values from the table which is inside another table? There’s a solution to this too!

Suppose the table we want to get keys of is in the table someTable:

someTable = {
interestingTable = {
...
}
}

We’ll call getTableKeys function like this:

auto v = luah::getTableKeys(L, "someTable.interestingTable");

Unfortunately this wouldn’t work for the implementation I’ve given above. That’s because we can’t do this:

lua_getglobal(L, "someTable.interestingTable");

Which our function would try to do. Let’s change this.

Suppose “getKeys” function is already in the stack.
Fist, we have to call lua_getglobal(L, “someTable”) and then lua_getfield(L, “interestingTable”).
Here’s what we’ll have on stack after doing this:

If we call lua_pcall(L, 1, 1, 0) then someTable would be used as a function parameter, not interestingTable!
So, we need to remove someTable from the stack before we can do this.

Let’s write a function which would let us get tables on stack like this:

We use dots as separators. The first token of the variableName string is a name of a global table which we get with lua_getglobal. Then we get other tables which are one inside the other with lua_getfield.
Suppose we write this:

We need to remove other tables (player, pos) from stack if needed. We do this by placing the table which we needed to get in the position where the global table (player in our case) was (we can’t just call lua_replace(L, 0) because something may already be in the stack!). Then we call lua_settop which pops all the unneeded tables from stack.

So, now we need to change this line in luah::getTableKeys:

lua_getglobal(L, name);

to this:

lua_gettostack(L, name);

And our function will work for all tables. Okay, let’s get back to creating components and entities.

Creating components

Okay, now we can get a list of components to create. But how are we going to create them?
First of all, you need to find a way to create component instances by strings. There are lots of Object Factory implementations, so I won’t provide any, I’ll relay on if/else for simplicity instead.

And now let’s change GraphicsComponent’s and NpcComponent’s default constructors to this:

GraphicsComponent(luabridge::LuaRef& componentTable);

NpcComponent(luabridge::LuaRef& componentTable);

componentTable is a reference to a table from the script which contains data about entity’s component.
Look at the ghost.lua. We’ll pass reference to ghost.GraphicsComponent in GraphicsComponent constructor and reference to ghost.NpcComponent in NpcComponent constructor.

But wait, there’s more!

And that’s it for now. There are lots of things to make while I’m writing the next tutorial. Try to make this system work with some libraries like SFML. Create a EntityFactory class to make entity creation better, create a bunch of components. Experiment and send your feedback to me.
Next time I’ll show you how to call C++ functions from Lua and do this effectively.

Hope you’ve enjoyed the tutorial!
Subscribe to my blog or to my twitter to not miss the next part of the tutorial.
Thanks for reading!

Thanks! No, you can’t “unload” a script, you can only remove global variables in your lua_State. So, you have to get list of all global variables in a script and remove them. I’ll show how to do it in the next article. :)

Yeah, I can agree with unique_ptr, I’ll fix it a bit later. undordered_map is not neccessary, though, because Entities always have around 5-10 components, comparing two std::type_index’es is fast, so std::map will do fine and will require less memory than std::unordered_map.

Hello! Great articles, really, but i’m having difficulties understanding a certain part.

Let’s say my GraphicsComponent is dependant on a InputComponent(which takes keyboard input) to decide which sprite animation to draw. How would this work? Doing something like gc->setInputComponent(component); seems like a bad idea to me. Do you have any recommendations on how to do this?

Hi! The easiest way to decouple InputSystem from everything else is to use events. So, when a player presses the button, you create a “ButtonPressed” event and all systems which are interested in it, get it and process it. For example, GraphicsSystem gets this ButtonPressed event and then sets the needed animation. (But this is too simple, I think you’ll need to have some sort of StateSystem, which’ll get input events and then change the animations)
If the systems are very coupled and use several components very often, you can do such system without any problems, just store two lists of components together! (For example: PhysicsSystem may work on PositionComponent and MovemenComponent and CollisionComponent)

Hello, I was wondering how you would implement a function in your component? i.e. On the last tutorial an image of the ghosts lua file showed CollisionComponent having a collide function. Thanks for the great tutorials!

I am not sure if you will read this post because this tutorial is a bit old.
Anyway, I created a namespace which includes those two functions which you wrote into your main.cpp, i. e. loadEntity and addComponent.
Unfortunately, i get an error in that line: LuaRef entityTable = getGlobal(L, type.c_str());
The error says: error: cannot convert ‘lua_State*’ to ‘luabridge::lua_State*’ for argument ‘1’ to ‘luabridge::LuaRef luabridge::getGlobal(luabridge::lua_State*, const char*)’

I am confused why there is the function luabridge::getGlobal with the luabridge::lua_State as parameter instead of the normal lua_State.