· 7 years ago · Jan 20, 2019, 09:52 PM
1module wereshift.engine.scripting.lua;
2import lua;
3import std.string;
4import std.traits;
5import std.conv;
6import polyplex.utils.strutils;
7import std.variant;
8
9private __gshared bool isInitialized;
10
11alias LuaStateFunction = lua_CFunction;
12alias LuaStatePtr = lua_State*;
13
14__gshared string LUA_PATH = "content/scripts/?.lua";
15
16/// Expose this to Lua via LuaUserData
17struct Expose;
18
19class LuaException : Exception {
20public:
21 this(string message, string origin) {
22 super("<{0}> from {1}".Format(message, origin));
23 }
24}
25
26/// Gets a new Lua state.
27LuaState newState() {
28 import std.process;
29
30 // Make sure that lua is only loaded once in to memory.
31 if (!isInitialized) {
32 bool loaded = loadLua();
33 }
34
35 lua_State* sptr = luaL_newstate();
36
37 environment["LUA_PATH"] = LUA_PATH;
38 // Bind lua libs
39 // Sandbox away potentially dangerous stuff for now.
40 /*luaopen_base(sptr);
41 luaopen_string(sptr);
42 luaopen_math(sptr);
43 luaopen_table(sptr);
44
45 // This only works via lua calls ¯\_(ツ)_/¯
46 // Tbh this might be a little dangerous, but its what i can do rn.
47 // Also pushcfunction doesn't take extern(C) elements so we do some dumb casting.
48 lua_pushcfunction(sptr, cast(LuaStateFunction) luaopen_package);
49 lua_pushliteral(sptr, LUA_LOADLIBNAME);
50
51 // This is named wrong but w/e
52 luaL_call(sptr, 1, 0);*/
53 luaL_openlibs(sptr);
54 return new LuaState(sptr);
55}
56
57/// Disposes lua from memory, run on exit.
58/// or not, whatever floats your goat.
59void disposeLua() {
60 unloadLua();
61}
62
63enum IsInteger(T) = (is(typeof(T) == byte) || is(typeof(T) == short)
64 || is(typeof(T) == int) || is(typeof(T) == long) || is(typeof(T) == lua_Integer));
65enum IsNumber(T) = (is(typeof(T) == float) || is(typeof(T) == double) || is(typeof(T) == lua_Number));
66enum IsBoolean(T) = (is(typeof(T) == bool));
67enum IsUnsigned(T) = (is(typeof(T) == ubyte) || is(typeof(T) == ushort)
68 || is(typeof(T) == uint) || is(T : ulong) || is(typeof(T) == lua_Unsigned));
69enum IsKContext(T) = (is(typeof(T) == ptrdiff_t) || is(typeof(T) == lua_KContext));
70enum IsString(T) = (is(T : string) || is(typeof(T) == const(char)*));
71enum IsFunction(T) = (is(T : lua_CFunction) || is(T : LuaStateFunction));
72
73Variant stackToVariant(LuaState state) {
74 return stackToVariant(state.state);
75}
76
77private Variant stackToVariant(lua_State* state) {
78 immutable(int) type = lua_type(state, -1);
79 Variant x;
80 switch (type) {
81 case (LUA_TBOOLEAN):
82 x = lua_toboolean(state, -1);
83 break;
84 case (LUA_TLIGHTUSERDATA):
85 x = lua_touserdata(state, -1);
86 break;
87 case (LUA_TNUMBER):
88 x = lua_tonumber(state, -1);
89 break;
90 case (LUA_TSTRING):
91 x = lua_tostring(state, -1).text;
92 break;
93 default:
94 lua_pop(state, 1);
95 break;
96 }
97 return x;
98}
99
100private void g_push(T)(lua_State* state, T value) {
101 static if (IsUnsigned!T) {
102 lua_pushinteger(state, cast(lua_Unsigned) value);
103 } else static if (IsInteger!T) {
104 lua_pushinteger(state, cast(lua_Integer) value);
105 } else static if (IsNumber!T) {
106 lua_pushnumber(state, cast(lua_Number) value);
107 } else static if (IsBoolean!T) {
108 lua_pushboolean(state, cast(int) value);
109 } else static if (IsString!T) {
110 lua_pushstring(state, toStringz(value));
111 } else static if (IsFunction!T) {
112 lua_pushcfunction(state, cast(lua_CFunction) value);
113 } else {
114 lua_pushlightuserdata(state, value);
115 }
116}
117
118private T g_pop(T)(lua_State* state) {
119 static if (IsUnsigned!T) {
120 return cast(T) lua_tointeger(state, -1);
121 } else static if (IsInteger!T) {
122 return cast(T) lua_tointeger(state, -1);
123 } else static if (IsNumber!T) {
124 return cast(T) lua_tonumber(state, -1);
125 } else static if (IsBoolean!T) {
126 return cast(T) lua_toboolean(state, -1);
127 } else static if (IsString!T) {
128 return lua_tostring(state, -1).text;
129 } else static if (IsFunction!T) {
130 return lua_tocfunction(state.state, -1);
131 } else {
132 return cast(T) lua_touserdata(state, -1);
133 }
134}
135
136mixin template luaTableDef() {
137 lua_State* state;
138
139 void push(T)(T value) {
140 return g_push!T(state, value);
141 }
142
143 T pop(T)() {
144 return g_pop!T(state);
145 }
146}
147
148class LuaRegistry {
149private:
150 mixin luaTableDef;
151
152 this(LuaState state) {
153 this(state.state);
154 }
155
156 this(lua_State* state) {
157 this.state = state;
158 }
159
160public:
161 void set(T, TX)(TX id, T value) {
162 push!TX(id);
163 push!T(value);
164 lua_settable(state, LUA_REGISTRYINDEX);
165 }
166
167 T get(T, TX)(TX id) {
168 push!TX(id);
169 lua_gettable(state, LUA_REGISTRYINDEX);
170 return pop!T;
171 }
172
173 LuaTable newTable(string name) {
174 return new LuaTable(state, name, true, false);
175 }
176}
177
178/// A temporary table which will can set set in a function
179/// You can only set values in this table, not get them.
180class LuaLocalTable {
181private:
182 mixin luaTableDef;
183 this(LuaState state) {
184 this(state.state);
185 }
186
187 this(lua_State* state) {
188 import std.stdio;
189 this.state = state;
190
191 // create table.
192 lua_newtable(this.state);
193 }
194
195public:
196 void set(T, TX)(T id, TX value) {
197 import std.stdio;
198 //push!string(id.text);
199
200 push!TX(value);
201 lua_setfield(state, -2, toStringz(id.text));
202
203 /*writeln("lua_settable");
204 lua_settable(state, -3);*/
205 }
206
207 void setTable(int id) {
208 lua_settable(state, id);
209 }
210
211 void setMetaTable(int id) {
212 lua_setmetatable(state, id);
213 }
214
215 void bindMetatable(LuaMetaTable table) {
216 table.setMetatable();
217 }
218}
219
220class LuaMetaTable {
221private:
222 mixin luaTableDef;
223 string name;
224 this(LuaState state, string name) {
225 this(state.state, name);
226 }
227
228 this(lua_State* state, string name) {
229 this.state = state;
230 this.name = name;
231
232 // create table.
233 luaL_newmetatable(this.state, toStringz(name));
234 }
235
236 /// metatable shenannigans
237 void setMetatable(int id = -3) {
238 import std.stdio;
239 push!string(name);
240 lua_gettable(state, LUA_REGISTRYINDEX);
241 lua_setmetatable(state, id);
242 }
243
244
245 // pushes this to the lua stack
246 void toStack() {
247 lua_gettable(state, LUA_REGISTRYINDEX);
248 }
249
250public:
251 void set(T, TX)(T id, TX value) {
252 //push!T(id);
253 push!TX(value);
254 lua_setfield(state, -2, toStringz(id.text));
255 //lua_settable(state, -3);
256 }
257}
258
259/// A lua table.
260class LuaTable {
261private:
262 mixin luaTableDef;
263 string name;
264 string isMetatableTo = null;
265 immutable(char)* nameRef;
266
267 bool parentRegistry;
268
269 this(lua_State* state) {
270 this.state = state;
271 }
272
273 this(lua_State* state, string name, bool parentRegistry = false, bool exists = false) {
274 this(state);
275 name = name;
276 nameRef = toStringz(name);
277 parentRegistry = parentRegistry;
278
279 if (!parentRegistry) {
280 // create table.
281 if (!exists) {
282 lua_newtable(this.state);
283 lua_setglobal(this.state, nameRef);
284 } else {
285 lua_getglobal(this.state, nameRef);
286 }
287 } else {
288 lua_newtable(this.state);
289 if (!exists) {
290 push!string(name);
291 lua_newtable(this.state);
292 lua_settable(state, LUA_REGISTRYINDEX);
293 } else {
294 push!string(name);
295 lua_gettable(state, LUA_REGISTRYINDEX);
296 }
297 }
298 }
299
300public:
301 ~this() {
302 if (!parentRegistry) {
303 // push this table to the stack.
304 lua_getglobal(state, nameRef);
305 } else {
306 push!string(name);
307 lua_gettable(state, LUA_REGISTRYINDEX);
308 }
309
310 lua_pushnil(state);
311 lua_settable(state, -2);
312 }
313
314 this(LuaState state, string name, bool exists = false) {
315 this(state.state, name, exists);
316 }
317
318 void set(T)(int id, T value) {
319 if (isMetatableTo !is null) {
320 throw new Exception("Please restore metatable before modifying");
321 }
322 if (!parentRegistry) {
323 // push this table to the stack.
324 lua_getglobal(state, nameRef);
325 } else {
326 push!string(name);
327 lua_gettable(state, LUA_REGISTRYINDEX);
328 }
329
330 lua_pushinteger(state, id);
331
332 push!T(value);
333
334 lua_settable(state, -3);
335 }
336
337 void set(T)(string name, T value) {
338 if (isMetatableTo !is null) {
339 throw new Exception("Please restore metatable before modifying");
340 }
341 if (!parentRegistry) {
342 // push this table to the stack.
343 lua_getglobal(state, nameRef);
344 } else {
345 push!string(name);
346 lua_gettable(state, LUA_REGISTRYINDEX);
347 }
348
349 lua_pushstring(state, toStringz(name));
350
351 push!T(value);
352
353 lua_settable(state, -3);
354 }
355
356 T get(T)(int id) {
357 if (isMetatableTo !is null) {
358 throw new Exception("Please restore metatable before modifying");
359 }
360 if (!parentRegistry) {
361 // push this table to the stack.
362 lua_getglobal(state, nameRef);
363 } else {
364 push!string(name);
365 lua_gettable(state, LUA_REGISTRYINDEX);
366 }
367
368 // push value to stack and convert it.
369 lua_rawgeti(state, -1, id);
370 T p = pop!T(state);
371
372 // Pop value on stack and return.
373 lua_pop(state, 2);
374 return p;
375 }
376
377 T get(T)(string name) {
378 if (isMetatableTo !is null) {
379 throw new Exception("Please restore metatable before modifying");
380 }
381 if (!parentRegistry) {
382 // push this table to the stack.
383 lua_getglobal(state, nameRef);
384 } else {
385 push!string(name);
386 lua_gettable(state, LUA_REGISTRYINDEX);
387 }
388
389 // push value to stack and convert it.
390 lua_getfield(state, -1, toStringz(name));
391 T p = pop!T;
392
393 // Pop value on stack and return.
394 lua_pop(state, 2);
395 return p;
396
397 }
398
399 // pushes this to the lua stack
400 void toStack() {
401 lua_getglobal(state, nameRef);
402 }
403
404 /// Deletes this table from global space and sets it as metatable to another table
405 void metatableTo(LuaTable table) {
406 lua_getglobal(state, table.nameRef);
407 lua_getglobal(state, nameRef);
408 lua_setmetatable(state, -2);
409 lua_setglobal(state, table.nameRef);
410
411 // Remove old table ref
412 lua_pushnil(state);
413 lua_setglobal(state, nameRef);
414
415 isMetatableTo = table.name;
416 }
417
418 void bindMetatable(LuaMetaTable table) {
419 lua_getglobal(state, nameRef);
420 table.setMetatable();
421 }
422
423 /// restores this table in to global scope.
424 void restoreThis() {
425 if (isMetatableTo !is null) {
426 throw new Exception("This table is not bound.");
427 }
428
429 lua_getglobal(state, toStringz(isMetatableTo));
430 if (lua_getmetatable(state, -1) > 0) {
431 lua_setglobal(state, nameRef);
432
433 // Now remove the old table.
434 lua_getglobal(state, toStringz(isMetatableTo));
435 lua_pushnil(state);
436 lua_setmetatable(state, -2);
437
438 isMetatableTo = null;
439 } else {
440 throw new Exception("Metatable seems to have been removed outside of scope.");
441 }
442 }
443}
444
445class LuaThread {
446private:
447 lua_State* state;
448 LuaState parent;
449
450 this(LuaState parent) {
451 this.parent = parent;
452 }
453
454public:
455 ~this() {
456 lua_close(state);
457 }
458
459 /// Executes a string as lua code in the thread.
460 void executeString(string code, string name = "unnamed") {
461 if (luaL_dostring(state, toStringz(code)) != LUA_OK) {
462 throw new LuaException(lua_tostring(state, -1).text, name);
463 }
464 lua_close(state);
465 }
466}
467
468import polyplex.utils.strutils;
469
470/// Makes object instantiable by Lua
471/*template Luaify(T, int argCount) {
472
473 static if (is(T == struct)) {
474
475 import std.variant;
476 import std.traits;
477 import std.traits;
478 import std.format;
479
480 static T instantiate(Variant[] variant) {
481 T newT;
482 static foreach (element; __traits(derivedMembers, T)) {
483 static if (__traits(compiles, __traits(getMember, T, element))) {
484 static if (hasUDA!(__traits(getMember, T, element), Expose)) {
485 mixin(q{newT.%s = cast(typeof(__traits(getMember, T, element)))variant[%d];}.format(element, i));
486
487 }
488 }
489 }
490 return newT;
491 }
492 } else {
493 }
494
495}*/
496
497private mixin template luaUserData(T, string name = "") {
498 import std.stdio;
499
500 enum NAME = name != "" ? name : T.stringof;
501private:
502 T* data;
503public:
504
505
506 // TODO: Make this a template constraint?
507 LuaStateFunction __new = (state) {
508
509 // __index function
510 LuaStateFunction __index = (state) {
511
512 // Index.
513 string index = lua_tostring(state, -1).text;
514
515 if (index != "selfPtr") {
516 // get self pointer
517 lua_pushstring(state, toStringz("selfPtr"));
518 lua_rawget(state, 1);
519
520
521 // self pointer.
522 T* tPtr = (cast(T*)lua_touserdata(state, -1));
523
524
525 // Trait dark magic hide your children.
526 static foreach (element; __traits(derivedMembers, T)) {
527
528 // Make sure only public members are considered
529 static if (__traits(compiles, __traits(getMember, T, element))) {
530
531 // They also need to have the @Expose attribute
532 static if (hasUDA!(__traits(getMember, T, element), Expose)) {
533
534 // And the the right index.
535 if (element == index) {
536
537 // Finally return it.
538 mixin(q{g_push!(typeof(__traits(getMember, T, element)))(state, tPtr.%s);}.format(element));
539 return 1;
540 }
541 }
542 }
543 }
544 }
545
546 // return other things users might've set.
547 lua_pushstring(state, toStringz(index));
548 lua_rawget(state, 1);
549 return 1;
550 };
551
552 // __newindex function
553
554 LuaStateFunction __newindex = (state) {
555
556 // Get value
557 Variant val = stackToVariant(state);
558
559 // ! remember to pop first
560 lua_pop(state, 1);
561
562 // get index.
563 string index = stackToVariant(state).coerce!string;
564
565 if (index != "selfPtr") {
566
567 // Push string of self pointer.
568 lua_pushstring(state, toStringz("selfPtr"));
569 lua_rawget(state, 1);
570
571 T* tPtr = (cast(T*)lua_touserdata(state, -1));
572
573 // Trait dark magic hide your children.
574 static foreach (element; __traits(derivedMembers, T)) {
575
576 // Make sure only public members are considered
577 static if (__traits(compiles, __traits(getMember, T, element))) {
578
579 // They also need to have the @Expose attribute
580 static if (hasUDA!(__traits(getMember, T, element), Expose)) {
581
582 // And the the right index.
583 if (element == index) {
584
585 // Finally change it.
586 mixin(q{tPtr.%s = val.coerce!%s;}.format(element, typeof(__traits(getMember, T, element)).stringof));
587 return 0;
588 }
589 }
590 }
591 }
592 }
593
594 // WE DON'T WANT THE USER TO CHANGE THE SELFPTR.
595 return 0;
596 };
597
598 // constructor begin
599 Variant[] params;
600 immutable(int) stack = lua_gettop(state);
601 foreach (i; 0 .. stack) {
602 params ~= stackToVariant(state);
603 }
604 lua_pop(state, stack);
605
606 // Instantiate T.
607
608 static if (is(T == struct)) {
609 T* tInstance = new T;
610 tInstance.instantiate(params);
611 } else {
612 T tInstance = new T;
613 tInstance.instantiate(params);
614 }
615
616 // Create local table.
617 LuaLocalTable table = new LuaLocalTable(state);
618 int mtabId = lua_gettop(state);
619
620 table.set!(string, T*)("selfPtr", tInstance);
621
622 LuaMetaTable metaTable = new LuaMetaTable(state, "A");
623 int mttabId = lua_gettop(state);
624
625 metaTable.set!(string, LuaStateFunction)("__index", __index);
626 metaTable.set!(string, LuaStateFunction)("__newindex", __newindex);
627 metaTable.setMetatable();
628 lua_pop(state, 1);
629
630 return lua_gettop(state);
631 };
632
633
634 T get() {
635 return *data;
636 }
637}
638
639/// Binding to D data.
640class ManagedUserData(T) {
641private:
642 LuaState state;
643 LuaTable tableRef;
644
645 mixin luaUserData!T;
646
647 this()(LuaState state, T* dataPtr) {
648 // TODO: Do binding via template magic.
649 data = dataPtr;
650 state = state;
651
652 import std.stdio;
653
654 tableRef = state.newTable(T.stringof);
655 tableRef.set!LuaStateFunction("new", __new);
656 LuaTable table = state.newTable(T.stringof~"__callMeta");
657 table.set!LuaStateFunction("__call", __new);
658 table.metatableTo(tableRef);
659 }
660}
661
662class LuaState {
663private:
664 lua_State* state;
665 LuaRegistry registry;
666 LuaThread[] threads;
667
668 this(lua_State* state, bool wrapper = false) {
669 this.state = state;
670 if (!wrapper)
671 registry = new LuaRegistry(this);
672 }
673
674public:
675 ~this() {
676 destroy(threads);
677 destroy(registry);
678 lua_close(state);
679 }
680
681 /// Creates a binding to some user data (via pointer)
682 ManagedUserData!T bindUserData(T)(T* data) {
683 return new ManagedUserData!T(this, data);
684 }
685
686 /**
687 Creates a new table.
688 */
689 LuaTable newTable(string name) {
690 return new LuaTable(this, name);
691 }
692
693 /**
694 Creates a new table, which doesn't exist globally.
695 Values can only get set while the table is active.
696 Table becomes inactive as soon as other data is managed.
697 */
698 LuaLocalTable newLocalTable() {
699 return new LuaLocalTable(this);
700 }
701
702 /**
703 Creates a new callstack (called threads in lua)
704
705 This can be used cross-threads, be sure to use mutex when needed.
706 */
707 LuaThread newThread() {
708 LuaThread thread = new LuaThread(this);
709 threads ~= thread;
710 return thread;
711 }
712
713 /**
714 Execute a string.
715 */
716 void executeString(string code, string name = "unnamed", bool coroutine = false) {
717 if (coroutine) {
718 LuaThread t = newThread();
719 t.executeString(code);
720 return;
721 }
722 if (luaL_dostring(state, toStringz(code)) != LUA_OK) {
723 throw new LuaException(lua_tostring(state, -1).text, name);
724 }
725 }
726
727 /**
728 Execute a file.
729 */
730 void executeFile(string path, bool coroutine = false) {
731 import std.file;
732
733 string data = path.readText;
734 executeString(data, path, coroutine);
735 }
736
737 ref LuaRegistry getRegistry() {
738 return registry;
739 }
740}