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_size<T>, sol::stack::pusher<T, C>, sol::stack::getter<T, C>, sol::stack::checker<T, sol::type t,  C>.

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++:

 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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>

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

struct two_things {
	int a;
	bool b;
};

namespace sol {

	// First, the expected size
	// Specialization of a struct
	template <>
	struct lua_size<two_things> : std::integral_constant<int, 2> {};

	// Then, specialize the type
	// this makes sure Sol can return it properly
	template <>
	struct lua_type_of<two_things> : std::integral_constant<sol::type, sol::type::poly> {};

	// Now, specialize various stack structures
	namespace stack {

		template <>
		struct checker<two_things> {
			template <typename Handler>
			static bool check(lua_State* L, int index, Handler&& handler, 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 = stack::check<int>(L, absolute_index, handler)
					&& stack::check<bool>(L, absolute_index + 1, handler);
				tracking.use(2);
				return success;
			}
		};

		template <>
		struct getter<two_things> {
			static two_things get(lua_State* L, int index, record& tracking) {
				int absolute_index = lua_absindex(L, index);
				// Get the first element
				int a = stack::get<int>(L, absolute_index);
				// Get the second element, 
				// in the +1 position from the first
				bool b = 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 };
			}
		};

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

	}
}

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:

 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
int main() {
	std::cout << "=== customization ===" << std::endl;
	std::cout << std::boolalpha;
	
	sol::state lua;
	lua.open_libraries(sol::lib::base);

	// 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 getter, pusher and checker 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 getter/checker operation did and increment it by index + tracking.last after using a stack::check<..>( L, index, tracking) call.

You can read more about the structs 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!