Mod Support #2 – Embedding Lua into C++

This is part of a series of posts revolving around mod support, the introduction and links to the other posts can be found here.

Our current game in development, Solitude, is a cooperative multiplayer action survival game. Since its a coop game we use a game server that is bundled with the client to allow players to host their own servers and it’s also used for the single player experience. From the very beginning we wanted to give players the maximum mod support we were able to so we decided both the game client and server would be moddable. As our game server is written in C++ that meant embedding and integrating Lua into C++. I’ll write about integrating Lua into Unity as a separate post.

 

A Faster Lua – LuaJIT

Lua is the scripting language we decided to integrate into our game’s scripting layer for code logic control. The aim is for the scripting layer to control all gameplay logic while the core, C++, layer will control generic non-gameplay game logic and time critical algorithms. To gain the speed increases discussed in the last post we decided to go with LuaJIT, which is a Just-In-Time compiler for Lua.

LuaJIT is distributed as source code so to gain a binary version you need to compile it against your compiler of choice. Our server is developed using the C++ compiler MinGW x64 and since we want it to be as cross platform as possible we use the POSIX threads version. What this means is that the threading library used is cross platform instead of using the default Windows threading library when on Windows.

LuaJIT can have some trouble compiling if you’re using the wrong variation of MinGW. For LuaJIT to compile correctly you need to use the SEH and not the SJLJ variation. SEH and SJLJ are two different types of exception handling systems and if you try using the SJLJ version it just won’t compile at all. After that’s compiled you need to link your C++ project against LuaJIT and run some examples to test everything is working. If you are already using core Lua then it’s a simple drop-in replacement.

 

Bringing Object-Orientation to Lua

Lua is a powerful, fast, lightweight, embeddable scripting language but Lua is not an object-oriented language.

The above is a pretty powerful statement, especially since today so many languages are object-oriented. Ideally we would like to use all the advantages of Lua but still have object-orientation. So… is that possible? Yes, by using Lua’s flexible table data structure and some clever libraries. To help us save time we use a library called classes.lua. It allows you to construct classes in Lua that can be passed around and used in a way that feels natural to anyone used to object-orientation.

For an example, at the top of your intended lua class file you’d add:

Door = classes.class();

Provide a constructor:

function Door:init() 
	-- code
end

and then when you want to create an object from the class you’d call:

local door = classes.class(Door);

and it’s as easy as that. You can then call methods on the object like you would in any object-oriented language but with the syntax of:

door:open();

Without classes.lua you wouldn’t be able to do this in Lua (unless you coded it yourself). By bringing in object-orientation it allows us to better bind our core and scripting layers together and make an easy to use and flexible modding framework. I’d highly recommend you look at classes.lua, or the many other OO libraries, for Lua.

 

Binding Lua and C++: The API

Once you have LuaJIT and object-orientation working in your project you need to consider how you want modders to interact with your game. Generally it’s a very good idea to create a level of abstraction so to make things easier for modders. Make C++ do the heavy lifting where possible and allow it to present the results to the scripting layer when requested or required.

When you want to start coding your API you quickly find yourself asking questions like “How on Earth do I get Lua to understand C++ objects? Can my Lua objects be sent back into C++ for processing?”. Welcome to the world of Lua / C++ binding.

Lua does not understand your C++ classes unless you code it to understand it. Binding, in this example, is the process of exposing the C++ classes to Lua. It allows Lua to understand your C++ code and there are two ways to bind C++ and Lua together.

Manual Binding

Manual binding means you write the entire binding yourself. There is a pretty infamous and painfully sharp post about why you should take this approach instead of automatic binding. You can read it here but to summarise they paint automatic binding in a bad light. I happen to disagree with the post but it does raise some valid points. At the end of the day, manual binding is harder and takes much more time to complete. If you’re a small team there will always be the cost vs. benefit argument. For us, manual bindings didn’t bring any real advantages.

Automatic Binding / Glue

Automatic binding / glue means the binding is automatically created for you. Most binding libraries simplify the process massively and provide various advantages depending on the library used. I won’t go into all the libraries as there are too many out there but each take a slightly different approach to binding. There will be a performance hit for using a library but a lot of libraries take performance very seriously and do their best to keep things as fast as possible.

For Solitude we used an automatic binding library called LuaBridge. After a lot of research and evaluation I found that this worked best for us and was the right balance of sensible syntax, functionality and performance. When using LuaBridge, you link it to your C++ project then register the classes you wish to expose to Lua. For example,

void ScriptAPI::registerEventManager(lua_State* state) {
	getGlobalNamespace(state)
			.beginNamespace("event")
				.beginClass<event::EventManager>("EventManager")
					.addFunction("registerEventHandler", &event::EventManager::lua_registerEventHandler)
					.addFunction("registerEvent", &event::EventManager::lua_registerEvent)
					.addFunction("triggerClientEvent", &event::EventManager::lua_triggerClientEvent)
					.addFunction("triggerEvent", &event::EventManager::lua_triggerEvent)
				.endClass()
			.endNamespace();
}

The above code registers the Solitude C++ EventManager so it’s accessible to Lua. You can pick and choose what methods you expose on the class so it’s flexible if you only want to expose select functionality to Lua. You specify the method names and how you’d like to access them from Lua. The method signature is automatically extrapolated by the library. This extrapolation can cause some issues with method overloading but there are workarounds for that. From Lua, you’d access the EventManager as follows:

-- Door was opened so trigger the door opened event
EventManager:triggerEvent("ON_DOOR_OPENED");

Even from this simple example you can already see how powerful and easy it is to bind C++ and Lua together with LuaBridge. Things can become even more powerful when you expose your custom objects and actually create them in Lua instead of C++, then feed them back into C++ for processing. That’ll be for a later blog post though.

After LuaJIT, classes.lua and LuaBridge are in your project you can slowly create your scripting API. Knowing what should be in the core layer and what should be in the scripting layer takes time as there is not always a clear boundary. Hopefully this helps create a clearer understanding of what it takes to embed Lua into C++. Thanks for reading. Feel free to get in contact with me on the comments, email or grab me on Twitter at @CWolf.

 

Links

Lua – Scripting language
LuaJIT – Just-In-Time version of Lua
classes.lua – Object-orientation library for Lua
LuaBridge – C++/Lua binding library
MinGW-w64 C++ Compiler – C++ compiler

Leave a Reply

You must be logged in to comment.

Interfacing with UI #4 – Coherent UI

February 5th, 2016

This is part of a series of posts revolving around user interface design and development, the introduction and links to the other posts can be found here. Last I wrote about user interfaces I discussed the new Unity UI system and I wrote about our process of porting from Daikon Forge to it. That was a year and a half ago and a lot has changed since then. To keep things interesting we decided to move from Unity UI (yet another move?!) to Coherent UI and I’ll explain why we did it. Why Move… Again?!... (read more)

@SolitudeGame: Status update: Fuel reserves low. Asteroid mining facility detected on sensors. No response to our communications. On approach station appears to be abandoned and running on emergency power only. Away mission approved. Mission objective: Search and salvage - fuel is a priority.

23/03/2022 @ 11:00am UTC

@RogueVec: And so it begins! #RebootDevelop

19/04/2018 @ 8:05am UTC

@RogueVec: We'll be at @RebootDevelop this year. We can't wait! If you want to hang out just give us a shout! #RebootDevelop2018 #GameDev

16/04/2018 @ 12:06pm UTC

@SolitudeGame: Fullscreen terminals allow you to hook into your ship's guns for fine control! Moddable gun modules, terminals and UI! https://t.co/B5N01jrA70 #GameDev

8/12/2017 @ 4:58pm UTC

@CWolf: Woo! And, now we have a cross-compiled (nix --> win64) @SolitudeGame server in our build and deploy pipeline #RV #GameDev

28/11/2017 @ 3:39pm UTC