adding your own types

Sometimes, overriding sol to make it handle certain struct’s and class’es as something other than just userdata is desirable. The way to do this is to take advantage of the 4 customization points for sol. These are sol_lua_check, sol_lua_get, sol_lua_push, and sol_lua_check_get.

These are template class/structs, so you’ll override them using a technique C++ calls class/struct specialization. Below is an example of a struct that gets broken apart into 2 pieces when going in the C++ –> Lua direction, and then pulled back into a struct when going in the Lua –> C++:

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

#include <iostream>
#include "assert.hpp"

struct two_things {
	int a;
	bool b;
};

template <typename Handler>
bool sol_lua_check(sol::types<two_things>, lua_State* L, int index, Handler&& handler, sol::stack::record& tracking) {
	// indices can be negative to count backwards from the top of the stack,
	// rather than the bottom up
	// to deal with this, we adjust the index to
	// its absolute position using the lua_absindex function 
	int absolute_index = lua_absindex(L, index);
	// Check first and second second index for being the proper types
	bool success = sol::stack::check<int>(L, absolute_index, handler) && sol::stack::check<bool>(L, absolute_index + 1, handler);
	tracking.use(2);
	return success;
}

two_things sol_lua_get(sol::types<two_things>, lua_State* L, int index, sol::stack::record& tracking) {
	int absolute_index = lua_absindex(L, index);
	// Get the first element
	int a = sol::stack::get<int>(L, absolute_index);
	// Get the second element, 
	// in the +1 position from the first
	bool b = sol::stack::get<bool>(L, absolute_index + 1);
	// we use 2 slots, each of the previous takes 1
	tracking.use(2);
	return two_things{ a, b };
}

int sol_lua_push(sol::types<two_things>, lua_State* L, const two_things& things) {
	int amount = sol::stack::push(L, things.a);
	// amount will be 1: int pushes 1 item
	amount += sol::stack::push(L, things.b);
	// amount 2 now, since bool pushes a single item
	// Return 2 things
	return amount;
}

int main() {
	std::cout << "=== customization ===" << std::endl;
	std::cout << std::boolalpha;
	
	sol::state lua;
	lua.open_libraries(sol::lib::base);

This is the base formula that you can follow to extend to your own classes. Using it in the rest of the library should then be seamless:

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
	// Create a pass-through style of function
	lua.script("function f ( a, b ) print(a, b) return a, b end");

	// get the function out of Lua
	sol::function f = lua["f"];

	two_things things = f(two_things{ 24, false });
	c_assert(things.a == 24);
	c_assert(things.b == false);
	// things.a == 24
	// things.b == true

	std::cout << "things.a: " << things.a << std::endl;
	std::cout << "things.b: " << things.b << std::endl;
	std::cout << std::endl;

	return 0;
}

And that’s it!

A few things of note about the implementation: First, there’s an auxiliary parameter of type sol::stack::record for the getters and checkers. This keeps track of what the last complete operation performed. Since we retrieved 2 members, we use tracking.use(2); to indicate that we used 2 stack positions (one for bool, one for int). The second thing to note here is that we made sure to use the index parameter, and then proceeded to add 1 to it for the next one.

You can make something pushable into Lua, but not get-able in the same way if you only specialize one part of the system. If you need to retrieve it (as a return using one or multiple values from Lua), you should specialize the sol::stack::getter template class and the sol::stack::checker template class. If you need to push it into Lua at some point, then you’ll want to specialize the sol::stack::pusher template class. The sol::lua_size template class trait needs to be specialized for both cases, unless it only pushes 1 item, in which case the default implementation will assume 1.

Note

It is important to note here that the gett, push and check differentiate between a type T and a pointer to a type T*. This means that if you want to work purely with, say, a T* handle that does not have the same semantics as just T, you may need to specify checkers/getters/pushers for both T* and T. The checkers for T* forward to the checkers for T, but the getter for T* does not forward to the getter for T (e.g., because of int* not being quite the same as int).

In general, this is fine since most getters/checkers only use 1 stack point. But, if you’re doing more complex nested classes, it would be useful to use tracking.last to understand how many stack indices the last gett/check operation did and increment it by index + tracking.last after using a stack::check<..>( L, index, tracking) call.

You can read more about the extension points themselves over on the API page for stack, and if there’s something that goes wrong or you have anymore questions, please feel free to drop a line on the Github Issues page or send an e-mail!