propaganda

Notes on Lua

Avid readers of my blog and followers of mine probably can tell by now that most of my personal projects are written in C and Lua.

Would these be the the languages I’d take with me if I had to leave on a deserted island? The answer to that is a resounding yes.

Some orthodox C++, JS or even 16-bit assembly might rear their ugly head every now and then, but those instances can be considered the exceptions rather than rules.

What the heck is orthodox C++? If you asked that question, then you are in luck because I just happen to know the location of this perfect place, where you’ll find the answers you’ve been looking for your entire life.

With that said, I thought that I’d put together some notes on Lua in the hopes that it might help total newcomers, as well as people who might have dabbled with it to a certain extent in the past, but then decided not to go with it for one reason or another.

2022 is really the year of second chances, therefore perhaps these notes might change your mind and you’ll give Lua another try.

Lua is really an excellent companion language and one of the best (if not the best) languages out there for making your games and apps script-able.

It’s one of those languages where embeddability isn’t just an after thought or an unintended happy consequence, but it’s actually a core part of the language.

If I had to describe Lua in three words, I’d probably say something along the lines of compact, fast and flexible.

Without any further ado, let’s get started.

Non-zero index based arrays

When talking about Lua, one of the first, if not the absolute first thing that comes up is the non-zero index based arrays.

Languages where arrays start at 1 instead of 0 are evil and yada, yada! You probably heard this spiel a million times over.

While, this is a totally valid point to discuss, it’s really not that big of a deal as it might appear to the unsuspecting eye.

Why? Well, first of all nothing stops you from using 0 as an index in the first place so long you are aware of the implications of doing so.

local t = {}

for i=0,3 do
	t[i] = i
end

The code above is perfectly valid Lua. Now, what are the caveats or the implications?

But, once again this is truly a non-issue for all intents and purposes. The mental shift that people say that is required is an imaginary one. Take my word for it.

When would you actually need zero based array indexing? I can think of two specific scenarios.

In both of these cases you could really just do a +1 and everything would be just fine. On the other hand, you will most likely have a higher level interface that is more convenient to use rather than litter the entire code-base with inline index or offset computations.

If this was THE MAJOR THING that has been preventing you from trying out Lua, just ignore it and you might find yourself pleasantly surprised.

Procedural vs Functional vs OOP

Another thing that is sometimes brought up as kind of a negative when it comes to Lua is the fact that it doesn’t really forces you to pick and/or use any particular so called design patterns or styles.

Obviously, Lua is not the only language out there that does this, but I feel like Lua is still special in some ways when it comes to this because of its dynamic nature.

I also consider this fact to be one its strengths rather than shortcomings.

Procedural

Let’s take a look at a purely procedural way of doing things first.

function init_monster(t)
	t.health = 100
	t.speed = 50
	t.position = { x = 0, y = 0 }
end

function move_monster(t)
	local p = t.position
	p.x = p.x + t.speed
	p.y = p.y + t.speed
end

local t = {}
init_monster(t)
move_monster(t)

Another variation on this theme could something like this:

function create_monster()
	local t = {
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }     
	}
	return t
end

local t = create_monster()
move_monster(t)

One could also do away with the so called constructor altogether and just declare the table inline.

local t = {
	health = 100,
	speed = 50,
	position = { x = 0, y = 0 }     
}

move_monster(t)

Alternatively, one could also use a combination of inline declaration and an anonymous function.

local t = (function()
	return {
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }     
	}
)()
    
move_monster(t)

Functional

What about all that totally amaze-balls functional hotness? No problem at all, Lua got you covered.

function map(t, f)
	local result = {}

	for i, v in ipairs(t) do
		result[i] = f(v, i)
	end
    
	return result
end

function create_monster(id)
	local t = {
		id = id,
		health = 100,
		speed = 50,
		position = { x = 0, y = 0 }     
	}
	return t
end

function move_monster(t)
	local p = t.position
    
	p.x = p.x + t.speed
	p.y = p.y + t.speed
    
	return t
end

function add_log(message, f)
	return function(v)
		print(message, v.id)
		return f(v)
	end
end

map(
	map(
		{ 1, 2, 3 },
		create_monster
	),
	add_log(
		"moving monster",
		move_monster
	)
)

OOP

And now, what everybody has been waiting for. Drum-rolls! Object Oriented Programming, or Brain Damage Oriented Programming as I like to call it these days.

Once your mind has been infested with the OOP paradigm it’s always impossible to rid of it, at least not without significant efforts.

local CLASS_METATABLE <const> = {
	__call = function(self, ...)
		local instance = setmetatable({}, self)
        
		if type(instance.init) == "function" then
			instance:init(...)
		end
        
		return instance
	end
}

function class(klass, super_klass)
	local klass = klass or {}
    
	for k, v in pairs(super_klass or {}) do
		klass[k] = v   	    
	end
    
	klass.__index = klass
	return setmetatable(klass, CLASS_METATABLE)
end

Monster = class(Monster)
Monster.init = function(self, name, kind, speed)
	self.name = name
	self.kind = kind
	self.speed = speed or 5
	self.position = { x = 0, y = 0 }
    
	return self
end
Monster.move = function(self, x, y)
	local p = self.position
	local speed = self.speed
    
	p.x = p.x + speed
	p.y = p.y + speed
    
	return self
end

Zombie = class(Zombie, Monster)
Zombie.init = function(self, name)
	return Monster.init(self, name, "zombie")
end
Zombie.print = function(self)
	print(self.kind, self.name)
	return self
end

local zombie = Zombie("peter")
zombie:move():print()

DSL

Now, what if you want a DSL to describe your monster types in a nice data-oriented fashion? Lua really shines when it comes to this, considering that it has its roots as a data description language of sorts.

local MONSTER_TYPE_METATABLE <const> = {
	__call = function(self, t)
		assert(t.name ~= nil, "monster type needs a valid name")
		assert(self[t.name] == nil, "duplicate monster type")
    
		self[t.name] = t
		table.insert(self, t)
        
		return t
	end
}

monster = setmetatable({}, MONSTER_TYPE_METATABLE)

monster {
	name = "zombie",
	speed = 5
}

monster {
	name = "ogre",
	speed = 15
}

for _, v in ipairs(monster) do
	print(v.name, v.speed)    
end

print("ogre speed", monster.ogre.speed)

One nice feature that makes this possible is that the paranthesis around functions can be omitted if said function has a single string or table argument.

This feature makes Lua perfect for creating DSLs of all sorts.

Metatables and Metamethods

Both OOP and DSL examples above made use of so called metatables as well as metamethods in the form of __call in order to achieve the desired and necessary syntactic sugar.

A metatable is table that contains one or more metamethods and then is set as the metatable of another table.

Any given table at any given time can have a single metatable associated with it, which can be set and retrieved by setmetable and getmetable respectively.

A prime example that I always like to give to illustrate the concept is defining a metatable that implements the __tostring metamethod in order to provide pretty-printing capabilities.

local sprintf = string.format

local METATABLE <const> = {
	__tostring = function(self)
		return sprintf("{ x = %d, y = %d }", self.x, self.y)
	end
}

t = setmetatable({ x = 11, y = 22 }, METATABLE)
print(t)

With this setup, print(t) will print out { x = 11, y = 22 } instead of table: 0xdeadbeef.

Even if you do not intend to do any heavy meta-programming like in the examples given above, you should still take the time and familiarize yourself with the concept of metatables and metamethods.

You can learn more about metatables and metamethods by clicking here.

Coroutines

Lua comes with support for coroutines out of the box. I won’t go into a lot of details and will provide only a relatively simple and easy to understand example below.

function monster_wait(self, amount)
	local t = 0
	while t < amount do
		coroutine.yield()
		t = t + sys.delta_time 
	end
end

function monster_think(self, dt)
	while true do
		local player = monster_find_player(self)
        
		if player then
			print('shooting at player')
			monster_shoot_at_player(self, player)
		end
       
		monster_wait(self, 0.5)
	end
end

local t = create_monster()
local co = coroutine.create(monster_think)
coroutine.resume(co, t, dt)
coroutine.resume(co, t, dt)
coroutine.resume(co, t, dt)

One interesting thing to note here is that one can yield anywhere from within the coroutine, even from another function as illustrated by the monster_wait function.

You can learn more about coroutines by clicking here.

Modules and Packages

Lua doesn’t have any special keywords or language features per-say when it comes to providing support for modules and packages alike.

If you think about it, both modules and packages are just Lua tables containing functions.

local math = require "math"
print(math.pi)

Sometimes the words library, module and package are used interchangeably when referring to modules.

So, how is this implemented behind the scenes?

The require function, which is just a good old regular function, ends up looking for the required module or package by parsing two variables. Namely package.path and package.cpath.

> package.path
./?.lua;./?/init.lua
> package.cpath
./?.so

What is the difference between path and cpath? I am pretty sure that you guessed already, cpath is used to look for Lua native extensions in the form of dynamic linking libraries or shared objects, while path is exclusively for looking up Lua source files, including pre-compiled ones with luac given that they still use .lua as an extension.

There’s no real difference as far as the user is concerned between the two. You just call require and whatever matches first will be returned, or if the given module cannot be found in either paths an error that indicates this fact will be raised.

It should go without saying that both of these variables can be modified at run-time and the changes will take effect the next time require is called with module name that hasn’t been loaded or required previously.

Another interesting bit of trivia that I’d like to mention is the fact that by looking at the directory separator one can determine the platform the Lua interpreter. On Windows this is going to be \ and on Unix-likes it’s going to be / obviously.

This trick is sometimes used in Lua scripts designed to be executed via the command line Lua interpreter, also known as lua or lua.exe respectively.

If you have a Lua script called monster.lua then you just require it like this.

-- without parenthesis
local monster = require "monster"

-- with parenthesis
local zombie = require("zombie")

Of course this assumes that your monster.lua looks something like this.

local M = {}

M.create = function(name)
	return { name = name }
end

return M

What about packages? If you have a directory called monster with a Lua script called init.lua in it, then once again you just require the same way.

local monster = require "monster"

This in in turn will load the init.lua from within that directory.

Remember that monster.lua takes priority over monster/init.lua as per the patterns defined in package.path, so if you happen to have both of them within the very same directory, it will end up requiring monster.lua instead.

What if you want to be able to require from other sources like a ZIP archived backed virtual file system?

You can create your own custom loader and insert into package.loaders before or after the default loader as you see fit as illustrated in the example below.

function create_zip_archive_loader(archive_filename)
    local archive = assert(zip.open(archive_filename, "r"))
    
    return function(name)
        local filename = string.format(
            "%s.lua",
            string.gsub(name, "%.", "/")
        )

        local contents = archive:read(filename)
        if contents then
            return assert(loadstring(contents, filename))
        end

        return string.format(
            "could not find '%s' in zip archive '%s'",
            filename,
            archive_filename
        )
    end
end

table.insert(package.loaders, 2, create_zip_archive_loader("scripts.zip"))

Not too shabby right?

In most cases you will really want to just roll your own import, especially when it comes to games where you will also want sandboxing and you are almost always going to be loading from a virtual file system of some description.

With all that being said, I’d recommend creating your own import function and exposing that instead of the built-in require.

function create_import(archive_filename)
    local archive = assert(zip.open(archive_filename, "r"))
    
    return function(name)
        local filename = string.format(
            "%s.lua",
            string.gsub(name, "%.", "/")
        )
        local contents = assert(archive:read(filename))
        return assert(loadstring(contents, filename))
   	end
end

import = create_import("scripts.zip")

-- use it just like you would the built-in `require`
local monster = import "monster"

Sandboxing

Sandboxing is another thing that Lua can do without the need for any complicated or fancy language features.

If you worked with Lua before you probably stumbled upon the mysterious _G table, which is really an alias to the table that serves as the global environment when the lua_State is first createda nd initialized.

Any global variables or functions that one defines will end up in this table. The alias is provided for convenience purposes, more exactly cases like these:

As a result, sandboxing in its most basic and pure form is simply a matter of shadowing the global environment table with another table called _ENV defined in the local scope.

function sandbox()
	local _ENV = {
        print = print
    }
    
    return function(message)
    	print(message)
        print(_VERSION)
    end
end

sandbox()("hello world")

If you run this, you’ll see that it will print hello world and nil, because the global constant _VERSION wasn’t included in the _ENV table thus it is not accessible from within the returned function. Which is really a closure, but more on that later.

In most real world scenarios however, you’ll probably never set _ENV by hand and let loadstring or loadfile do it internally while loading the desired chunk and setting its upvalue to the desired environment.

And now, I am going to illustrate sandboxing via loadfile by combining the DSL and import examples shown above in order to give you a more realistic approach on how one would go about it.

local MONSTER_TYPE_METATABLE <const> = {
	__call = function(self, t)
		assert(t.name ~= nil, "monster type needs a valid name")
		assert(self[t.name] == nil, "duplicate monster type")
    
		self[t.name] = t
		table.insert(self, t)
        
		return t
    end
}

function create_monster_importer()
	local env = {
		monster = setmetatable({}, MONSTER_TYPE_METATABLE),
		print = print
	}

	env.import = function(name)
		local filename = string.format(
			"%s.lua",
			string.gsub(name, "%.", "/")
		)
		local chunk = assert(loadfile(filename, "tb", env))
		return chunk()
	end
    
	return env
end

local monster_importer = create_monster_importer()

monster_importer:import("monsters/ogre")
monster_importer:import("monsters/zombie")

for i, v in ipairs(monster_importer.monster) do
	print(i, v.name, v.speed)    
end

Then monsters/ogre.lua could look like this.

monster {
    name = "ogre",
    speed = 15
}

monster {
    name = "zombie_boss",
    speed = 20
}

And finally monsters/zombie.lua which is more of the same but in a different Lua script.

monster {
    name = "zombie",
    speed = 5
}

monster {
    name = "zombie_boss",
    speed = 10
}

Please note how we pass in the env table as the last argument of loadfile. The loadstring function supports the same arguments as well and can be used in the same manner.

This env table ends up overriding the global environment by altering the upvalue of the loaded chunk and only the functions and/or variables defined within said table will be available within said chunk when executed.

In other words, trying to call any other functions than monster, print and import will result in an error, simply because nothing else has been defined in the env table.

Also, any variables, functions that might be defined within the loaded chunks, they also end up in the env table rather than the global environment table.

Obviously, if we are talking about user generated content like in the case of Roblox, then obviously additional measures have to taken, like preventing infinite loops and so forth, but in most other locally moddable games and/or apps, this is more than sufficient to provide a safe, intuitive and a super convenient data-driven way to define configuration, entities, levels, maps and so on.

C <-> Lua interaction

I feel like we traveled down far enough through the proverbial rabbit hole at this point to bring the bi-directional interaction between C and Lua to the table in the context of embedding the Lua interpreter inside a game or an application.

Considering that this is quite a big topic to tackle, I’ll try to be as concise as possible without sacrificing too much in terms of details that are important to gain a better understanding of how things are glued together.

What I noticed is that most people end up under or over exposing and never really taking the time to find just the right balance that actually make sense and feels what I like to call Lua-like for whatever its worth.

The over exposing is generally done by people who happen be using C++. Which of course is to be expected, considering that any prolonged exposure to C++ or OOP in general is well known to cause permanent and irreversible brain damage.

It’s never ever desirable to expose C++ classes 1:1 in a purely OOP fashion. I am not your mum, and I can’t tell you what to do with your adult life, but PRETTY PLEASE, DO NOT EVER DO THIS, if for nothing else but respect for you fellow human beings you happen to be sharing this piece of rock floating in space with.

And even more importantly, do not use one of those “auto-class-binding generator” like libraries. If your mind just took you to a very dark place called BOOST, please find the nearest church and pour some holy water on over your head, but it might be too late, so please don’t hold your breath.

Writing your own bindings by hand is the way to go, even if it might look intimidating at first glance.

Calling Lua functions from C

However, before go into all that let’s take a look at how one would go about calling Lua functions from C.

In the case of a game, if you have done things right, you do not ever really need more than 3 global functions to serve as trusty entry points into the world of Lua.

By taking this route, you make it very easy to profile how much time is spent in Lua every frame, which is a very important and rather convenient side effect to consider.

local escape_key = nil

function on_init(argv)
    -- call once on init
    escape_key = input_key_index("escape")
end

function on_frame(dt, w, h)
    -- called once every frame
    if input_key_pressed(escape_key) then
		print("key pressed") 
    end
end

function on_quit()
	-- called once on quit    
end

On the C side of things, you create your lua_State and then lookup and call the 3 functions.

Proper error handling has been largely omitted for purposes of brevity.

lua_State *L = luaL_newstate();
if(L == NULL)
{
    // fatal error failed to create Lua state
    return -1;
}
// load and initialize the Lua standard library
luaL_openlibs(L);

if(!script_on_init(L, argc, argv))
{
    // fatal error so just return early
    return -1;
}

while(running)
{
    double dt = /* calculate delta time in ms/sec */;
    
    process_os_events();
    
    script_on_frame(L, dt, w, h);
    
    // swap GL buffers
    // vsync and/or apply frame limiting
}

script_on_quit(L);

lua_close(L);
L = NULL;

Now, you might be tempted to ask, should one even call process_os_events() on in C or simply expose it to Lua and then let the user decide if they care about processing events during any particular frame, or perhaps also pick and choose which events they might want to process.

This would also mean that one doesn’t have to change the change and recompile the C side of things in situations where certain events are irrelevant for the current game. For example, if the game doesn’t support joysticks, then there’s no reason to process anything joystick related.

function on_frame(dt, w, h)
	for event in poll_events() do
		if event.type == EVENT_TYPE_KEYDOWN && event.key == 27 then
    		print("key pressed")  
		end
	end
end

Both approaches have their pros and cons. I’ll let you make up your own mind about which one resonates more with your particular style and needs.

Alright, but how do you actually call any of these Lua functions from C (the host game/application)?

bool script_on_init(lua_State *L, const int argc, const char *argv[])
{
    int i, ret;
        
 	if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return false;
    }
    
   	lua_createtable(L, argc, 0);
    for(i = 0; i < argc; i++) 
    {    
        lua_pushstring(L, argv[i]);
        lua_rawseti(L, -2, i);
    }
    
    ret = lua_pcall(L, 1 /* one argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
    
    return true;
}

Alright, I admit it that there’s a lot going on and I’ll try to point everything out almost in a line by line fashion, so bear with me. It will be worth it in the end.

The first order of business is to lookup the on_init function by name in the global environment, which should sound familiar since I mentioned it briefly when I talked about sandboxing above.

if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
{
	lua_pop(L, 1);
    return false;
}

This is done by using the lua_getglobal function, which conveniently pushes the value of the key "on_init" into the stack and then returns the type of said value.

Before going much further, it’s worth noting that the Lua C API is stack based, which means that one interacts with the lua_State by pushing and popping values into and from aforementioned stack.

Values pushed into the stack can be referred to by a numeric index. This index can be positive or negative.

A positive index can be used to refer to values from the bottom of the stack going in an upwards direction, while a negative index can be used to refer to values from the top of the stack going in a downwards direction.

So let’s rewrite the above piece of code in a slightly different way to illusrate the use of a negative index when referring to a value at the top of stack, which translates to index -1.

lua_getglobal(L, "on_init");
if(!lua_isfunction(L, -1))
{
	lua_pop(L, 1);
    return false;
}

Here we ignore the return value of lua_getglobal and use the lua_isfunction to find out if the value pushed into the top of the stack by lua_getglobal is of type LUA_TFUNCTION.

The usage of lua_pop should be self explanatory and it is simply used to pop values off the top of he stack.

One important distinction to note here is that the second argument is the actual count of values to pop and it doesn’t represent a stack index.

Now there’s a third way to rewrite this lookup that we just did which also illustrates popping two values of the top of the stack in one fell swoop.

lua_pushglobaltable(L);
if(lua_getfield(L, -1, "on_init") != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Of course we could also just ignore th return value of lua_getfield and then use lua_isfunction in order to determine the type of said value just like we did in the previous implementation.

Speaking of lua_isfunction, I want to point out that the fact that it’s just a good old fashioned convenience macro, which expands to an expression making use of the lua_type function.

#define lua_isfunction(L, n) (lua_type(L, (n)) == LUA_TFUNCTION)

Alright, let’s take a look at a forth variation of the lookup.

lua_pushglobaltable(L);
lua_pushstring(L, "on_init");
if(lua_gettable(L, -2) != LUA_TFUNCTION)
{
    lua_pop(L, 2);
	return false;   
}
lua_pop(L, 1);

Wait a minute, why are we still popping 2 and then 1 off the stack? Shouldn’t we also account for the value pushed by lua_pushstring?

No, because lua_gettable pops it off the stack and then pushes the resulting value of the lookup in its place.

Instead of lua_pushstring could have also used lua_pushliteral which is another macro that ensures that its argument is a compile-time constant string literal and not a run-time variable.

#define lua_pushliteral(L, s) lua_pushstring(L, "" s)

This is done by the means of a preprocessor trick that makes the compiler throw an error when s is not a compile-time constant string literal.

How does it do that? Well, we know that the preprocessor is kind enough and happens concatenate adjacent compile-time constant string literals, but will refuse to concatenate a variable and a string literal due to the expression not being computable at compile-time.

And now it’s time for the fifth and final version of looking up our on_init function. It’s true, cross my heart and hope to die or pinkie promise, whichever you happen to fancy.

If we look at lua_pushglobaltable we notice that it also happens to be a macro.

#define lua_pushglobaltable(L) \          
    ((void) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS))

Casting to (void) is just another preprocessor trick to silence any unwanted compiler warnings regarding unused return values.

You might have seen this used for doing the same when it comes to unused variables.

#define UNUSED(x) (void) (x)

int main(int argc, char *argv[])
{
    UNUSED(argc);
    UNUSED(argv);
    
    return 0;
}

Syntatic macro sugar and spice is what makes your code look pretty and nice. I am a poetic genius, aren’t I? I am sure you agree, so let’s carry on. Time is of the essence.

lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS));
lua_pushliteral(L, "on_init");
if(lua_rawget(L, -2) != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Please ignore the LUA_REGISTRYINDEX pseudo-index for now, we’ll come back to it in a second and all will be revealed before your eyes.

The lua_rawgeti and lua_rawget operate raw on a given table hence their name.

What this means is that they do not call any of the table’s metamethods like __index when doing lookups. Of course if the table in question doesn’t have a metatable or doesn’t implement the __index metamethod then it doesn’t really matter, but I am sure you get the idea.

The lua_gettable and lua_getfield functions on the other hand will end up calling the metamethods in question.

When to use one versus the other? Generally speaking you’d use the raw access methods when implementing metamethods themselves to avoid infinite loops, or when you simply don’t care about metamethods at all.

It should go without saying that raw access has less overhead especially if the defined metamethods are heavy duty and do otherwise complicated lookups.

What if the function that we wanted to lookup isn’t in the global environment table, but rather in a module or package.

game = game or {}
game.on_init = function(argv)
	-- noop    
end

Given the five examples above, I think we can kind of tell how to go about that.

if(lua_getglobal(L, "game") != LUA_TTABLE)
{
    lua_pop(L, 1);
    return false;
}
if(lua_getfield(L, -1, "on_init") != LUA_TFUNCTION)
{
    lua_pop(L, 2);
    return false;
}
lua_pop(L, 1);

Easy peasy, lemon squeezy.

Now that we mastered the dark arts of looking up the function we want to call, written our thesis and gotten our well deserved PhD, it’s time to move on to the next phase, which involves preparing the argument that we want to call the function with.

lua_createtable(L, argc, 0);
for(i = 0; i < argc; i++) 
{    
	lua_pushstring(L, argv[i]);
    lua_rawseti(L, -2, i);
}

First we create a new table and push it into the stack by calling lua_createtable.

The second and third arguments allow us to reserve memory for numeric and key indices. These come handy when we happen to know the expected or approximate amount of elements that we wish to insert into said table.

This of course can also help us reduce the number of extraneous memory allocations as the table grows during the insertion process of the elements.

In our particular case we know the exact amount we want and that is equal to argc which is the number of command line arguments that were passed to our executable.

Then we simply iterate over the arguments and set each argument at a given numeric index by the meas of lua_rawseti which just like its companion getter lua_rawgeti will not try to invoke any of the metamethods as we established before.

The second argument to lua_rawseti is set to -2 because the table we just created is on position two downwards from the top of the stack.

After it sets the value, lua_rawseti will also pop the string we previously pushed into the stack.

You might have noticed that we start at index 0 rather than 1, which is not a typo by the way. Why?

function on_init(argv)
	for _, arg in ipairs(argv) do
       -- noop 
    end
    print(argv[0])
end

This allows us access to the name of the executable if we need it, without having to worry about skipping the first argument when working with the table in every other case.

function on_init(argv)
	for i=2, #argv do
		local arg = argv[i]
       	-- noop
    end
    print(argv[1])
end

Now that our table (array) of command line arguments is ready, we can go ahead and finally call our on_init function.

ret = lua_pcall(L, 1 /* one argument */, 0, 0);
if(ret != 0)
{
	fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
    lua_pop(L, 1);
    return false;
}

When lua_pcall is called, it will pop both the function and the number of arguments indicated by the first argument off the stack.

If there have been any errors with calling the function it will return a non zero value and push the error message into the top of the stack, which we can retrieve and display the user in some form or another.

The p in lua_pcall comes from protected, meaning that it is guaranteed to return safely in case of any errors and unwind the stack as expected.

What if we wanted to allow on_init to return an error code and then propagate that up to main() and use it as an exit code?

function on_init(argv)
	-- noop
    return -1
end
int script_on_init(lua_State *L, const int argc, const char *argv[])
{
    int i, ret;
        
 	if(lua_getglobal(L, "on_init") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return false;
    }
    
   	lua_createtable(L, argc, 0);
    for(i = 0; i < argc; i++) 
    {    
        lua_pushstring(L, argv[i]);
        lua_rawseti(L, -2, i);
    }
    
    ret = lua_pcall(L, 1 /* one argument */, 1 /* one return value */, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
    
    if(lua_isnumber(L, -1))
    {
        ret = lua_tonumber(L, -1);
    }
    
    lua_pop(L, 1);
    return ret;
}

int main(int argc, char *argv[])
{
    int ret;
    lua_State *L;
    
    ret = script_on_init(L, argc, (const char **) argv);
    if(ret != 0)
    	return ret;
    
    // ...
    
    return ret;
}

The first thing that you might notice is that lua_pcall now has its third argument set to 1 which is the number of return values we expect on_init to have.

ret = lua_pcall(L, 1 /* one argument */, 1 /* one return value */, 0);

The second thing to call out is the fact that we check if said return value is a number. If it is, then we grab the actual value and pop it off the stack.

if(lua_isnumber(L, -1))
{
	ret = lua_tonumber(L, -1);
}
lua_pop(L, 1);

This enables us to also not have to explicitly return anything in on_init and in such cases we will just assume that the return value is equal to 0.

function on_init(argv)
	-- noop
end

Obviously, we could also just print an error and return a specific error code in case it wasn’t a number if we so desired. But, personally I prefer to make things optional when it makes sense to do so.

Now let’s take a look at how would one go about calling on_quit.

void script_on_quit(lua_State *L)
{
    int ret;
    
    if(lua_getglobal(L, "on_quit") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
		return;
    }
    
    ret = lua_pcall(L, 0, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
    }
}

I don’t think that on_quit needs any discussion or clarifications, it should all self explanatory.

And, finally the implementation of calling on_frame.

void script_on_frame(
	lua_State *L,
    const double dt,
    const int w,
    const int h
)
{
    int ret;
        
 	if(lua_getglobal(L, "on_frame") != LUA_TFUNCTION)
    {
        lua_pop(L, 1);
        return;
    }

    lua_pushnumber(L, dt);
    lua_pushinteger(L, w);
    lua_pushinteger(L, h);
    
    ret = lua_pcall(L, 3 /* arguments argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
}

The only thing we haven’t see before is the fact that on_frame expects and receives 3 arguments, which are pushed in order into the stack and then lua_pcall is passed in 3 as its second argument.

Considering this function is going to be called every frame, which can mean 60 or more times per second, depending on the frame rate, it would be nice to avoid having to do the lookup by name every single time.

A way to do this is by grabbing a reference which is then stored in the global registry that I mentioned before and told you that we’ll come back to later.

if(lua_getglobal(L, "on_frame") != LUA_TFUNCTION)
{
	lua_pop(L, 1);
    return;
}

int on_frame_ref = luaL_ref(lua, LUA_REGISTRYINDEX);

The idea behind this is to grab the reference once on startup and then just reuse said reference avoiding the more expensive global lookup on every frame.

It is also worth pointing out that grabbing a reference will prevent Lua from garbage collecting the object or function in question that the reference is pointing to.

The sister function luaL_unref can be used to release a reference once we are done with it.

So let’s take a look at how this affects the implementation of script_on_frame.

void script_on_frame(
    lua_State *L,
    const int ref,
    const double dt,
    const int w,
    const int h)
{
    int ret;
        
 	lua_rawgeti(L, LUA_REGISTRYINDEX, ref);

    lua_pushnumber(L, dt);
    lua_pushinteger(L, w);
    lua_pushinteger(L, h);
    
    ret = lua_pcall(L, 3 /* arguments argument */, 0, 0);
    if(ret != 0)
    {
        fprintf(stderr, "Lua Error:\n%s\n", lua_tostring(L, -1));
        lua_pop(L, 1);
        return false;
    }
}

Calling C functions from Lua

Now that we know how to call Lua functions from C, it is time to dive into exposing some C functions that then can be called from Lua.

int create_window(lua_State *L)
{
    return 0;
}

Notice the signature of the function, it has a single argument which is the lua_State and returns an int.

The return value indicates how many values it should pop from the stack and return to the caller. In the example above, this is 0, meaning that the function is not expected to push anything into the stack and as a direct result not expected to return anything.

Let’s take a look at a more full fledged example.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    
    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    
   	if(!os_window_create(title, w, h))
    {
        lua_pushboolean(L, false);
        lua_pushliteral(L, "window creation failed");
        return 2; 
    }
    
    lua_pushboolean(L, true);
    lua_pushnil(L);
    return 2;
}
local ok, err = create_window("My Window", 640, 480)
if not ok then
   print(err) 
end

local ok = assert(create_window("My Window", 800, 600))

The create_window function takes 3 arguments, a string and two integers, which we use retrieve the values of by calling the family of luaL_checktype functions.

These simply check the type of the value in the stack at the given index, if it matches the expected type, then the value is returned, otherwise a type-check like error is raised.

title = luaL_checkstring(L, 1);
w = luaL_checkinteger(L, 2);
h = luaL_checkinteger(L, 3);

We could have also used negative stack indexing if we so desired.

title = luaL_checkstring(L, -3);
w = luaL_checkinteger(L, -2);
h = luaL_checkinteger(L, -1);

These two are obviously equivalent. The question now arises when to use one versus the other?

My recommendation would be to use negative stack indexing when looking up arguments only in the case of auxiliary and/or utility type functions which cannot make any assumptions about the stack other than what is currently at the top.

Take a look at the example below.

int set_window_title(lua_State *L)
{
    const char *title;
    
    title = luaL_checkstring(L, -1);
    
    os_set_window_title(title);
    
    return 0;
}

lua_pushliteral(L, "Nice Window");
set_window_title(L);
lua_pop(L, 1);

The second thing to note about the implementation of create_window is how it returns a tuple (fancy word for two values) rather than one as indicated by the return value of 2.

This so called pattern is used by the built-in Lua standard library and its kind of an unwritten rule when it comes to handling certain types of errors without raising an fatal error explicitly by calling the luaL_error, luaL_typerror, and luaL_argerror family of functions.

In order to catch errors raised by these functions one must make use the two protected flavors of lua_call, namely lua_pcall and lua_xpcall respectively.

Once more the quintessential question arises in the form of when to use one versus the other?

My recommendation is to use the tuple approach in cases when the error is about dealing with an external resource of some sort (i.e file system error) that can be retried and might not be considered a fatal error, while raising actual errors via the means of luaL_error should be reserved for actual fatal errors that might require stopping execution of the script or even the host application altogether.

local f, err = io.open("test.lua", "rb")
if not f then
	print(err)    
end

local f = assert(io.open("test.lua", "rb"))

The use of strings rather than numerical constants when passing in options (flags) to a certain function is yet another common pattern used by the built-in Lua standard library, as well as third party libraries.

While this makes total sense and seems like a reasonable thing to do, and in many cases is probably what one would end up doing anyway, in the context of a game, if a particular function is called every frame, it’s probably not desirable have a whole lot of strcmp happening in there.

With that in mind, I’d recommend to use good old numerical constants that can be OR-ed together as flags; which is what one would do on the C side of things anyway in this particular situation.

#define BIT(x) (1 << (x))
#define _STRINGIFY(x) #s
#define STRINGIFY(x) _STRINGIFY(x)

enum
{
    WINDOW_FULLSCREEN = BIT(0),
    WINDOW_RESIZABLE  = BIT(1),
    WINDOW_CENTERED   = BIT(2)
    // ...
};

#define script_set_constant(L, c) \
	do \
	{ \
		lua_pushinteger(L, c); \
		lua_setglobal(L, STRINGIFY(c)); \
	} while(0)

// ...

script_set_constant(L, WINDOW_FULLSCREEN);
script_set_constant(L, WINDOW_RESIZABLE);
script_set_constant(L, WINDOW_CENTERED);
// ...

The BIT helper macro should be self explanatory as it shifts 1 with N given as an argument giving us a series of power of twos.

In the case of STRINGIFY we apply an extra level of indirection by wrapping it in another macro to ensure that it always evaluates to what we expect it to.

This indirection is often used also when defining a macro for concatenation.

#define _CONCAT(a, b) a ## b
#define CONCAT(a, b) _CONCAT(a, b)

Finally, the do { ... } while(0) within the script_set_constant macro is a standard trick for achieving multi-line macros that work with single-line statements without an explicit { ... } block.

if(dev_mode)
    script_set_constant(L, DEV_MODE);

But, if you have written any non-trivial amounts of C in your natural life time, these so called tricks should already be in your daily arsenal and not unfamiliar in any shape or form.

On the Lua side our create_window function now can be called by OR-ing together the flags.

create_window("My Window", 640, 480, WINDOW_RESIZABLE | WINDOW_CENTERED)

In the sample implementation above, create_window simply returns true or false depending if the window creation has succeeded or not. This is fine if you only ever have one window, but what if you want to have more than one?

Well, in that case you probably want to return a handle or a pointer which then can be passed in as an argument to a function like close_window.

This is where so called userdata enters the picture. Lua happens to offer two flavors of userdata.

Feature(s) Light userdata Full userdata
GC (garbage collection) no yes
Metatables no yes
__gc metamethod no yes
__eq metamethod no yes
== (equality) same “pointer” same “object” or via __eq

Let’s re-implement create_window by using light userdata.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_t *window;
    
    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);
    
    window = os_window_create(title, w, h, flags);
    if(window == NULL)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2; 
    }
    
    lua_pushlightuserdata(L, window);
    lua_pushnil(L);
    return 2;
}

Let’s implement the close_window function now in order to showcase how one would use said light userdata in another function.

int close_window(lua_State *L)
{
    os_window_t *window;
    
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    
    window = lua_touserdata(L, 1);
    
    if(window != NULL)
        os_window_close(window);
    
    return 0;
}

It should be self evident that light userdata is just a good old C pointer. Which means that we can also store any sort of value as light userdata so long the size of value in question happens to be less than or equal to the size of sizeof(void *) or uintptr_t.

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_handle_t window;
    
    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);
    
    window = os_window_create(title, w, h, flags);
    if(window == OS_WINDOW_INVALID)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2; 
    }
    
    lua_pushlightuserdata(L, (void *) (uintptr_t) window);
    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    os_window_handle_t window;
    
    luaL_checktype(L, 1, LUA_TLIGHTUSERDATA);
    
    window = (os_window_handle_t) (uintptr_t) lua_touserdata(L, 1);
    
    if(window != OS_WINDOW_INVALID)
        os_window_close(window);
    
    return 0;
}

Now it is time to re-implement the create_window and close_window functions by using full userdata.

typedef struct
{
    os_window_t *window;
    uint32_t flags;
} window_userdata_t;

#define WINDOW_METATABLE "WINDOW *"

int close_window(lua_State *L);

static const luaL_Reg window_metamethods[] = {   
	{ "__gc", close_window },            
	{ "__close", close_window },                                     
	{ NULL, NULL }
};

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    os_window_t *window;
    window_userdata_t *window_userdata;
    
    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);
    
    window = os_window_create(title, w, h, flags);
    if(window == OS_WINDOW_INVALID)
    {
        lua_pushnil(L);
        lua_pushliteral(L, "window creation failed");
        return 2; 
    }
    
    window_userdata = lua_newuserdata(L, sizeof(window_userdata_t));
    if(window_userdata == NULL)
        return luaL_error(L, "out of memory");
    
    window_userdata->window = window;
    window_userdata->flags = flags;
    
    if(luaL_newmetatable(L, WINDOW_METATABLE))
        luaL_setfuncs(L, window_metamethods);
    
    lua_setmetatable(L, -2);

    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    window_userdata_t *userdata;
    
   	userdata = luaL_checkudata(L, 1, WINDOW_METATABLE);
    if(userdata == NULL)
        return luaL_typeerror(L, 1, WINDOW_METATABLE);
    
    if(userdata->window != NULL)
    {
        os_window_close(userdata->window);
        userdata->window = NULL;
    }
    
    return 0;
}

The first thing that should stand out here is the use of lua_newuserdata which is used to allocate memory in order to hold our window_userdata_t struct.

The second thing that might stand out is this lovely piece of code.

if(luaL_newmetatable(L, WINDOW_METATABLE))
	luaL_setfuncs(L, window_metamethods);
    
lua_setmetatable(L, -2);

The luaL_newmetatable will attempt to lookup if there is a metatable with the provided name, if there is one it will push it into the top of the stack and return 0, otherwise it will create a brand new one and return 1.

In the case it ended up creating a new one, which would be the very first time create_window was called, we want to initialize the metadata and set the relevant metamethods we care about, namely __gc and __close. We do this by callingluaL_setfuncs with the array of function definitions.

We’ll come back and go into more detail when it comes to luaL_setfuncs later.

Finally, lua_setmetatable will set this metatable as the metatable of the newly created full userdata.

We could of course move this out of the create_window function and do it once when we setup the initial lua_State, in which case setting the metatable inside create_window could be simplified down to calling luaL_setmetatable instead of lua_setmetatable. Notice the uppercase L.

luaL_setmetatable(L, WINDOW_METATABLE);

Another thing I’d like call out is the way the userdata is being retrieved in close_window.

userdata = luaL_checkudata(L, 1, WINDOW_METATABLE);
if(userdata == NULL)
	return luaL_typeerror(L, 1, WINDOW_METATABLE);

The luaL_checkudata function will return NULL if the one of the following conditions is true:

Due to the fact that luaL_checkudata doesn’t raise an unexpected type error on its own, we end up doing so ourselves by calling the luaL_typeerror function.

Why didn’t we explicitly cast the userdata to window_userdata_t *, is that a typo? No, in C one doesn’t have to cast to and from void *, due to the fact that such casts are considered to be implicit.

The question that we are faced with yet again is when to use one versus the other?

I’d say, go with light userdata when you want to do explicit and manual resource management , otherwise go with full userdata especially when garbage collection is needed or is desirable.

Personally, I prefer to use simple opaque integer identifiers, instead of userdata of any kind, except when I need upvalues, in which case light userdata can come in very handy, but more on that later down the line.

typedef union
{
	uint64_t opaque;
    struct
    {
		uint64_t type     : 8;
        uint64_t reserved : 24;
		uint64_t index    : 32;
	};
} id_t;

int create_window(lua_State *L)
{
    const char *title;
    int w, h;
    uint32_t flags;
    id_t id;
    
    title = luaL_checkstring(L, 1);
    w = luaL_checkinteger(L, 2);
    h = luaL_checkinteger(L, 3);
    flags = luaL_checkinteger(L, 4);
    
    id = os_window_create(title, w, h, flags);
    
	lua_pushinteger(L, id.opaque);
    lua_pushnil(L);
    return 2;
}

int close_window(lua_State *L)
{
    id_t id;
    
  	id.opaque = luaL_checkinteger(L, 1);
    
    os_window_close(id);
    return 0;
}

This approach also reduces the need to litter explicit and verbose error handling everywhere, because you are always guaranteed to get an id.

In case of any errors you just get an id of zero, which is then safe to pass in as an argument to any related functions and is guaranteed to result in a no-op.

On a side note, I just realized the fact that I never brought up how to handle optional arguments.

int create_window(lua_State *L)
{
    const char *title;
    int n, w = 640, h = 480;
    uint32_t flags = 0;
    id_t id;
    
    title = luaL_checkstring(L, 1);
    
    n = lua_gettop(L);
    if(n > 1)
    {
    	w = luaL_checkinteger(L, 2);
    	h = luaL_checkinteger(L, 3);
   		
        if(n > 3)
    		flags = luaL_checkinteger(L, 4);
    }
    
    id = os_window_create(title, w, h, flags);
    
	lua_pushinteger(L, id.opaque);
    lua_pushnil(L);
    return 2;
}

The lua_gettop function can be used to get the number of elements pushed into the stack. We can use this information to determine how many arguments have been pushed into the stack when our function was called.

It’s really as simple as that. Personally, I also like to use lua_gettop to ensure that the function leaves the stack in a clean state, especially after performing a lot of operations that alter the stack in one way or another.

int table_map(lua_State *L)
{
    int n;
    
    n = lua_gettop(L);

    /* call functions that operate on the stack */
    
    assert(lua_gettop(L) == n);
    
	/* push any return values on the stack */
    return 1;
}

In other words, stack hygiene is essential and can save you from a myriad of weird, subtle and hard to debug issues that might arise when not practiced and taken as seriously as one should.

None of the bindings that we ended up implementing and looking at worked with any global state and it’s high time to change that considering that in any game and app one will have some manner of global state or shared context of some description to work with.

typedef struct
{
    bool running;
    bool vsync;
} game_t;

int game_set_vsync(lua_State *L)
{    
    game->vsync = luaL_checkboolean(L, 1);   
    return 0;
}

Isn’t the use of global variables static or otherwise considered bad practice? The answer to that question is yes, however not always.

Two particular scenarios come to mind, when in my opinion it’s perfectly fine to use global variables.

Case one, they are static, confined to a single compilation unit (read .c source file) and thead-safety has been considered and handled as necessary (i.e locking read/write via a mutex).

Case two, they are not static but marked as const and extern-ed as such via a declaration in a header file.

However, the use of non static and non const global variables that can be changed from any compilation unit should be avoided at all cost, unless you are a time traveler who also happens to be coding in C like it’s the great year of 1989.

What if the state is not a static global variable? This is where the so called upvalues that I mentioned above come into play.

But, what are these mysterious upvalues exactly?

In simplest terms they are akin to local variables accessible within the confines of a closure.

function create_id_generator()
	local id = 0
    
    return function()
    	id = id + 1
        return id
    end
end

local id = create_id_generator()
print(id())
print(id())

In the example above you can think of the local variable id as the upvalue of the anonymous function (closure) being returned.

This would be roughly equivalent of a local static variable declared within a function in C.

So let’s rewrite game_set_vsync by using an upvalue to a pointer to our game state.

typedef struct
{
    bool running;
    bool vsync;
} game_t;

int game_set_vsync(lua_State *L)
{
    game_t *game;

    game = lua_touserdata(L, lua_upvalueindex(1));
    game->vsync = luaL_checkboolean(L, 1);
    
    return 0;
}

We could also check for the type of the upvalue, but this is largely unnecessary in most cases.

luaL_checktype(L, lua_upvalueindex(1), LUA_TLIGHTUSERDATA);

Another useful fact to be aware of when it comes to upvalues is that they do not live on the actual stack and that the lua_upvalueindex function is just a convenience helper macro that returns a pseudo-index.

#define lua_upvalueindex(i)	(LUA_REGISTRYINDEX - (i))

Speaking of closures, let’s take a quick detour and look at the implementation of iterators.

local function odd()
   	local f = function(t, i)
		i = i + 1
        local v = t[i]
        while v do
			if v & 1 == 1 then
                return i, v
            else
                i = i + 1
                v = t[i]
            end
        end
    end
    
	return function(t)
    	return f, t, 0 
    end
end

return odd()

And now let’s see the iterator in action.

local odd = require "odd"

local t = { 33, 32, 13 }
for i, v in odd(t) do
    print(i, v)
end

This will iterate over all the elements of the array, but will yield only when it happens to encounter an element that is odd.implemention

1	33
3	13

The expression v & n == 1 is equivalent to v % n == 1 but only when n is power of two.

Iterators compatible with the in keyword always return a triple, in our case return f, t, 0.

The iterator closure that is called on every iteration, the table that is being iterated on and finally the initial value of the current index.

Closures of course can also be created from C by the means of lua_pushcclosure, but we’ll talk more about that later.

Now that the deep dive into writing bindings has been more or less completed, it is time to check out how to actually expose them in order to make them callable from the Lua.

The first and obvious approach is to just set them in the global environment, by using the reverse of what we did when looking up functions as illustrated in the Calling Lua from C section above.

Except that instead of getglobal, getfield, getttable, rawget you’d end up using the sister setter family of functions, namely setglobal, setfield, settable and rawget respectively.

Not going to go through absolutely all the permutations yet again, I am sure that you get the basic idea behind what I am driving at.

int create_window(lua_State *L);

lua_pushcfunction(L, create_window);
lua_setglobal("create_window");

Exposing a C closure with an upvalue isn’t much more complicated either.

int create_window(lua_State *L);

game_t *game = /* game state */;

lua_pushlightuserdata(L, game);
lua_pushcclosure(L, game, 1);
lua_setglobal(L, "game_set_vsync");

What about exposing some sort of an integer constant? You got a taste of this already when we talked about exposing options and flags.

#define GAME_VERSION 0x100

lua_pushinteger(L, GAME_VERSION);
lua_setglobal(L, "GAME_VERSION");

In most cases you will very likely have more than one function to expose, and just so it happens that Lua has a helper function that comes to our rescue.

I am talking about luaL_setfuncs of course, which we also used to set the metamethods on our metatable above.

static const luaL_Reg window_funcs[] = {
    { "create_window", create_window },
    { "close_window", close_window },
    { NULL, NULL }
};

lua_pushglobaltable(L);
luaL_setfuncs(L, window_funcs, 0);
lua_pop(L, 1);

An important detail to call out is the { NULL, NULL } sentinel value, which is used to designate the end of the static array of function definitions.

The third argument of luaL_setfuncs indicates the number of upvalues that should be pushed for every function defined in the array, which is zero in this particular case due to the fact that the functions in question do not need or use an upvalue.

However, if they did then we’d push the light userdata and set the third argument to 1 like it can be observed in the example below.

static const luaL_Reg window_funcs[] = {
    { "game_set_vsync", game_set_vsync },
    { "game_set_running", game_set_running },
    { NULL, NULL }
};
game_t *game = /* game state */;

lua_pushglobaltable(L);
lua_pushlightuserdata(L, game);
luaL_setfuncs(L, window_funcs, 1);
lua_pop(L, 1);

luaL_setfuncs will pop the upvalues of the stack, leaving only the table at the top, which is why we pass 1 instead of 2 to lua_pop.

Nothing is stopping us from rolling our own luaL_setfuncs and luaL_Reg if that’s what we fancy.

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
#define SCRIPT_FUNC(f) { STRINGIFY(f), f }
#define SCRIPT_FUNC_NULL { NULL, NULL }

typedef struct
{
    const char *name;
    lua_CFunction func;
} script_func_t;

static const script_func_t window_funcs[] = {
    SCRIPT_FUNC(game_set_vsync),
    SCRIPT_FUNC(game_set_running),
    SCRIPT_FUNC_NULL
};
game_t *game = /* game state */;

void script_set_funcs(
	lua_State *L,
	const script_func_t *func,
    game_t *game
)
{
    lua_pushglobaltable(L);

    for(; func->name != NULL; func++)
    {
 		lua_pushlightuserdata(L, game);
        lua_pushcclosure(L, func->func, 1);
        lua_setfield(L, -2, func->name);
    }
    
    lua_pop(L, 1);
}

script_set_funcs(L, window_funcs, game);

lua_CFunction is just convenience function pointer typedef as one would expect.

typedef int (*lua_CFunction)(lua_State *L);

If the use of the { NULL, NULL } sentinel is still giving you heavy anxiety and cold sweats, let’s re-implement script_set_funcs without it in order to ease your pain.

#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#define SCRIPT_FUNC(f) { STRINGIFY(f), f }

typedef struct
{
    const char *name;
    lua_CFunction func;
} script_func_t;

static const script_func_t window_funcs[] = {
    SCRIPT_FUNC(game_set_vsync),
    SCRIPT_FUNC(game_set_running)
};
static const size_t window_funcs_size = ARRAY_SIZE(window_funcs);
game_t *game = /* game state */;

void script_set_funcs(
	lua_State *L,
	const script_func_t *func,
    const size_t size,
    game_t *game
)
{
    lua_pushglobaltable(L);

    for(const script_func_t *end = func + size; func != end; func++)
    {
 		lua_pushlightuserdata(L, game);
        lua_pushcclosure(L, func->func, 1);
        lua_setfield(L, -2, func->name);
    }
    
    lua_pop(L, 1);
}

script_set_funcs(L, window_funcs, window_funcs_size, game);

Better than a healthy dose of XANAX on a Friday night, right? I certainly hope so.

The second approach we could take, which I am sure you guessed already is to group these functions into modules and then expose those modules. This is what the Lua standard library is doing.

static const luaL_Reg script_window_module_funcs[] = {
    { "create", create_window },
    { "close", close_window },
    { NULL, NULL }
};

int script_window_module(lua_State *L)
{
    luaL_newlibtable(L, script_window_module_funcs);
    luaL_setfuncs(L, script_window_module_funcs, 0);
    return 1;
}

static const luaL_Reg script_modules[] = {
    { "window", script_window_module },
    { NULL, NULL }
};

void script_set_modules(lua_State *L, const luaL_Reg *module)
{
    for(; module->name != NULL; module++)
    {
        luaL_requiref(L, module->name, module->func, 1);
        lua_pop(L, 1);
    }
}

script_set_modules(L, script_modules);

The first thing to talk about here is the luaL_newlibtable macro.

#define luaL_newlibtable(L, l) \
	lua_createtable(L, 0, sizeof(l) / sizeof((l)[0]) - 1)

It creates a table and preallocates enough memory for the number of functions we intend to set in the table. Notice how it accounts for the { NULL, NULL } sentinel value, hence the -1 at the end of the expression.

The second thing to bring forth is the proverbial fourth argument of the luaL_requiref function which is set to 1 (true), which in turn means that each module will also be set in the global environment table.

asset(window.create("Window", 800, 600, WINDOW_FULLSCREEN))

If we set this to 0 (false) then it is necessary to explicitly require the module on the Lua side before it can be used.

local window = require "window"
asset(window.create("Window", 800, 600, WINDOW_FULLSCREEN))

It should be crystal clear to you by now that pretty much everything can be implemented in more than one way, with each implementation having their own pros and cons depending on what you need.

So, global functions or modules? I’d say go with global functions if what you are doing is more akin to data-driven configuration than anything else, otherwise go with modules.

The End

While this turned out to be a much more comprehensive guide of some sorts than what I initially had in mind, it still only manages to scratch the surface when it comes to a great many aspects.

Please take everything outlined here with a grain of salt and do not consider it as some authoritative manual to be followed to the letter, or else all hell will break loose and the four horsemen of the apocalypse will be unleashed upon the world.

To learn more and dive into some of the specific details about Lua, please take a look at the excellent Lua Reference Manual and Programming in Lua.

Until next time, please remember to always, and I mean absolutely always to pop() your pushes() or to push() your pops(), I always forget which way it is.

Anyways, take whatever happens to resonate more with you and your own personal style.


2022-12-04