aboutsummaryrefslogtreecommitdiffstats
path: root/lib/sol2/examples/source/metatable_customization.cpp
blob: c259598f1b7c2b740739b3752367fb0f3121b6d0 (plain)
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#define SOL_ALL_SAFETIES_ON 1
#include <sol/sol.hpp>

#include <iostream>

struct thing {
	int member_variable = 5;

	double member_function() const {
		return member_variable / 2.0;
	}
};

#define TEMPLATE_AUTO(x) decltype(x), x

// cheap storage: in reality, you'd need to find a
// better way of handling this.
// or not! It's up to you.
static std::unordered_map<sol::string_view, sol::object> thing_function_associations;
static std::unordered_map<sol::string_view, sol::object> thing_variable_associations;

void register_thing_type(sol::state& lua) {
	thing_variable_associations.emplace_hint(thing_variable_associations.cend(),
		"member_variable", sol::object(lua.lua_state(), sol::in_place, &sol::c_call<TEMPLATE_AUTO(&thing::member_variable)>)
	);
	thing_function_associations.emplace_hint(thing_function_associations.cend(),
		"member_function", sol::object(lua.lua_state(), sol::in_place, &sol::c_call<TEMPLATE_AUTO(&thing::member_function)>)
	);

	struct call_handler {
		static int lookup_function(lua_State* L) {
			sol::stack_object source(L, 1);
			sol::stack_object key(L, 2);
			if (!source.is<thing>()) {
				return luaL_error(L, "given an incorrect object for this call");
			}
			sol::optional<sol::string_view> maybe_svkey = key.as<sol::optional<sol::string_view>>();
			if (maybe_svkey) {
				{
					// functions are different from variables
					// functions, when obtain with the syntax
					// obj.f, obj.f(), and obj:f()
					// must return the function itself
					// so we just push it realy into our target
					auto it = thing_function_associations.find(*maybe_svkey);
					if (it != thing_function_associations.cend()) {
						return it->second.push(L);
					}
				}
				{
					// variables are different than funtions
					// when someone does `obj.a`, they expect
					// this __index call (this lookup function) to
					// return to them the value itself they're seeing
					// so we call out lua_CFunction that we serialized earlier
					auto it = thing_variable_associations.find(*maybe_svkey);
					if (it != thing_variable_associations.cend()) {
						// note that calls generated by sol3 
						// for member variables expect the stack ordering to be
						// 2(, 3, ..., n) -- value(s)
						// 1 -- source
						// so we destroy the key on the stack
						sol::stack::remove(L, 2, 1);
						lua_CFunction cf = it->second.as<lua_CFunction>();
						return cf(L);
					}
				}
			}
			return sol::stack::push(L, sol::lua_nil);
		}

		static int insertion_function(lua_State* L) {
			sol::stack_object source(L, 1);
			sol::stack_object key(L, 2);
			sol::stack_object value(L, 3);
			if (!source.is<thing>()) {
				return luaL_error(L, "given an incorrect object for this call");
			}
			// write to member variables, etc. etc...
			sol::optional<sol::string_view> maybe_svkey = key.as<sol::optional<sol::string_view>>();
			if (maybe_svkey) {
				{
					// variables are different than funtions
					// when someone does `obj.a`, they expect
					// this __index call (this lookup function) to
					// return to them the value itself they're seeing
					// so we call out lua_CFunction that we serialized earlier
					auto it = thing_variable_associations.find(*maybe_svkey);
					if (it != thing_variable_associations.cend()) {
						// note that calls generated by sol3 
						// for member variables expect the stack ordering to be
						// 2(, 3, ..., n) -- value(s)
						// 1 -- source
						// so we remove the key value
						sol::stack::remove(L, 2, 1);
						lua_CFunction cf = it->second.as<lua_CFunction>();
						return cf(L);
					}
					else {
						// write to member variable, maybe override function
						// if your class allows for it?
						(void)value;
					}
				}
				// exercise for reader:
				// how do you override functions on the metatable with
				// proper syntax, but error when the type is
				// an "instance" object?
			}
			return 0;
		}
	};

	lua.new_usertype<thing>("thing");
	sol::table metatable = lua["thing"];

	metatable[sol::meta_method::index] = &call_handler::lookup_function;
	metatable[sol::meta_method::new_index] = &call_handler::insertion_function;
}

void unregister_thing_type(sol::state&) {
	thing_function_associations.clear();
	thing_variable_associations.clear();
}

int main() {

	std::cout << "=== metatable with custom-built (static) handling ===" << std::endl;


	sol::state lua;
	lua.open_libraries(sol::lib::base);

	// register custom type + storage
	register_thing_type(lua);

	lua.script(R"(t = thing.new() 
		print(t.member_variable)
		print(t:member_function())
		t.member_variable = 24
		print(t.member_variable)
		print(t:member_function())
	)");

	// clear storage
	unregister_thing_type(lua);

	std::cout << std::endl;

	return 0;
}