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