1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3
4 #ifndef _LUATABLE_H
5 #define _LUATABLE_H
6
7 #include <cassert>
8 #include <iterator>
9
10 #include <lua.hpp>
11
12 #include "LuaPushPull.h"
13 #include "LuaRef.h"
14 #include "LuaUtils.h"
15
16 /*
17 * The LuaTable class is a wrapper around a table present on the stack. There
18 * are three ways to instantiate a LuaTable object:
19 *
20 * > lua_State *l;
21 * > int i; // the stack index of a table
22 * > LuaTable(l); // This will allocate a new table on top of the stack
23 * > LuaTable(l, array_s, hash_s); // Same as previous but it uses "lua_createtable",
24 * > // so it preallocate array part and hash part on LVM
25 * > LuaTable(l, i); // This will wrap the object around an existing table
26 *
27 * Note that the LuaTable object never removes the wrapped table from the stack.
28 * Also, there is no integrity check except at the object creation, which means
29 * that if you fiddle with the stack below the wrapped table you will get
30 * unexpected results (most likely a crash).
31 *
32 * Get/Set:
33 *
34 * The Get and Set operators use the pi_lua_generic_{push pull} functions
35 * to fetch a value of any type from the table. It is possible to add support
36 * for extra types by overloading the aforementioned functions for the new
37 * type. The Get function takes an optional default value, which will be returned
38 * if the table does not contain the requested key. If no default is given, and
39 * the key is not in the table then a Lua error is generated. If the key is present
40 * but the value has an incompatible type, then a Lua error is generated (even if
41 * a default value is passed to the Get method).
42 *
43 * These operations are designed to restore the state of the stack, thus making
44 * it impossible to Get a LuaTable, as the latter would need a place on the
45 * stack. For this reason, the Sub() method is used to get a subtable,
46 * placing the "returned" table on the top of the stack.
47 *
48 * Example:
49 *
50 * > lua_State *l; // stack size: X
51 * > LuaTable t = LuaTable(l+1); // stack size: X+1, t = {}
52 * > t.Set("foo", 1); // stack size: X+1, t = {foo:1}
53 * > int foo = t.Get<int>("foo");
54 * > //int bar = t.Get<int>("bar"); // WOULD CRASH!
55 * > int bar = t.Get("bar", 0);
56 * > {
57 * > LuaTable t2(l); // stack size: X+2
58 * > t.Set("baz", t2); // t = {foo:1, baz:<t2>}
59 * > } // t2 isn't a valid name, we can now safely pop the table out.
60 * > lua_pop(l, 1); // stack size: X+1
61 * > LuaTable t2_bis = t.Sub("baz"); // stack size: X+2
62 *
63 * STL loaders:
64 *
65 * If you want to load a whole vector or map into a LuaTable, just do
66 *
67 * > std::vector v; // Or std::list, std::set, whatever as long as it has iterators
68 * > std::map m;
69 * > LuaTable t;
70 * > t.LoadMap(m.begin(), m.end());
71 * > t.LoadVector(v.begin(), v.end());
72 *
73 * Note that LoadVector doesn't overwrite the content of the table, it appends
74 * to its array-like part. Unless you have numerical index beyond its length,
75 * which you shouldn't do anyway.
76 *
77 * LoadMap happily overwrites any data if necessary.
78 *
79 * VecIter:
80 *
81 * It is possible to get STL-like iterators on the array part of the table.
82 * The use cases are typically in loops or to use in the STL algorithms or as
83 * inputs for containers.
84 *
85 * The two methods are LuaTable::Begin<Value>() and LuaTable::End<Value>()
86 *
87 * As usual, since C++ is static typed, the iterators will fail in a mixed-typed table
88 * (generating a Lua error when you attempt to access an element with the wrong type).
89 *
90 * ScopedTable:
91 *
92 * The ScopedTable class is a LuaTable derivative that comes with two constructors:
93 * * New table constructor: ScopedTable(l);
94 * * LuaRef contructor: ScopedTable(my_lua_ref_object);
95 * Both constructors will push a new table onto the stack, and when the C++
96 * ScopedTable objects are destroyed, this new table is removed and everything
97 * above it on the stack gets shifted down.
98 */
99 class LuaTable {
100 public:
101 // For now, every lua_State * can only be NULL or Pi::LuaManager->GetLuaState();
LuaTable(const LuaTable & ref)102 LuaTable(const LuaTable &ref) :
103 m_lua(ref.m_lua),
104 m_index(ref.m_index) {} // Copy constructor.
LuaTable(lua_State * l,int index)105 LuaTable(lua_State *l, int index) :
106 m_lua(l),
107 m_index(lua_absindex(l, index)) { assert(lua_istable(m_lua, m_index)); }
LuaTable(lua_State * l)108 explicit LuaTable(lua_State *l) :
109 m_lua(l)
110 {
111 lua_newtable(m_lua);
112 m_index = lua_gettop(l);
113 }
114
LuaTable(lua_State * l,int array_s,int hash_s)115 explicit LuaTable(lua_State *l, int array_s, int hash_s) :
116 m_lua(l)
117 {
118 lua_createtable(m_lua, array_s, hash_s);
119 m_index = lua_gettop(l);
120 }
121
~LuaTable()122 ~LuaTable() {}
123
124 const LuaTable &operator=(const LuaTable &ref)
125 {
126 m_lua = ref.m_lua;
127 m_index = ref.m_index;
128 return *this;
129 }
130 template <class Key>
131 LuaTable PushValueToStack(const Key &key) const;
132 template <class Value, class Key>
133 Value Get(const Key &key) const;
134 template <class Key>
135 LuaTable Sub(const Key &key) const; // Does not clean up the stack.
136 template <class Value, class Key>
137 Value Get(const Key &key, Value default_value) const;
138 template <class Value, class Key>
139 LuaTable Set(const Key &key, const Value &value) const;
140
141 template <class Ret, class Key, class... Args>
142 Ret Call(const Key &key, const Args &... args) const;
143 template <class Key, class... Args>
Call(const Key & key,const Args &...args)144 void Call(const Key &key, const Args &... args) const
145 {
146 Call<bool>(key, args...);
147 }
148 template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
149 std::tuple<Ret1, Ret2, Ret...> Call(const Key &key, const Args &... args) const;
150
151 template <class Key, class... Args>
CallMethod(const Key & key,const Args &...args)152 void CallMethod(const Key &key, const Args &... args) const
153 {
154 Call<bool>(key, *this, args...);
155 }
156 template <class Ret, class Key, class... Args>
CallMethod(const Key & key,const Args &...args)157 Ret CallMethod(const Key &key, const Args &... args) const
158 {
159 return Call<Ret>(key, *this, args...);
160 }
161 template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
CallMethod(const Key & key,const Args &...args)162 std::tuple<Ret1, Ret2, Ret...> CallMethod(const Key &key, const Args &... args) const
163 {
164 return Call<Ret1, Ret2, Ret...>(key, *this, args...);
165 }
166
167 template <class PairIterator>
168 LuaTable LoadMap(PairIterator beg, PairIterator end) const;
169 template <class ValueIterator>
170 LuaTable LoadVector(ValueIterator beg, ValueIterator end) const;
171
172 template <class Key, class Value>
173 std::map<Key, Value> GetMap() const;
174
GetLua()175 lua_State *GetLua() const { return m_lua; }
GetIndex()176 int GetIndex() const { return m_index; }
Size()177 size_t Size() const { return lua_rawlen(m_lua, m_index); }
178
179 /* VecIter, as in VectorIterator (only shorter to type :-)
180 *
181 * Careful, its LuaTable specialization isn't stable WRT the stack, and using its value
182 * will push a table onto the stack, and will only clean it up when the iterator
183 * gets destroyed or inc/decremented.
184 *
185 * For all other values, occasional operations on the stack may occur but it should
186 * not leak anything.
187 */
188 template <class Value>
189 class VecIter : public std::iterator<std::input_iterator_tag, Value> {
190 public:
VecIter()191 VecIter() :
192 m_table(0),
193 m_currentIndex(0),
194 m_cache(),
195 m_dirtyCache(true) {}
~VecIter()196 ~VecIter() {}
VecIter(LuaTable * t,int currentIndex)197 VecIter(LuaTable *t, int currentIndex) :
198 m_table(t),
199 m_currentIndex(currentIndex),
200 m_cache(),
201 m_dirtyCache(true) {}
VecIter(const VecIter & copy)202 VecIter(const VecIter ©) :
203 m_table(copy.m_table),
204 m_currentIndex(copy.m_currentIndex),
205 m_cache(),
206 m_dirtyCache(true) {}
207 void operator=(const VecIter ©)
208 {
209 CleanCache();
210 m_table = copy.m_table;
211 m_currentIndex = copy.m_currentIndex;
212 }
213
214 VecIter operator++()
215 {
216 if (m_table) {
217 CleanCache();
218 ++m_currentIndex;
219 }
220 return *this;
221 }
222 VecIter operator++(int)
223 {
224 VecIter copy(*this);
225 if (m_table) {
226 CleanCache();
227 ++m_currentIndex;
228 }
229 return copy;
230 }
231 VecIter operator--()
232 {
233 if (m_table) --m_currentIndex;
234 return *this;
235 }
236 VecIter operator--(int)
237 {
238 VecIter copy(*this);
239 if (m_table) --m_currentIndex;
240 return copy;
241 }
242
243 bool operator==(const VecIter &other) const { return (m_table == other.m_table && m_currentIndex == other.m_currentIndex); }
244 bool operator!=(const VecIter &other) const { return (m_table != other.m_table || m_currentIndex != other.m_currentIndex); }
245 Value operator*()
246 {
247 LoadCache();
248 return m_cache;
249 }
250 const Value *operator->()
251 {
252 LoadCache();
253 return &m_cache;
254 }
255
256 private:
CleanCache()257 void CleanCache() { m_dirtyCache = true; }
LoadCache()258 void LoadCache()
259 {
260 if (m_dirtyCache) {
261 m_cache = m_table->Get<Value>(m_currentIndex);
262 m_dirtyCache = false;
263 }
264 }
265 LuaTable *m_table;
266 int m_currentIndex;
267 Value m_cache;
268 bool m_dirtyCache;
269 };
270
271 template <class Value>
Begin()272 VecIter<Value> Begin() { return VecIter<Value>(this, 1); }
273 template <class Value>
End()274 VecIter<Value> End() { return VecIter<Value>(this, Size() + 1); }
275
276 protected:
LuaTable()277 LuaTable() :
278 m_lua(0),
279 m_index(0) {} //Protected : invalid tables shouldn't be out there.
280 lua_State *m_lua;
281 int m_index;
282 };
283
284 class ScopedTable : public LuaTable {
285 public:
ScopedTable(const LuaTable & t)286 ScopedTable(const LuaTable &t) :
287 LuaTable(t)
288 {
289 if (m_lua) {
290 lua_pushvalue(m_lua, m_index);
291 m_index = lua_gettop(m_lua);
292 }
293 }
ScopedTable(lua_State * l)294 ScopedTable(lua_State *l) :
295 LuaTable(l) {}
ScopedTable(const LuaRef & r)296 ScopedTable(const LuaRef &r) :
297 LuaTable()
298 {
299 r.PushCopyToStack();
300 m_lua = r.GetLua();
301 m_index = lua_gettop(m_lua);
302 }
~ScopedTable()303 ~ScopedTable()
304 {
305 if (m_lua && !lua_isnone(m_lua, m_index) && lua_istable(m_lua, m_index))
306 lua_remove(m_lua, m_index);
307 }
308 };
309
310 template <class Key>
PushValueToStack(const Key & key)311 LuaTable LuaTable::PushValueToStack(const Key &key) const
312 {
313 pi_lua_generic_push(m_lua, key);
314 lua_gettable(m_lua, m_index);
315 return *this;
316 }
317
318 template <class Key>
Sub(const Key & key)319 LuaTable LuaTable::Sub(const Key &key) const
320 {
321 PushValueToStack(key);
322 return (lua_istable(m_lua, -1)) ? LuaTable(m_lua, -1) : LuaTable();
323 }
324
325 template <class Value, class Key>
Get(const Key & key)326 Value LuaTable::Get(const Key &key) const
327 {
328 Value return_value;
329 PushValueToStack(key);
330 pi_lua_generic_pull(m_lua, -1, return_value);
331 lua_pop(m_lua, 1);
332 return return_value;
333 }
334
335 template <class Value, class Key>
Get(const Key & key,Value default_value)336 Value LuaTable::Get(const Key &key, Value default_value) const
337 {
338 PushValueToStack(key);
339 if (!(lua_isnil(m_lua, -1)))
340 pi_lua_generic_pull(m_lua, -1, default_value);
341 lua_pop(m_lua, 1);
342 return default_value;
343 }
344
345 template <class Value, class Key>
Set(const Key & key,const Value & value)346 LuaTable LuaTable::Set(const Key &key, const Value &value) const
347 {
348 pi_lua_generic_push(m_lua, key);
349 pi_lua_generic_push(m_lua, value);
350 lua_settable(m_lua, m_index);
351 return *this;
352 }
353
354 template <class Key, class Value>
GetMap()355 std::map<Key, Value> LuaTable::GetMap() const
356 {
357 LUA_DEBUG_START(m_lua);
358 std::map<Key, Value> ret;
359 lua_pushnil(m_lua);
360 while (lua_next(m_lua, m_index)) {
361 Key k;
362 Value v;
363 if (pi_lua_strict_pull(m_lua, -2, k) && pi_lua_strict_pull(m_lua, -1, v)) {
364 ret[k] = v;
365 } else {
366 // XXX we should probably emit some kind of warning here somehow
367 }
368 lua_pop(m_lua, 1);
369 }
370 LUA_DEBUG_END(m_lua, 0);
371 return ret;
372 }
373
374 template <class PairIterator>
LoadMap(PairIterator beg,PairIterator end)375 LuaTable LuaTable::LoadMap(PairIterator beg, PairIterator end) const
376 {
377 for (PairIterator it = beg; it != end; ++it)
378 Set(it->first, it->second);
379 return *this;
380 }
381
382 template <class ValueIterator>
LoadVector(ValueIterator beg,ValueIterator end)383 LuaTable LuaTable::LoadVector(ValueIterator beg, ValueIterator end) const
384 {
385 lua_len(m_lua, m_index);
386 int i = lua_tointeger(m_lua, -1) + 1;
387 lua_pop(m_lua, 1);
388 for (ValueIterator it = beg; it != end; ++it, ++i)
389 Set(i, *it);
390 return *this;
391 }
392
393 template <class Ret, class Key, class... Args>
Call(const Key & key,const Args &...args)394 Ret LuaTable::Call(const Key &key, const Args &... args) const
395 {
396 LUA_DEBUG_START(m_lua);
397 Ret return_value;
398
399 lua_checkstack(m_lua, sizeof...(args) + 3);
400 PushValueToStack(key);
401 pi_lua_multiple_push(m_lua, args...);
402 pi_lua_protected_call(m_lua, sizeof...(args), 1);
403 pi_lua_generic_pull(m_lua, -1, return_value);
404 lua_pop(m_lua, 1);
405 LUA_DEBUG_END(m_lua, 0);
406 return return_value;
407 }
408
409 template <class Ret1, class Ret2, class... Ret, class Key, class... Args>
Call(const Key & key,const Args &...args)410 std::tuple<Ret1, Ret2, Ret...> LuaTable::Call(const Key &key, const Args &... args) const
411 {
412 LUA_DEBUG_START(m_lua);
413 lua_checkstack(m_lua, sizeof...(args) + 3);
414 PushValueToStack(key);
415 pi_lua_multiple_push(m_lua, args...);
416 pi_lua_protected_call(m_lua, sizeof...(args), sizeof...(Ret) + 2);
417 auto return_values = pi_lua_multiple_pull<Ret1, Ret2, Ret...>(m_lua, -static_cast<int>(sizeof...(Ret)) - 2);
418 lua_pop(m_lua, static_cast<int>(sizeof...(Ret)) + 2);
419 LUA_DEBUG_END(m_lua, 0);
420 return return_values;
421 }
422
423 template <>
LoadCache()424 inline void LuaTable::VecIter<LuaTable>::LoadCache()
425 {
426 if (m_dirtyCache) {
427 m_cache = m_table->Sub(m_currentIndex);
428 m_dirtyCache = false;
429 }
430 }
431 template <>
CleanCache()432 inline void LuaTable::VecIter<LuaTable>::CleanCache()
433 {
434 if (!m_dirtyCache && m_cache.GetLua()) {
435 lua_remove(m_cache.GetLua(), m_cache.GetIndex());
436 }
437 m_dirtyCache = true;
438 }
439
pi_lua_generic_push(lua_State * l,const LuaTable & value)440 inline void pi_lua_generic_push(lua_State *l, const LuaTable &value)
441 {
442 lua_pushvalue(l, value.GetIndex());
443 }
444 #endif
445