1 module pixelperfectengine.scripting.lua; 2 3 import bindbc.lua; 4 5 import core.vararg; 6 7 import std.string : toStringz, fromStringz; 8 import std.variant; 9 import std.typetuple; 10 import std.traits; 11 import std.exception : enforce; 12 13 import pixelperfectengine.system.exc; 14 public import pixelperfectengine.scripting.lualib : registerLibForScripting; 15 public import pixelperfectengine.scripting.globals; 16 17 import collections.linkedmap; 18 19 /** 20 * Initializes the Lua scripting engine. 21 * Returns: true if successful. 22 */ 23 public bool initLua() { 24 LuaSupport ver = loadLua(); 25 return ver == LuaSupport.lua54; 26 } 27 ///A default allocator for Lua. 28 extern(C) 29 package void* luaAllocator(void* ud, void* ptr, size_t osize, size_t nsize) @system nothrow { 30 import core.memory; 31 if (nsize == 0) { 32 GC.free(ptr); 33 return null; 34 } else { 35 return GC.realloc(ptr, nsize); 36 } 37 } 38 /** 39 * Calls a Lua function with the given name and arguments. 40 * Params: 41 * state = The Lua state, where the function is located. 42 * ... = The arguments to be passed to the function. 43 * Template params: 44 * T = The return type. 45 * funcName = The name of the function. 46 * Returns: An expected return type, which can be set to a tagged algebraic type to avoid potential mismatches. 47 * Throws: A LuaException if the return type isn't matched, the execution ran into an error, or the function isn't 48 * found. 49 */ 50 public T callLuaFunc(T, string funcName)(lua_State* state, ...) @system { 51 lua_getglobal(state, funcName); 52 if (!lua_isfunction(state, 0 /* LUA_TOP */)) 53 throw new LuaException(8,"Function not found"); 54 foreach (arg; _arguments) { 55 if (arg == typeid(byte)) { 56 lua_pushinteger(state, va_arg!byte(_argptr)); 57 } else if (arg == typeid(short)) { 58 lua_pushinteger(state, va_arg!short(_argptr)); 59 } else if (arg == typeid(int)) { 60 lua_pushinteger(state, va_arg!int(_argptr)); 61 } else if (arg == typeid(long)) { 62 lua_pushinteger(state, va_arg!long(_argptr)); 63 } else if (arg == typeid(ushort)) { 64 lua_pushinteger(state, va_arg!ushort(_argptr)); 65 } else if (arg == typeid(uint)) { 66 lua_pushinteger(state, va_arg!uint(_argptr)); 67 } else if (arg == typeid(ubyte)) { 68 lua_pushinteger(state, va_arg!ubyte(_argptr)); 69 } else if (arg == typeid(bool)) { 70 lua_pushboolean(state, va_arg!bool(_argptr)); 71 } else if (arg == typeid(double)) { 72 lua_pushnumber(state, va_arg!double(_argptr)); 73 } else if (arg == typeid(string)) { 74 lua_pushstring(state, toStringz(va_arg!string(_argptr))); 75 } else if (arg == typeid(LuaVar)) { 76 va_arg!LuaVar(_argptr).pushToLuaState(state); 77 } else assert(0, "Argument not supported!"); 78 } 79 int errorCode = lua_pcall(state, cast(int)_arguments.length, is(T == void) ? 0 : 1, 0); 80 static if (!is(T == void)) { 81 LuaVar result = LuaVar(state, -1); 82 lua_pop(state, 1); 83 static if (is(T == LuaVar)) { 84 return result; 85 } else { 86 return result.get!T; 87 } 88 } 89 } 90 /** 91 * Registers a D function to be called from Lua. 92 * Code is modified from MrcSnm's example found in the HipremeEngine. 93 * Params: 94 * state = The Lua state to handle the data from the Lua side of things. 95 * Template params: 96 * Func = The function to be registered. 97 */ 98 extern(C) public int registerDFunction(alias Func)(lua_State* state) nothrow 99 if(isSomeFunction!(Func)) { 100 import std.traits:Parameters, ReturnType; 101 102 Parameters!Func params; 103 int stackCounter = 0; 104 try { 105 foreach_reverse(ref param; params) { 106 stackCounter--; 107 param = luaGetFromIndex!(typeof(param))(state, stackCounter); 108 } 109 } catch (Exception e) { 110 luaL_error(state, "Argument type mismatch with D functions!"); 111 } 112 113 try { 114 static if(is(ReturnType!Func == void)) { 115 Func(params); 116 return 0; 117 } else static if(is(ReturnType!Func == struct)) { 118 auto retVal = Func(params); 119 static foreach (key ; (ReturnType!Func).tupleof) { 120 LuaVar(__traits(child, retVal, key)).pushToLuaState(state); 121 } 122 return cast(int)retVal.tupleof.length; 123 } else { 124 LuaVar(Func(params)).pushToLuaState(state); 125 return 1; 126 } 127 } catch (Exception e) { 128 //luaPushVar(L, null); 129 lastLuaToDException = e; 130 try { 131 luaL_error(state, ("A D function threw: "~e.toString~"!\0").ptr); 132 } catch(Exception e) { 133 luaL_error(state, "D threw when stringifying exception!"); 134 } 135 return 1; 136 } 137 } 138 /** 139 * Registers a D delegate to be called from Lua. 140 * Code is modified from MrcSnm's example found in the HipremeEngine. 141 * Params: 142 * state = The Lua state to handle the data from the Lua side of things. 143 * Template params: 144 * Func = The function to be registered. 145 * Note: When calling a D delegate from Lua, the first parameter is always a light user data 146 * containing the class instance that the delegate should be executed on. 147 */ 148 extern (C) public int registerDDelegate(alias Func)(lua_State* state) nothrow 149 if(isSomeFunction!(Func)) { 150 import std.traits:Parameters, ReturnType; 151 alias ClassType = __traits(parent, Func); 152 Parameters!Func params; 153 int stackCounter = 0; 154 stackCounter--; 155 ClassType c; 156 try { 157 c = luaGetFromIndex!ClassType(state, stackCounter); 158 foreach_reverse(ref param; params) { 159 stackCounter--; 160 param = luaGetFromIndex!(typeof(param))(state, stackCounter); 161 } 162 } catch (Exception e) { 163 luaL_error(state, "Argument type mismatch with D functions!"); 164 } 165 166 try { 167 static if(is(ReturnType!Func == void)) { 168 __traits(child, c, Func)(params);//c.Func(params); 169 return 0; 170 } else static if(is(ReturnType!Func == struct)) { 171 ReturnType!Func retVal = __traits(child, c, Func)(params);//auto retVal = c.Func(params); 172 static foreach (key ; (ReturnType!Func).tupleof) { 173 LuaVar(__traits(child, retVal, key)).pushToLuaState(state); 174 } 175 return cast(int)retVal.tupleof.length; 176 } else { 177 LuaVar(__traits(child, c, Func)(params)).pushToLuaState(state);//LuaVar(c.Func(params)).pushToLuaState(state); 178 return 1; 179 } 180 } catch (Exception e) { 181 //luaPushVar(L, null); 182 lastLuaToDException = e; 183 try { 184 luaL_error(state, ("A D function threw: "~e.toString~"!\0").ptr); 185 } catch(Exception e) { 186 luaL_error(state, "D threw when stringifying exception!"); 187 } 188 return 1; 189 } 190 } 191 ///Contains the pointer to the exception thrown by a D function called from the Lua side. 192 public static Exception lastLuaToDException; 193 ///Fetches a value from a lua_State variable. 194 package T luaGetFromIndex(T)(lua_State* L, int ind) { 195 static if(isIntegral!T || isSomeChar!T) { 196 if (!lua_isinteger(L, ind)) 197 throw new LuaException(7,"Type mismatch!"); 198 lua_Integer i = lua_tointeger(L, ind); 199 return cast(T)i; 200 } else static if(isFloatingPoint!T) { 201 if (!lua_isnumber(L, ind)) 202 throw new LuaException(7,"Type mismatch!"); 203 lua_Number n = lua_tonumber(L, ind); 204 return cast(T)n; 205 } else static if(is(T == class) || is(T == interface)) { 206 if (!lua_islightuserdata(L, ind)) 207 throw new LuaException(7,"Type mismatch!"); 208 void* data = lua_touserdata(L, ind); 209 return cast(T)data; 210 } else static if(is(T == string)) { 211 import std.string : fromStringz; 212 if (!lua_isstring(L, ind)) 213 throw new LuaException(7,"Type mismatch!"); 214 return cast(string)(fromStringz(lua_tostring(L, ind))); 215 } else static if(is(T == void*)) { 216 if (!lua_islightuserdata(L, ind)) 217 throw new LuaException(7,"Type mismatch!"); 218 void* data = lua_touserdata(L, ind); 219 return data; 220 } else static if(is(T == LuaVar)) { 221 return LuaVar(L, ind); 222 } else static assert(0, "Type not supported!"); 223 224 } 225 /** 226 * Contains type identifiers related to Lua. 227 */ 228 public enum LuaVarType { 229 Null, 230 Boolean, 231 Number, 232 Integer, 233 String, 234 Function, 235 Userdata, 236 Thread, 237 Table 238 } 239 /** 240 * Implements a Lua variable with all the underlying stuff required for it. 241 */ 242 public struct LuaVar { 243 ///Contains the type of the given Lua variable. 244 private LuaVarType _type; 245 private union { 246 void* dataPtr; 247 long dataInt; 248 double dataNum; 249 } 250 ///Creates a Lua variable of type `void`. 251 public static LuaVar voidType() @safe pure nothrow { 252 LuaVar result; 253 result._type = LuaVarType.Null; 254 return result; 255 } 256 /** 257 * Initializes the value with the type of `val` 258 */ 259 public this(T)(T val) @safe pure nothrow { 260 static if (is(T == void*) || is(T == LuaTable*)) { 261 dataPtr = val; 262 } else static if (isIntegral!T || isBoolean!T) { 263 dataInt = val; 264 } else static if (isFloatingPoint!T) { 265 dataNum = val; 266 } else static if (is(T == string)) { 267 void __workaround() @system pure nothrow { 268 dataPtr = cast(void*)toStringz(val); 269 } 270 void _workaround() @trusted pure nothrow { 271 __workaround(); 272 } 273 _workaround(); 274 } else static if (is(T == const(char)*)) { 275 dataPtr = cast(void*)val; 276 } 277 setType!(T); 278 } 279 /** 280 * Fetches the given index (idx) from the lua_State (state), and initializes a LuaVar based on its type. 281 */ 282 public this(lua_State* state, int idx) nothrow { 283 int type = lua_type(state, idx); 284 switch (type) { 285 case LUA_TLIGHTUSERDATA: 286 dataPtr = lua_touserdata(state, idx); 287 _type = LuaVarType.Userdata; 288 break; 289 case LUA_TBOOLEAN: 290 dataInt = lua_toboolean(state, idx); 291 _type = LuaVarType.Boolean; 292 break; 293 case LUA_TNUMBER: 294 if (lua_isinteger(state, idx)) { 295 dataInt = lua_tointeger(state, idx); 296 _type = LuaVarType.Integer; 297 } else { 298 dataNum = lua_tonumber(state, idx); 299 _type = LuaVarType.Number; 300 } 301 break; 302 case LUA_TSTRING: 303 dataPtr = cast(void*)lua_tostring(state, idx); 304 _type = LuaVarType.String; 305 break; 306 default: 307 break; 308 } 309 } 310 ///Sets the type of this variable internally. 311 private void setType(T)() @nogc @safe pure nothrow { 312 static if (isIntegral!T || isSomeChar!T) { 313 _type = LuaVarType.Integer; 314 } else static if (isBoolean!T) { 315 _type = LuaVarType.Boolean; 316 } else static if (isFloatingPoint!T) { 317 _type = LuaVarType.Number; 318 } else static if (is(T == string) || is(T == const(char)*)) { 319 _type = LuaVarType.String; 320 } else static if (is(T == void*) || is(T == class) || is(T == interface)) { 321 _type = LuaVarType.Userdata; 322 } else static if (is(T == LuaTable)) { 323 _type = LuaVarType.Table; 324 } else { 325 _type = LuaVarType.Null; 326 } 327 } 328 ///Returns the type held by this stucture. 329 public LuaVarType type() const @nogc @safe pure nothrow { 330 return _type; 331 } 332 ///Internal dereference. 333 private T deRef(T)() const @nogc @system pure nothrow { 334 static if (is(T == void*) || is(T == LuaTable*)) { 335 return cast(T)dataPtr; 336 } else static if (isIntegral!T || isBoolean!T) { 337 return cast(T)dataInt; 338 } else static if (isFloatingPoint!T) { 339 return cast(T)dataNum; 340 } else static if (is(T == string)) { 341 return fromStringz(cast(const(char*))dataPtr); 342 } else static if (is(T == const(char*))) { 343 return cast(const(char*))dataPtr; 344 } else static assert(0, "Unsupported type!"); 345 } 346 /** 347 * Pushes the struct's value to the given lua_State. 348 */ 349 package void pushToLuaState(lua_State* state) @system nothrow { 350 final switch (_type) with (LuaVarType) { 351 case Null: 352 lua_pushnil(state); 353 break; 354 case Boolean: 355 lua_pushboolean(state, deRef!bool()); 356 break; 357 case Number: 358 lua_pushnumber(state, deRef!double()); 359 break; 360 case Integer: 361 lua_pushinteger(state, deRef!long()); 362 break; 363 case String: 364 lua_pushstring(state, cast(const(char*))dataPtr); 365 break; 366 case Function: 367 break; 368 case Userdata: 369 lua_pushlightuserdata(state, dataPtr); 370 break; 371 case Thread: 372 break; 373 case Table: 374 break; 375 } 376 } 377 /** 378 * Returns the given type of `T`. 379 * Throws: LuaException in case of type mismatch. 380 */ 381 public T get(T)() const @trusted pure { 382 static if (isIntegral!T) { 383 if (_type == LuaVarType.Integer) 384 return cast(T)deRef!long; 385 } else static if (isFloatingPoint!T) { 386 if (_type == LuaVarType.Integer) 387 return cast(T)deRef!double; 388 } else static if (is(T == string)) { 389 if (_type == LuaVarType.String) 390 return deRef!string; 391 } else static if (is(T == const(char*))){ 392 if (_type == LuaVarType.String) 393 return deRef!(const(char*)); 394 } else static if (is(T == void*)) { 395 if (_type == LuaVarType.Userdata) 396 return deRef!(void*); 397 } else static if (is(T == bool)) { 398 if (_type == LuaVarType.Boolean) 399 return deRef!bool; 400 } else static if (is(typeof(T) == LuaTable*)) { 401 if (_type == LuaVarType.Table) 402 return deRef!(LuaTable*); 403 } else static assert(0, "Type not supported!"); 404 throw new LuaException(7, "Wrong type!"); 405 } 406 /** 407 * used to implement an interface with tables in Lua. 408 */ 409 public bool opEquals(const LuaVar other) const @nogc @trusted pure nothrow { 410 if (_type != other._type) return false; 411 switch (_type) { 412 case LuaVarType.Number: 413 return deRef!double() == other.deRef!double(); 414 case LuaVarType.Integer: 415 return deRef!long() == other.deRef!long(); 416 case LuaVarType.String: 417 return deRef!string() == other.deRef!string(); 418 case LuaVarType.Boolean: 419 return deRef!bool() == other.deRef!bool(); 420 case LuaVarType.Userdata: 421 return deRef!(void*)() == other.deRef!(void*)(); 422 default: 423 return false; 424 } 425 } 426 /** 427 * Assigns the value to this struct and sets the type if needed. 428 */ 429 public auto opAssign(T)(T val) @safe pure nothrow { 430 static if (is(T == void*) || is(T == LuaTable*)) { 431 dataPtr = val; 432 } else static if (isIntegral!T || isBoolean!T) { 433 dataInt = val; 434 } else static if (isFloatingPoint!T) { 435 dataNum = val; 436 } 437 setType!T; 438 return this; 439 } 440 /** 441 * Casts the type to `T` if possible. 442 * Throws: LuaException, if implicit type casting is impossible. 443 */ 444 T opCast(T)() const @safe pure { 445 import std.math : nearbyint; 446 static if (isIntegral!T) { 447 if (_type == LuaVarType.Integer) { 448 return cast(T)dataInt; 449 } else if (_type == LuaVarType.Number) { 450 return cast(T)nearbyint(dataNum); 451 } 452 } else static if (isFloatingPoint!T) { 453 if (_type == LuaVarType.Integer) { 454 return cast(T)dataInt; 455 } else if (_type == LuaVarType.Number) { 456 return cast(T)dataNum; 457 } 458 } else static if (is(T == string)) { 459 string ww() const @system pure { 460 return fromStringz(cast(char*)dataPtr); 461 } 462 string w() const @trusted pure { 463 return ww; 464 } 465 if (_type == LuaVarType.String) { 466 return w; 467 } 468 } 469 return get!T(); 470 } 471 } 472 473 alias LuaTable = LinkedMap!(LuaVar, LuaVar); 474 /** 475 * Implements an interface to the lua_State* variable with automatic garbage management and some basic functionality. 476 */ 477 public class LuaScript { 478 protected lua_State* state; 479 protected string source; 480 protected bool isLoaded; 481 /** 482 * Initializes a Lua script from the provided source code. 483 * Params: 484 * source = The source code of the script file. 485 * name = The name of the file. 486 * Throws: LuaException, if either a syntax or memory error was encountered. 487 */ 488 this(string source, const(char*) name) { 489 this.source = source; 490 state = lua_newstate(&luaAllocator, null); 491 const int errorCode = lua_load(state, &reader, cast(void*)this, name, null); 492 switch (errorCode) { 493 default: 494 break; 495 case LUA_ERRSYNTAX: 496 throw new LuaException(LUA_ERRSYNTAX, "Syntax error in file!"); 497 case LUA_ERRMEM: 498 throw new LuaException(LUA_ERRMEM, "Memory error!"); 499 } 500 registerLibForScripting(state); 501 } 502 ///Automatically deallocates the lua_State variable. 503 ~this() { 504 lua_close(state); 505 } 506 ///Returns the lua_State variable for any manual use. 507 public lua_State* getState() @nogc nothrow pure { 508 return state; 509 } 510 /** 511 * Executes the main function of the scipt. 512 * Returns: A LuaVar variable with the appropriate return value if there was any. 513 * Throws: A LuaException if the execution ran into an error, or the function isn't found. 514 */ 515 public LuaVar runMain() { 516 return callLuaFunc!(LuaVar, "main")(state); 517 } 518 extern(C) 519 private static const(char*) reader(lua_State* st, void* data, size_t* size) nothrow { 520 LuaScript ls = cast(LuaScript)data; 521 if (ls.isLoaded) { 522 *size = 0; 523 return null; 524 } else { 525 ls.isLoaded = true; 526 *size = ls.source.length; 527 return ls.source.ptr; 528 } 529 } 530 } 531 /** 532 * Thrown on errors encountered during Lua script execution. 533 */ 534 public class LuaException : PPEException { 535 public int errorCode; 536 /// 537 @nogc @safe pure nothrow this(int errorCode, string msg, string file = __FILE__, size_t line = __LINE__, 538 Throwable nextInChain = null) 539 { 540 this.errorCode = errorCode; 541 super(msg, file, line, nextInChain); 542 } 543 /// 544 @nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__) 545 { 546 super(msg, file, line, nextInChain); 547 } 548 }