From 54e7e36ee94634371c76881847043b7c70f54a5a Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 28 May 2017 23:51:57 +0200 Subject: [PATCH] Implemented basis for Lua scripting and registering FSX variables - incomplete and crashes on shutdown --- .gitignore | 5 + G940LEDControl/Bin/lua52.dll | Bin 0 -> 239851 bytes G940LEDControl/DelphiLua/Lua.API.pas | 767 +++++ G940LEDControl/DelphiLua/Lua.pas | 2484 +++++++++++++++++ G940LEDControl/DelphiLua/readme.txt | 1 + G940LEDControl/Forms/MainFrm.dfm | 8 + G940LEDControl/Forms/MainFrm.pas | 17 + G940LEDControl/G940LEDControl.dpr | 6 +- G940LEDControl/G940LEDControl.dproj | 5 +- G940LEDControl/G940LEDControl.res | Bin 28540 -> 27828 bytes G940LEDControl/Units/FSXLEDFunction.pas | 5 +- .../Units/FSXLEDFunctionProvider.pas | 276 +- G940LEDControl/Units/FSXLEDFunctionWorker.pas | 6 +- .../Units/LuaLEDFunctionProvider.pas | 332 +++ G940LEDControl/Units/StaticLEDFunction.pas | 4 - .../XSD/SimBaseDocument.settings.xml | 2 +- G940LEDControl/XSD/SimBaseDocument.xsd | 2 - 17 files changed, 3836 insertions(+), 84 deletions(-) create mode 100644 .gitignore create mode 100644 G940LEDControl/Bin/lua52.dll create mode 100644 G940LEDControl/DelphiLua/Lua.API.pas create mode 100644 G940LEDControl/DelphiLua/Lua.pas create mode 100644 G940LEDControl/DelphiLua/readme.txt create mode 100644 G940LEDControl/Units/LuaLEDFunctionProvider.pas diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5a1f635 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__history +*.local +*.dcu +*.exe +*.identcache diff --git a/G940LEDControl/Bin/lua52.dll b/G940LEDControl/Bin/lua52.dll new file mode 100644 index 0000000000000000000000000000000000000000..d5c63a5e7157473bf367d83b44800f0cfd2ecdf8 GIT binary patch literal 239851 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

i%jEuHCK3%PaRI?WH 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%0AfH2r~&GLdSDsQ0o)GU4Qv2D2z(3}2DSquz>~l? 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') } + LUA_SIGNATURE = #33'Lua'; + + { option for multiple returns in 'lua_pcall' and 'lua_call' } + LUA_MULTRET = -1; + + { reserve some space for error handling } + LUAI_MAXSTACK = 1000000; + LUAI_FIRSTPSEUDOIDX = (-LUAI_MAXSTACK - 1000); + + { pseudo-indices } + LUA_REGISTRYINDEX = LUAI_FIRSTPSEUDOIDX; + + function lua_upvalueindex(idx: Integer): Integer; inline; + +const + { thread status } + LUA_OK = 0; + LUA_YIELD_ = 1; + LUA_ERRRUN = 2; + LUA_ERRSYNTAX = 3; + LUA_ERRMEM = 4; + LUA_ERRGCMM = 5; + LUA_ERRERR = 6; + + +type + size_t = NativeUint; + psize_t = ^size_t; + + lua_State = type Pointer; + lua_CFunction = function(L: lua_State): Integer; cdecl; + + { functions that read/write blocks when loading/dumping Lua chunks } + lua_Reader = function(L: lua_State; ud: Pointer; var sz: size_t): PAnsiChar; cdecl; + lua_Writer = function(L: lua_State; p: Pointer; sz: size_t; ud: Pointer): Integer; cdecl; + + { prototype for memory-allocation functions } + lua_Alloc = function(ud, ptr: Pointer; osize, nsize: size_t): Pointer; cdecl; + +const + { basic types } + LUA_TNONE = (-1); + + LUA_TNIL = 0; + LUA_TBOOLEAN = 1; + LUA_TLIGHTUSERDATA = 2; + LUA_TNUMBER = 3; + LUA_TSTRING = 4; + LUA_TTABLE = 5; + LUA_TFUNCTION = 6; + LUA_TUSERDATA = 7; + LUA_TTHREAD = 8; + + LUA_NUMTAGS = 9; + + { minimum Lua stack available to a C function } + LUA_MINSTACK = 20; + + + { predefined values in the registry } + LUA_RIDX_MAINTHREAD = 1; + LUA_RIDX_GLOBALS = 2; + LUA_RIDX_LAST = LUA_RIDX_GLOBALS; + + +type + { type of numbers in Lua } + lua_Number = type Double; + + { type for integer functions } + lua_Integer = type Integer; + + { unsigned integer type } + lua_Unsigned = type Cardinal; + + +var + { state manipulation } + lua_newstate: function(f: lua_Alloc; ud: Pointer): lua_State; cdecl; + lua_close: procedure(L: lua_State); cdecl; + lua_newthread: function(L: lua_State): lua_State; cdecl; + + lua_atpanic: function(L: lua_State; panicf: lua_CFunction): lua_CFunction; cdecl; + lua_version: function(L: lua_State): lua_Number; cdecl; + + { basic stack manipulation } + lua_absindex: function(L: lua_State; idx: Integer): Integer; cdecl; + lua_gettop: function(L: lua_State): Integer; cdecl; + + lua_settop: procedure(L: lua_State; idx: Integer); cdecl; + lua_pushvalue: procedure(L: lua_State; idx: Integer); cdecl; + lua_remove: procedure(L: lua_State; idx: Integer); cdecl; + lua_insert: procedure(L: lua_State; idx: Integer); cdecl; + lua_replace: procedure(L: lua_State; idx: Integer); cdecl; + lua_copy: procedure(L: lua_State; fromidx, toidx: Integer); cdecl; + lua_checkstack: function(L: lua_State; sz: Integer): Integer; cdecl; + + lua_xmove: procedure(from: lua_State; _to: lua_State; n: Integer); cdecl; + + { access functions (stack -> C) } + lua_isnumber: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_isstring: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_iscfunction: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_isuserdata: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_type: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_typename: function (L: lua_State; tp: Integer): PAnsiChar; cdecl; + lua_tonumberx: function (L: lua_State; idx: Integer; isnum: PInteger): lua_Number; cdecl; + lua_tointegerx: function (L: lua_State; idx: Integer; isnum: PInteger): lua_Integer; cdecl; + lua_tounsignedx: function (L: lua_State; idx: Integer; isnum: PInteger): lua_Unsigned; cdecl; + lua_toboolean: function (L: lua_State; idx: Integer): Integer; cdecl; + lua_tolstring: function(L: lua_State; idx: Integer; len: psize_t): PAnsiChar; cdecl; + lua_rawlen: function (L: lua_State; idx: Integer): size_t; cdecl; + lua_tocfunction: function (L: lua_State; idx: Integer): lua_CFunction; cdecl; + lua_touserdata: function (L: lua_State; idx: Integer): Pointer; cdecl; + lua_tothread: function (L: lua_State; idx: Integer): lua_State; cdecl; + lua_topointer: function (L: lua_State; idx: Integer): Pointer; cdecl; + + +const + { Comparison and arithmetic functions } + LUA_OPADD = 0; { ORDER TM } + LUA_OPSUB = 1; + LUA_OPMUL = 2; + LUA_OPDIV = 3; + LUA_OPMOD = 4; + LUA_OPPOW = 5; + LUA_OPUNM = 6; + + +var + lua_arith: procedure(L: lua_State; op: Integer); cdecl; + + +const + LUA_OPEQ = 0; + LUA_OPLT = 1; + LUA_OPLE = 2; + + +var + lua_rawequal: function(L: lua_State; idx1, idx2: Integer): Integer; cdecl; + lua_compare: function(L: lua_State; idx1, idx2, op: Integer): Integer; cdecl; + + + { push functions (C -> stack) } + lua_pushnil: procedure(L: lua_State); cdecl; + lua_pushnumber: procedure(L: lua_State; n: lua_Number); cdecl; + lua_pushinteger: procedure(L: lua_State; n: lua_Integer); cdecl; + lua_pushunsigned: procedure(L: lua_State; n: lua_Unsigned); cdecl; + lua_pushlstring: function (L: lua_State; s: PAnsiChar; l_: size_t): PAnsiChar; cdecl; + lua_pushstring: function (L: lua_State; s: PAnsiChar): PAnsiChar; cdecl; + lua_pushvfstring: function (L: lua_State; fmt: PAnsiChar; argp: Pointer): PAnsiChar; cdecl; + lua_pushfstring: function (L: lua_State; fmt: PAnsiChar; argp: Pointer): PAnsiChar; cdecl; + lua_pushcclosure: procedure(L: lua_State; fn: lua_CFunction; n: Integer); cdecl; + lua_pushboolean: procedure(L: lua_State; b: Integer); cdecl; + lua_pushlightuserdata: procedure(L: lua_State; p: Pointer); cdecl; + lua_pushthread: function (L: lua_State): Integer; cdecl; + + { get functions (Lua -> stack) } + lua_getglobal: procedure(L: lua_State; value: PAnsiChar); cdecl; + lua_gettable: procedure(L: lua_State; idx: Integer); cdecl; + lua_getfield: procedure(L: lua_State; idx: Integer; k: PAnsiChar); cdecl; + lua_rawget: procedure(L: lua_State; idx: Integer); cdecl; + lua_rawgeti: procedure(L: lua_State; idx, n: Integer); cdecl; + lua_rawgetp: procedure(L: lua_State; idx: Integer; p: Pointer); cdecl; + lua_createtable: procedure(L: lua_State; narr: Integer; nrec: Integer); cdecl; + lua_newuserdata: procedure(L: lua_State; sz: size_t); cdecl; + lua_getmetatable: function(L: lua_State; objindex: Integer): Integer; cdecl; + lua_getuservalue: procedure(L: lua_State; idx: Integer); cdecl; + + { set functions (stack -> Lua) } + lua_setglobal: procedure(L: lua_State; value: PAnsiChar); cdecl; + lua_settable: procedure(L: lua_State; idx: Integer); cdecl; + lua_setfield: procedure(L: lua_State; idx: Integer; k: PAnsiChar); cdecl; + lua_rawset: procedure(L: lua_State; idx: Integer); cdecl; + lua_rawseti: procedure(L: lua_State; idx, n: Integer); cdecl; + lua_rawsetp: procedure(L: lua_State; idx: Integer; p: Pointer); cdecl; + lua_setmetatable: function(L: lua_State; objindex: Integer): Integer; cdecl; + lua_setuservalue: procedure(L: lua_State; idx: Integer); cdecl; + + + { 'load' and 'call' functions (load and run Lua code) } + lua_callk: procedure(L: lua_State; nargs, nresults, ctx: Integer; k: lua_CFunction); cdecl; + lua_getctx: function(L: lua_State; var ctx: Integer): Integer; cdecl; + lua_pcallk: function(L: lua_State; nargs, nresults, errfunc, ctx: Integer; k: lua_CFunction): Integer; cdecl; + + lua_load: function(L: lua_State; reader: lua_Reader; dt: Pointer; chunkname, mode: PAnsiChar): Integer; cdecl; + lua_dump: function(L: lua_State; writer: lua_Writer; data: Pointer): Integer; cdecl; + + + procedure lua_call(L: lua_State; nargs, nresults: Integer); inline; + function lua_pcall(L: lua_State; nargs, nresults, errfunc: Integer): Integer; inline; + + +var + { coroutine functions } + lua_yieldk: function(L: lua_State; nresults, ctx: Integer; k: lua_CFunction): Integer; cdecl; + lua_resume: function(L, from: lua_State; nargs: Integer): Integer; cdecl; + lua_status: function(L: lua_State): Integer; cdecl; + + function lua_yield(L: lua_State; nresults: Integer): Integer; inline; + + +const + { garbage-collection function and options } + LUA_GCSTOP = 0; + LUA_GCRESTART = 1; + LUA_GCCOLLECT = 2; + LUA_GCCOUNT = 3; + LUA_GCCOUNTB = 4; + LUA_GCSTEP = 5; + LUA_GCSETPAUSE = 6; + LUA_GCSETSTEPMU = 7; + LUA_GCSETMAJORIN = 8; + LUA_GCISRUNNING = 9; + LUA_GCGEN = 0; + LUA_GCINC = 1; + +var + lua_gc: function(L: lua_State; what, data: Integer): Integer; cdecl; + + +var + { miscellaneous functions } + lua_error: function(L: lua_State): Integer; cdecl; + lua_next: function(L: lua_State; idx: Integer): Integer; cdecl; + + lua_concat: procedure(L: lua_State; n: Integer); cdecl; + lua_len: procedure(L: lua_State; idx: Integer); cdecl; + + lua_getallocf: function(L: lua_State; ud: Pointer): lua_Alloc; cdecl; + lua_setallocf: procedure(L: lua_State; f: lua_Alloc; ud: Pointer); cdecl; + + + { some useful macros } + function lua_tonumber(L: lua_State; idx: Integer): lua_Number; inline; + function lua_tointeger(L: lua_State; idx: Integer): lua_Integer; inline; + function lua_tounsigned(L: lua_State; idx: Integer): lua_Unsigned; inline; + + procedure lua_pop(L: lua_State; n: Integer); inline; + procedure lua_newtable(L: lua_State); inline; + procedure lua_register(L: lua_State; name: PAnsiChar; f: lua_CFunction); inline; + procedure lua_pushcfunction(L: lua_State; f: lua_CFunction); inline; + + function lua_isfunction(L: lua_State; n: Integer): Boolean; inline; + function lua_istable(L: lua_State; n: Integer): Boolean; inline; + function lua_islightuserdata(L: lua_State; n: Integer): Boolean; inline; + function lua_isnil(L: lua_State; n: Integer): Boolean; inline; + function lua_isboolean(L: lua_State; n: Integer): Boolean; inline; + function lua_isthread(L: lua_State; n: Integer): Boolean; inline; + function lua_isnone(L: lua_State; n: Integer): Boolean; inline; + function lua_isnoneornil(L: lua_State; n: Integer): Boolean; inline; + function lua_pushliteral(L: lua_State; const s: AnsiString): PAnsiChar; inline; + + procedure lua_pushglobaltable(L: lua_State); inline; + function lua_tostring(L: lua_State; idx: Integer): PAnsiChar; inline; + + +const + {====================================================================== + Debug API + =======================================================================} + + { Event codes } + LUA_HOOKCALL = 0; + LUA_HOOKRET = 1; + LUA_HOOKLINE = 2; + LUA_HOOKCOUNT = 3; + LUA_HOOKTAILCALL = 4; + + + { Event masks } + LUA_MASKCALL = (1 shl LUA_HOOKCALL); + LUA_MASKRET = (1 shl LUA_HOOKRET); + LUA_MASKLINE = (1 shl LUA_HOOKLINE); + LUA_MASKCOUNT = (1 shl LUA_HOOKCOUNT); + + +const + LUA_IDSIZE = 60; + + +type + lua_Debug = record + event: Integer; + name: PAnsiChar; + namewhat: PAnsiChar; // 'global', 'local', 'field', 'method' + what: PAnsiChar; // 'Lua', 'C', 'main', 'tail' + source: PAnsiChar; + currentline: Integer; + linedefined: Integer; + lastlinedefined: Integer; + nups: Byte; + nparams: Byte; + isvararg: Byte; + istailcall: Byte; + short_src: array[0..LUA_IDSIZE - 1] of AnsiChar; + //struct CallInfo *i_ci; /* active function */ + end; + + { Functions to be called by the debugger in specific events } + lua_Hook = procedure(L: lua_State; var ar: lua_Debug); cdecl; + + +var + lua_getstack: function(L: lua_State; level: Integer; var ar: lua_Debug): Integer; cdecl; + lua_getinfo: function(L: lua_State; what: PAnsiChar; var ar: lua_Debug): Integer; cdecl; + + lua_getlocal: function(L: lua_State; var ar: lua_Debug; n: Integer): PAnsiChar; cdecl; + lua_setlocal: function(L: lua_State; var ar: lua_Debug; n: Integer): PAnsiChar; cdecl; + lua_getupvalue: function(L: lua_State; funcindex, n: Integer): PAnsiChar; cdecl; + lua_setupvalue: function(L: lua_State; funcindex, n: Integer): PAnsiChar; cdecl; + + lua_upvalueid: function(L: lua_State; fidx, n: Integer): Pointer; cdecl; + lua_upvaluejoin: procedure(L: lua_State; fidx1, n1, fidx2, n2: Integer); + + lua_sethook: function(L: lua_State; func: lua_Hook; mask, count: Integer): Integer; cdecl; + lua_gethook: function(L: lua_State): lua_Hook; cdecl; + lua_gethookmask: function(L: lua_State): Integer; cdecl; + lua_gethookcount: function(L: lua_State): Integer; cdecl; + + {====================================================================== } + + +const + LUA_COLIBNAME = 'coroutine'; + LUA_TABLIBNAME = 'table'; + LUA_IOLIBNAME = 'io'; + LUA_OSLIBNAME = 'os'; + LUA_STRLIBNAME = 'string'; + LUA_BITLIBNAME = 'bit32'; + LUA_MATHLIBNAME = 'math'; + LUA_DBLIBNAME = 'debug'; + LUA_LOADLIBNAME = 'package'; + + +var + { lualib } + luaopen_base: function(L: lua_State): Integer; cdecl; + luaopen_coroutine: function(L: lua_State): Integer; cdecl; + luaopen_table: function(L: lua_State): Integer; cdecl; + luaopen_io: function(L: lua_State): Integer; cdecl; + luaopen_os: function(L: lua_State): Integer; cdecl; + luaopen_string: function(L: lua_State): Integer; cdecl; + luaopen_bit32: function(L: lua_State): Integer; cdecl; + luaopen_math: function(L: lua_State): Integer; cdecl; + luaopen_debug: function(L: lua_State): Integer; cdecl; + luaopen_package: function(L: lua_State): Integer; cdecl; + + { open all previous libraries } + luaL_openlibs: procedure(L: lua_State); cdecl; + luaL_requiref: procedure(L: lua_State; modname: PAnsiChar; openf: lua_CFunction; glb: Integer); cdecl; + + + +type + luaL_Reg = record + name: PAnsiChar; + func: lua_CFunction; + end; + PluaL_Reg = ^luaL_Reg; + +var + luaL_setfuncs: procedure(L: lua_State; luaL_Reg: PluaL_Reg; nup: Integer); cdecl; + + procedure luaL_where(L: lua_State; level: Integer); + function luaL_error(L: lua_State; fmt: PAnsiChar; argp: Pointer): Integer; + + + +const + DefaultLuaLibName = 'lua' + LUA_VERSION_MAJOR + LUA_VERSION_MINOR + '.dll'; + + procedure LoadLuaLib(const AFileName: string = DefaultLuaLibName); + procedure UnloadLuaLib; + + function LuaLibLoaded: Boolean; + + function DefaultLuaAlloc(ud, ptr: Pointer; osize, nsize: size_t): Pointer; cdecl; + + +implementation +uses + System.Generics.Collections, + Winapi.Windows; + +var + LuaLibHandle: THandle; + LuaFunctions: TList; + + +function DefaultLuaAlloc(ud, ptr: Pointer; osize, nsize: size_t): Pointer; +begin + if (nsize = 0) then + begin + FreeMemory(ptr); + Result := nil; + end else + Result := ReallocMemory(ptr, nsize); +end; + + +procedure LoadLuaLib(const AFileName: string = DefaultLuaLibName); + + procedure Load(var AVariable: Pointer; const AName: string); + begin + AVariable := GetProcAddress(LuaLibHandle, PChar(AName)); + LuaFunctions.Add(@AVariable); + end; + + +begin + UnloadLuaLib; + + LuaLibHandle := SafeLoadLibrary(AFileName); + if LuaLibHandle = 0 then + RaiseLastOSError; + + if not Assigned(LuaFunctions) then + LuaFunctions := TList.Create(); + + Load(@lua_newstate, 'lua_newstate'); + Load(@lua_close, 'lua_close'); + Load(@lua_newthread, 'lua_newthread'); + + Load(@lua_atpanic, 'lua_atpanic'); + Load(@lua_version, 'lua_version'); + + Load(@lua_absindex, 'lua_absindex'); + Load(@lua_gettop, 'lua_gettop'); + Load(@lua_settop, 'lua_settop'); + Load(@lua_pushvalue, 'lua_pushvalue'); + Load(@lua_remove, 'lua_remove'); + Load(@lua_insert, 'lua_insert'); + Load(@lua_replace, 'lua_replace'); + Load(@lua_copy, 'lua_copy'); + Load(@lua_checkstack, 'lua_checkstack'); + Load(@lua_xmove, 'lua_xmove'); + + Load(@lua_isnumber, 'lua_isnumber'); + Load(@lua_isstring, 'lua_isstring'); + Load(@lua_iscfunction, 'lua_iscfunction'); + Load(@lua_isuserdata, 'lua_isuserdata'); + Load(@lua_type, 'lua_type'); + Load(@lua_typename, 'lua_typename'); + Load(@lua_tonumberx, 'lua_tonumberx'); + Load(@lua_tointegerx, 'lua_tointegerx'); + Load(@lua_tounsignedx, 'lua_tounsignedx'); + Load(@lua_toboolean, 'lua_toboolean'); + Load(@lua_tolstring, 'lua_tolstring'); + Load(@lua_rawlen, 'lua_rawlen'); + Load(@lua_tocfunction, 'lua_tocfunction'); + Load(@lua_touserdata, 'lua_touserdata'); + Load(@lua_tothread, 'lua_tothread'); + Load(@lua_topointer, 'lua_topointer'); + + Load(@lua_arith, 'lua_arith'); + Load(@lua_rawequal, 'lua_rawequal'); + Load(@lua_compare, 'lua_compare'); + + Load(@lua_pushnil, 'lua_pushnil'); + Load(@lua_pushnumber, 'lua_pushnumber'); + Load(@lua_pushinteger, 'lua_pushinteger'); + Load(@lua_pushunsigned, 'lua_pushunsigned'); + Load(@lua_pushlstring, 'lua_pushlstring'); + Load(@lua_pushstring, 'lua_pushstring'); + Load(@lua_pushvfstring, 'lua_pushvfstring'); + Load(@lua_pushfstring, 'lua_pushfstring'); + Load(@lua_pushcclosure, 'lua_pushcclosure'); + Load(@lua_pushboolean, 'lua_pushboolean'); + Load(@lua_pushlightuserdata, 'lua_pushlightuserdata'); + Load(@lua_pushthread, 'lua_pushthread'); + + Load(@lua_getglobal, 'lua_getglobal'); + Load(@lua_gettable, 'lua_gettable'); + Load(@lua_getfield, 'lua_getfield'); + Load(@lua_rawget, 'lua_rawget'); + Load(@lua_rawgeti, 'lua_rawgeti'); + Load(@lua_rawgetp, 'lua_rawgetp'); + Load(@lua_newuserdata, 'lua_newuserdata'); + Load(@lua_getmetatable, 'lua_getmetatable'); + Load(@lua_getuservalue, 'lua_getuservalue'); + + Load(@lua_createtable, 'lua_createtable'); + Load(@lua_setglobal, 'lua_setglobal'); + Load(@lua_settable, 'lua_settable'); + Load(@lua_setfield, 'lua_setfield'); + + Load(@lua_rawset, 'lua_rawset'); + Load(@lua_rawseti, 'lua_rawseti'); + Load(@lua_rawsetp, 'lua_rawsetp'); + Load(@lua_setmetatable, 'lua_setmetatable'); + Load(@lua_setuservalue, 'lua_setuservalue'); + + Load(@lua_callk, 'lua_callk'); + Load(@lua_getctx, 'lua_getctx'); + Load(@lua_pcallk, 'lua_pcallk'); + + Load(@lua_yieldk, 'lua_yieldk'); + Load(@lua_resume, 'lua_resume'); + Load(@lua_status, 'lua_status'); + + Load(@lua_gc, 'lua_gc'); + + Load(@lua_load, 'lua_load'); + Load(@lua_dump, 'lua_dump'); + + Load(@lua_error, 'lua_error'); + Load(@lua_next, 'lua_next'); + + Load(@lua_concat, 'lua_concat'); + Load(@lua_len, 'lua_len'); + + Load(@lua_getallocf, 'lua_getallocf'); + Load(@lua_setallocf, 'lua_setallocf'); + + + Load(@lua_getstack, 'lua_getstack'); + Load(@lua_getinfo, 'lua_getinfo'); + + Load(@lua_getlocal, 'lua_getlocal'); + Load(@lua_setlocal, 'lua_setlocal'); + Load(@lua_getupvalue, 'lua_getupvalue'); + Load(@lua_setupvalue, 'lua_setupvalue'); + + Load(@lua_upvalueid, 'lua_upvalueid'); + Load(@lua_upvaluejoin, 'lua_upvaluejoin'); + + Load(@lua_sethook, 'lua_sethook'); + Load(@lua_gethook, 'lua_gethook'); + Load(@lua_gethookmask, 'lua_gethookmask'); + Load(@lua_gethookcount, 'lua_gethookcount'); + + Load(@luaopen_base, 'luaopen_base'); + Load(@luaopen_coroutine, 'luaopen_coroutine'); + Load(@luaopen_table, 'luaopen_table'); + Load(@luaopen_io, 'luaopen_io'); + Load(@luaopen_os, 'luaopen_os'); + Load(@luaopen_string, 'luaopen_string'); + Load(@luaopen_bit32, 'luaopen_bit32'); + Load(@luaopen_math, 'luaopen_math'); + Load(@luaopen_debug, 'luaopen_debug'); + Load(@luaopen_package, 'luaopen_package'); + + Load(@luaL_openlibs, 'luaL_openlibs'); + Load(@luaL_requiref, 'luaL_requiref'); + + Load(@luaL_setfuncs, 'luaL_setfuncs'); +end; + + +procedure UnloadLuaLib; +var + variable: PPointer; + +begin + if Assigned(LuaFunctions) then + begin + for variable in LuaFunctions do + variable^ := nil; + end; + + if LuaLibLoaded then + begin + FreeLibrary(LuaLibHandle); + LuaLibHandle := 0; + end; +end; + + +function LuaLibLoaded: Boolean; +begin + Result := (LuaLibHandle <> 0); +end; + + +{ Macros } +function lua_upvalueindex(idx: Integer): Integer; +begin + Result := (LUA_REGISTRYINDEX - idx); +end; + +procedure lua_call(L: lua_State; nargs, nresults: Integer); +begin + lua_callk(L, nargs, nresults, 0, nil); +end; + +function lua_pcall(L: lua_State; nargs, nresults, errfunc: Integer): Integer; +begin + Result := lua_pcallk(L, nargs, nresults, errfunc, 0, nil); +end; + +function lua_yield(L: lua_State; nresults: Integer): Integer; +begin + Result := lua_yieldk(L, nresults, 0, nil); +end; + +function lua_tonumber(L: lua_State; idx: Integer): lua_Number; +begin + Result := lua_tonumberx(L, idx, nil); +end; + +function lua_tointeger(L: lua_State; idx: Integer): lua_Integer; +begin + Result := lua_tointegerx(L, idx, nil); +end; + +function lua_tounsigned(L: lua_State; idx: Integer): lua_Unsigned; +begin + Result := lua_tounsignedx(L, idx, nil); +end; + +procedure lua_pop(L: lua_State; n: Integer); +begin + lua_settop(L, -(n) - 1); +end; + +procedure lua_newtable(L: lua_State); inline; +begin + lua_createtable(L, 0, 0); +end; + +procedure lua_register(L: lua_State; name: PAnsiChar; f: lua_CFunction); inline; +begin + lua_pushcfunction(L, f); + lua_setglobal(L, name); +end; + +procedure lua_pushcfunction(L: lua_State; f: lua_CFunction); +begin + lua_pushcclosure(L, f, 0); +end; + +function lua_isfunction(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TFUNCTION; +end; + +function lua_istable(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TTABLE; +end; + +function lua_islightuserdata(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TLIGHTUSERDATA; +end; + +function lua_isnil(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TNIL; +end; + +function lua_isboolean(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TBOOLEAN; +end; + +function lua_isthread(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TTHREAD; +end; + +function lua_isnone(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) = LUA_TNONE; +end; + +function lua_isnoneornil(L: lua_State; n: Integer): Boolean; +begin + Result := lua_type(L, n) <= 0; +end; + +function lua_pushliteral(L: lua_State; const s: AnsiString): PAnsiChar; +begin + Result := lua_pushlstring(L, PAnsiChar(s), Length(s)); +end; + +procedure lua_pushglobaltable(L: lua_State); +begin + lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); +end; + +function lua_tostring(L: lua_State; idx: Integer): PAnsiChar; +begin + Result := lua_tolstring(L, idx, nil); +end; + +procedure luaL_where(L: lua_State; level: Integer); +var + ar: lua_Debug; + msg: AnsiString; + +begin + if (lua_getstack(L, level, ar) <> 0) then // check function at level + begin + lua_getinfo(L, 'Sl', ar); // get info about it + if (ar.currentline > 0) then // is there info? + begin + msg := AnsiString(Format('%s:%d: ', [ar.short_src, ar.currentline])); + lua_pushlstring(L, PAnsiChar(msg), Length(msg)); + exit + end; + end; + lua_pushliteral(L, ''); // else, no information available... +end; + +function luaL_error(L: lua_State; fmt: PAnsiChar; argp: Pointer): Integer; +begin + luaL_where(L, 1); + lua_pushvfstring(L, fmt, argp); + lua_concat(L, 2); + Result := lua_error(L); +end; + + +initialization +finalization + UnloadLuaLib; + FreeAndNil(LuaFunctions); + +end. diff --git a/G940LEDControl/DelphiLua/Lua.pas b/G940LEDControl/DelphiLua/Lua.pas new file mode 100644 index 0000000..f00ec3e --- /dev/null +++ b/G940LEDControl/DelphiLua/Lua.pas @@ -0,0 +1,2484 @@ +{ + Wrapper classes for Lua API + + Created by M. van Renswoude, April 2014: + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +} +unit Lua; + +interface +uses + System.Classes, + System.Generics.Collections, + System.Generics.Defaults, + System.Rtti, + System.SysUtils, + + Lua.API; + +type + ELuaException = class(Exception); + ELuaInitException = class(ELuaException); + ELuaUnsupportedParameterException = class(ELuaException); + ELuaUnsupportedVariableException = class(ELuaException); + ELuaNoFunctionException = class(ELuaException); + ELuaNativeCodeException = class(ELuaException); + + TLuaLibrary = (Base, Coroutine, Table, IO, OS, StringLib, Bit32, Math, Debug, Package, All); + TLuaLibraries = set of TLuaLibrary; + + TLuaDataType = (LuaNone, LuaNil, LuaNumber, LuaBoolean, LuaString, LuaTable, + LuaFunction, LuaUserData, LuaThread, LuaLightUserData); + + TLuaVariableType = (VariableNone, VariableBoolean, VariableInteger, + VariableNumber, VariableUserData, VariableString, + VariableTable, VariableFunction); + + ILuaTable = interface; + ILuaFunction = interface; + + + ILuaVariable = interface + ['{ADA0D4FB-F0FB-4493-8FEC-6FC92C80117F}'] + function GetVariableType: TLuaVariableType; + function GetDataType: TLuaDataType; + + function GetAsBoolean: Boolean; + function GetAsInteger: Integer; + function GetAsNumber: Double; + function GetAsUserData: Pointer; + function GetAsString: string; + function GetAsTable: ILuaTable; + function GetAsFunction: ILuaFunction; + + procedure SetAsBoolean(ABoolean: Boolean); + procedure SetAsInteger(AInteger: Integer); + procedure SetAsNumber(ANumber: Double); + procedure SetAsUserData(AUserData: Pointer); + procedure SetAsString(AString: string); + procedure SetAsTable(ATable: ILuaTable); + + property VariableType: TLuaVariableType read GetVariableType; + property DataType: TLuaDataType read GetDataType; + + property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean; + property AsInteger: Integer read GetAsInteger write SetAsInteger; + property AsNumber: Double read GetAsNumber write SetAsNumber; + property AsUserData: Pointer read GetAsUserData write SetAsUserData; + property AsString: string read GetAsString write SetAsString; + property AsTable: ILuaTable read GetAsTable write SetAsTable; + property AsFunction: ILuaFunction read GetAsFunction; + end; + + + TLuaImplicitVariable = record + Variable: ILuaVariable; + + class operator Implicit(AValue: ILuaVariable): TLuaImplicitVariable; + class operator Implicit(AValue: Boolean): TLuaImplicitVariable; + class operator Implicit(AValue: Integer): TLuaImplicitVariable; + class operator Implicit(AValue: Double): TLuaImplicitVariable; + class operator Implicit(AValue: Pointer): TLuaImplicitVariable; + class operator Implicit(const AValue: string): TLuaImplicitVariable; + class operator Implicit(AValue: ILuaTable): TLuaImplicitVariable; + class operator Implicit(AValue: TLuaImplicitVariable): ILuaVariable; + class operator Implicit(AValue: TLuaImplicitVariable): Boolean; + class operator Implicit(AValue: TLuaImplicitVariable): Integer; + class operator Implicit(AValue: TLuaImplicitVariable): Double; + class operator Implicit(AValue: TLuaImplicitVariable): Pointer; + class operator Implicit(AValue: TLuaImplicitVariable): string; + class operator Implicit(AValue: TLuaImplicitVariable): ILuaTable; + end; + + + TLuaKeyValuePair = record + Key: ILuaVariable; + Value: ILuaVariable; + end; + + + ILuaTableEnumerator = interface + ['{4C3F4E20-F9E7-42E6-9446-78C535AF2E30}'] + function GetCurrent: TLuaKeyValuePair; + function MoveNext: Boolean; + + property Current: TLuaKeyValuePair read GetCurrent; + end; + + + ILuaTable = interface + ['{57FD52A1-7D53-485B-A630-29841C498387}'] + function GetEnumerator: ILuaTableEnumerator; + + function HasValue(AKey: TLuaImplicitVariable): Boolean; + function GetValue(AKey: TLuaImplicitVariable): ILuaVariable; + procedure SetValue(AKey: TLuaImplicitVariable; AValue: TLuaImplicitVariable); + end; + + + TLuaTableEnumerator = class(TInterfacedObject, ILuaTableEnumerator) + private + FEnumerator: TEnumerator>; + public + constructor Create(AEnumerator: TEnumerator>); + destructor Destroy; override; + + { ILuaTableEnumerator } + function GetCurrent: TLuaKeyValuePair; + function MoveNext: Boolean; + + property Current: TLuaKeyValuePair read GetCurrent; + end; + + + TLuaVariableEqualityComparer = class(TInterfacedObject, IEqualityComparer) + public + { IEqualityComparer } + function Equals(const Left, Right: ILuaVariable): Boolean; reintroduce; + function GetHashCode(const Value: ILuaVariable): Integer; reintroduce; + end; + + + TLuaTable = class(TInterfacedObject, ILuaTable) + private + FTable: TDictionary; + public + constructor Create; + destructor Destroy; override; + + { ILuaTable } + function GetEnumerator: ILuaTableEnumerator; + + function HasValue(AKey: TLuaImplicitVariable): Boolean; + function GetValue(AKey: TLuaImplicitVariable): ILuaVariable; + procedure SetValue(AKey: TLuaImplicitVariable; AValue: TLuaImplicitVariable); + end; + + + ILuaParametersEnumerator = interface + function GetCurrent: ILuaVariable; + function MoveNext: Boolean; + property Current: ILuaVariable read GetCurrent; + end; + + + ILuaReadParameters = interface(ILuaVariable) + ['{FB611D9E-B51D-460B-B5AB-B567EF853222}'] + function GetCount: Integer; + function GetItem(Index: Integer): ILuaVariable; + + function GetEnumerator: ILuaParametersEnumerator; + function ToString: string; + + property Count: Integer read GetCount; + property Items[Index: Integer]: ILuaVariable read GetItem; default; + end; + + + ILuaWriteParameters = interface + ['{5CEEB16B-158E-44BE-8CAD-DC2C330A244A}'] + function GetCount: Integer; + + procedure Push(ABoolean: Boolean); overload; + procedure Push(AInteger: Integer); overload; + procedure Push(ANumber: Double); overload; + procedure Push(AUserData: Pointer); overload; + procedure Push(const AString: string); overload; + procedure Push(ATable: ILuaTable); overload; + + property Count: Integer read GetCount; + end; + + + ILuaFunction = interface + ['{1BE5E470-0318-410E-8D5B-94BFE04A3DBE}'] + function Call(): ILuaReadParameters; overload; + function Call(AParameters: array of const): ILuaReadParameters; overload; + function Call(AParameters: ILuaReadParameters): ILuaReadParameters; overload; + end; + + + ILuaContext = interface + ['{1F999593-E3D1-4195-9463-A42025AE9830}'] + function GetParameters: ILuaReadParameters; + function GetResult: ILuaWriteParameters; + + property Parameters: ILuaReadParameters read GetParameters; + property Result: ILuaWriteParameters read GetResult; + end; + + + TLuaCFunction = reference to procedure(Context: ILuaContext); + TLuaCMethod = procedure(Context: ILuaContext) of object; + TLuaPushFunction = reference to procedure(AFunction: TLuaCFunction); + + + TCustomLuaRegistration = class(TObject) + private + FName: string; + protected + property Name: string read FName write FName; + public + constructor Create(const AName: string); + + procedure Apply(AState: lua_State; APushFunction: TLuaPushFunction); virtual; abstract; + end; + + + TLuaFunctionRegistration = class(TCustomLuaRegistration) + private + FCallback: TLuaCFunction; + protected + property Callback: TLuaCFunction read FCallback; + public + constructor Create(const AName: string; ACallback: TLuaCFunction); + + procedure Apply(AState: lua_State; APushFunction: TLuaPushFunction); override; + end; + + + TLuaFunctionTable = TDictionary; + + TLuaTableRegistration = class(TCustomLuaRegistration) + private + FFunctionTable: TLuaFunctionTable; + protected + property FunctionTable: TLuaFunctionTable read FFunctionTable; + public + constructor Create(const AName: string); + destructor Destroy; override; + + procedure RegisterFunction(const AName: string; AFunction: TLuaCFunction); virtual; + procedure Apply(AState: lua_State; APushFunction: TLuaPushFunction); override; + end; + + + TLuaRegistrationList = TObjectList; + TLuaRegisteredFunctionDictionary = TDictionary; + + + TLuaScript = class(TObject) + private + FStream: TStream; + FStreamOwnership: TStreamOwnership; + FBuffer: PAnsiChar; + public + constructor Create(const AData: string); overload; + constructor Create(const AStream: TStream; AOwnership: TStreamOwnership = soReference); overload; + destructor Destroy; override; + + function GetNextChunk(out ASize: NativeUint): PAnsiChar; virtual; + end; + + + TLua = class(TObject) + private + FState: lua_State; + FLoaded: Boolean; + FRegistrations: TLuaRegistrationList; + FRegisteredFunctions: TLuaRegisteredFunctionDictionary; + FRegisteredFunctionCookie: Integer; + FAutoOpenLibraries: TLuaLibraries; + FHasRun: Boolean; + FRttiContext: TRttiContext; + protected + function GetHasState: Boolean; virtual; + function GetState: lua_State; virtual; + + function DoAlloc(APointer: Pointer; AOldSize, ANewSize: NativeUint): Pointer; virtual; + + procedure DoNewState; virtual; + procedure DoClose; virtual; + procedure DoRegistration(ARegistration: TCustomLuaRegistration); virtual; + + procedure SetAutoOpenLibraries(const Value: TLuaLibraries); virtual; + protected + procedure CheckState; virtual; + procedure CheckIsFunction; virtual; + procedure AfterLoad; virtual; + + procedure AddRegistration(ARegistration: TCustomLuaRegistration); virtual; + function GetRegisteredFunctionCookie: Integer; virtual; + function RunRegisteredFunction(ACookie: Integer): Integer; virtual; + + property Loaded: Boolean read FLoaded write FLoaded; + property HasRun: Boolean read FHasRun write FHasRun; + property Registrations: TLuaRegistrationList read FRegistrations; + property RegisteredFunctions: TLuaRegisteredFunctionDictionary read FRegisteredFunctions; + public + constructor Create; + destructor Destroy; override; + + procedure LoadFromString(const AData: string; AAutoRun: Boolean = True; const AChunkName: string = ''); virtual; + procedure LoadFromStream(AStream: TStream; AOwnership: TStreamOwnership = soReference; AAutoRun: Boolean = True; const AChunkName: string = ''); virtual; + procedure LoadFromFile(const AFileName: string; AAutoRun: Boolean = True; const AChunkName: string = ''); virtual; + procedure LoadFromScript(AScript: TLuaScript; AOwnership: TStreamOwnership = soReference; AAutoRun: Boolean = True; const AChunkName: string = ''); virtual; + + function GetGlobalVariable(const AName: string): ILuaVariable; virtual; + procedure SetGlobalVariable(const AName: string; AVariable: TLuaImplicitVariable); virtual; + procedure RegisterFunction(const AName: string; AFunction: TLuaCFunction); virtual; +// procedure UnregisterFunction(); + + { Registers all published methods of the specified object. If ATableName + is provided, the methods will be bundled in a global table and accessible as + ATableName.Method(). If not provided, the methods are accessible directly + as global functions. } + procedure RegisterFunctions(AObject: TObject; const ATableName: string = ''; AIncludePublicVisibility: Boolean = False); virtual; +// procedure UnregisterFunctions(); + + procedure OpenLibraries(ALibraries: TLuaLibraries); virtual; + + { These methods should only be called right after one of the + LoadFrom methods, which must have AutoRun set to False. } + procedure Run; virtual; + procedure GetByteCode(AStream: TStream; APop: Boolean = False); virtual; + procedure Capture(const AName: string); virtual; + + function Call(const AFunctionName: string): ILuaReadParameters; overload; virtual; + function Call(const AFunctionName: string; AParameters: array of const): ILuaReadParameters; overload; virtual; + function Call(const AFunctionName: string; AParameters: ILuaReadParameters): ILuaReadParameters; overload; virtual; + + property HasState: Boolean read GetHasState; + property State: lua_State read GetState; + + property AutoOpenLibraries: TLuaLibraries read FAutoOpenLibraries write SetAutoOpenLibraries default [TLuaLibrary.All]; + end; + + + TLuaHelpers = class(TObject) + private + class var RegistryKeyCounter: Int64; + public + class function GetLuaDataType(AType: Integer): TLuaDataType; + class function GetLuaVariableType(ADataType: TLuaDataType): TLuaVariableType; + + class function CreateParameters(AParameters: array of const): ILuaReadParameters; + + class procedure PushVariable(AState: lua_State; AVariable: ILuaVariable); overload; + class procedure PushVariable(AState: lua_State; AVariable: ILuaVariable; AVariableType: TLuaVariableType); overload; + class procedure PushString(AState: lua_State; const AValue: string); + class procedure PushTable(AState: lua_State; ATable: ILuaTable); + + class function AllocLuaString(const AValue: string): PAnsiChar; + class procedure FreeLuaString(AValue: PAnsiChar); + + class procedure RaiseLastLuaError(AState: lua_State); + class function LuaToString(AState: lua_State; AIndex: Integer): string; + class function CallFunction(AState: lua_State; AParameters: ILuaReadParameters): ILuaReadParameters; + + class function NewRegistryKey: string; + end; + + + +implementation +uses + System.Math, + System.SyncObjs, + System.TypInfo; + + +type + PLuaScript = ^TLuaScript; + + TLuaParametersEnumerator = class(TInterfacedObject, ILuaParametersEnumerator) + private + FParameters: ILuaReadParameters; + FIndex: Integer; + protected + property Parameters: ILuaReadParameters read FParameters; + public + constructor Create(AParameters: ILuaReadParameters); + + function GetCurrent: ILuaVariable; + function MoveNext: Boolean; + end; + + + TCustomLuaParameters = class(TInterfacedObject, ILuaVariable, ILuaReadParameters) + protected + function GetDefaultVariable: ILuaVariable; + public + { ILuaVariable } + function GetVariableType: TLuaVariableType; + function GetDataType: TLuaDataType; + + function GetAsBoolean: Boolean; + function GetAsInteger: Integer; + function GetAsNumber: Double; + function GetAsUserData: Pointer; + function GetAsString: string; + function GetAsTable: ILuaTable; + function GetAsFunction: ILuaFunction; + + procedure SetAsBoolean(ABoolean: Boolean); + procedure SetAsInteger(AInteger: Integer); + procedure SetAsNumber(ANumber: Double); + procedure SetAsUserData(AUserData: Pointer); + procedure SetAsString(AString: string); + procedure SetAsTable(ATable: ILuaTable); + + { ILuaReadParameters } + function GetCount: Integer; virtual; abstract; + function GetItem(Index: Integer): ILuaVariable; virtual; abstract; + + function GetEnumerator: ILuaParametersEnumerator; + function ToString: string; override; + end; + + + TLuaStackParameters = class(TCustomLuaParameters) + private + FState: lua_State; + FCount: Integer; + protected + property State: lua_State read FState; + public + constructor Create(AState: lua_State; ACount: Integer = -1); + + { ILuaReadParameters } + function GetCount: Integer; override; + function GetItem(Index: Integer): ILuaVariable; override; + end; + + + TLuaStackVariable = class(TInterfacedObject, ILuaVariable) + private + FState: lua_State; + FIndex: Integer; + FTable: ILuaTable; + protected + property State: lua_State read FState; + property Index: Integer read FIndex; + public + constructor Create(AState: lua_State; AIndex: Integer); + + { ILuaVariable } + function GetDataType: TLuaDataType; + function GetVariableType: TLuaVariableType; + + function GetAsBoolean: Boolean; + function GetAsInteger: Integer; + function GetAsNumber: Double; + function GetAsUserData: Pointer; + function GetAsString: string; + function GetAsTable: ILuaTable; + function GetAsFunction: ILuaFunction; + + procedure SetAsBoolean(ABoolean: Boolean); + procedure SetAsInteger(AInteger: Integer); + procedure SetAsNumber(ANumber: Double); + procedure SetAsUserData(AUserData: Pointer); + procedure SetAsString(AString: string); + procedure SetAsTable(ATable: ILuaTable); + end; + + + TLuaResultParameters = class(TCustomLuaParameters) + private + FParameters: TList; + public + constructor Create(AState: lua_State; ACount: Integer); + destructor Destroy; override; + + function GetCount: Integer; override; + function GetItem(Index: Integer): ILuaVariable; override; + end; + + + TLuaVariable = class(TInterfacedObject, ILuaVariable) + private + FVariableType: TLuaVariableType; + FDataType: TLuaDataType; + FBooleanValue: Boolean; + FIntegerValue: Integer; + FNumberValue: Double; + FUserDataValue: Pointer; + FStringValue: string; + FTableValue: ILuaTable; + FFunctionValue: ILuaFunction; + protected + property VariableType: TLuaVariableType read FVariableType write FVariableType; + property DataType: TLuaDataType read FDataType write FDataType; + property BooleanValue: Boolean read FBooleanValue write FBooleanValue; + property IntegerValue: Integer read FIntegerValue write FIntegerValue; + property NumberValue: Double read FNumberValue write FNumberValue; + property UserDataValue: Pointer read FUserDataValue write FUserDataValue; + property StringValue: string read FStringValue write FStringValue; + property TableValue: ILuaTable read FTableValue write FTableValue; + property FunctionValue: ILuaFunction read FFunctionValue write FFunctionValue; + public + constructor Create; overload; + constructor Create(ABoolean: Boolean); overload; + constructor Create(AInteger: Integer); overload; + constructor Create(ANumber: Double); overload; + constructor Create(AUserData: Pointer); overload; + constructor Create(const AString: string); overload; + constructor Create(ATable: ILuaTable); overload; + constructor Create(AFunction: ILuaFunction); overload; + + { ILuaParameter } + function GetVariableType: TLuaVariableType; + function GetDataType: TLuaDataType; + + function GetAsBoolean: Boolean; + function GetAsInteger: Integer; + function GetAsNumber: Double; + function GetAsUserData: Pointer; + function GetAsString: string; + function GetAsTable: ILuaTable; + function GetAsFunction: ILuaFunction; + + procedure SetAsBoolean(ABoolean: Boolean); + procedure SetAsInteger(AInteger: Integer); + procedure SetAsNumber(ANumber: Double); + procedure SetAsUserData(AUserData: Pointer); + procedure SetAsString(AString: string); + procedure SetAsTable(ATable: ILuaTable); + end; + + + TLuaCachedVariable = class(TLuaVariable) + public + constructor Create(AState: lua_State; AIndex: Integer); + end; + + + TLuaCachedTable = class(TLuaTable) + public + constructor Create(AState: lua_State; AIndex: Integer); + end; + + + TLuaReadWriteParameters = class(TCustomLuaParameters, ILuaWriteParameters) + private + FParameters: TList; + public + constructor Create; + destructor Destroy; override; + + function GetCount: Integer; override; + function GetItem(Index: Integer): ILuaVariable; override; + + { ILuaWriteParameters } + procedure Push(ABoolean: Boolean); overload; + procedure Push(AInteger: Integer); overload; + procedure Push(ANumber: Double); overload; + procedure Push(AUserData: Pointer); overload; + procedure Push(const AString: string); overload; + procedure Push(ATable: ILuaTable); overload; + end; + + + TLuaStackWriteParameters = class(TInterfacedObject, ILuaWriteParameters) + private + FState: lua_State; + FCount: Integer; + protected + procedure Pushed; + + property State: lua_State read FState; + public + constructor Create(AState: lua_State); + + { ILuaWriteParameters } + function GetCount: Integer; + + procedure Push(ABoolean: Boolean); overload; + procedure Push(AInteger: Integer); overload; + procedure Push(ANumber: Double); overload; + procedure Push(AUserData: Pointer); overload; + procedure Push(const AString: string); overload; + procedure Push(ATable: ILuaTable); overload; + end; + + + TLuaFunction = class(TInterfacedObject, ILuaFunction) + private + FState: lua_State; + FRegistryKey: string; + protected + property State: lua_State read FState; + property RegistryKey: string read FRegistryKey; + public + constructor Create(AState: lua_State; AIndex: Integer); + destructor Destroy; override; + + { ILuaFunction } + function Call(): ILuaReadParameters; overload; + function Call(AParameters: array of const): ILuaReadParameters; overload; + function Call(AParameters: ILuaReadParameters): ILuaReadParameters; overload; + end; + + + TLuaContext = class(TInterfacedObject, ILuaContext) + private + FParameters: ILuaReadParameters; + FResult: ILuaWriteParameters; + public + constructor Create(AState: lua_State); + + { ILuaContext } + function GetParameters: ILuaReadParameters; + function GetResult: ILuaWriteParameters; + end; + + + +{ TLuaHelpers } +class function TLuaHelpers.GetLuaDataType(AType: Integer): TLuaDataType; +begin + case AType of + LUA_TNIL: Result := LuaNil; + LUA_TNUMBER: Result := LuaNumber; + LUA_TBOOLEAN: Result := LuaBoolean; + LUA_TSTRING: Result := LuaString; + LUA_TTABLE: Result := LuaTable; + LUA_TFUNCTION: Result := LuaFunction; + LUA_TUSERDATA: Result := LuaUserData; + LUA_TTHREAD: Result := LuaThread; + LUA_TLIGHTUSERDATA: Result := LuaLightUserData; + else + Result := LuaNone; + end; +end; + + +class function TLuaHelpers.GetLuaVariableType(ADataType: TLuaDataType): TLuaVariableType; +begin + case ADataType of + LuaNumber: Result := VariableNumber; + LuaBoolean: Result := VariableBoolean; + LuaString: Result := VariableString; + LuaTable: Result := VariableTable; + LuaFunction: Result := VariableFunction; + LuaUserData: Result := VariableUserData; + LuaLightUserData: Result := VariableUserData; + else + Result := VariableNone; + end; +end; + + +class function TLuaHelpers.CreateParameters(AParameters: array of const): ILuaReadParameters; +var + parameterIndex: Integer; + parameter: TVarRec; + resultParameters: TLuaReadWriteParameters; + table: ILuaTable; + +begin + resultParameters := TLuaReadWriteParameters.Create; + + for parameterIndex := Low(AParameters) to High(AParameters) do + begin + parameter := AParameters[parameterIndex]; + case parameter.VType of + vtInteger: resultParameters.Push(parameter.VInteger); + vtBoolean: resultParameters.Push(parameter.VBoolean); + vtChar: resultParameters.Push(string(parameter.VChar)); + vtExtended: resultParameters.Push(parameter.VExtended^); + vtString: resultParameters.Push(string(parameter.VString)); + vtPointer: resultParameters.Push(parameter.VPointer); + vtPChar: resultParameters.Push(string(parameter.VPChar)); + vtObject: + if parameter.VObject is TLuaTable then + resultParameters.Push(TLuaTable(parameter.VObject)); + + vtWideChar: resultParameters.Push(string(parameter.VWideChar)); + vtPWideChar: resultParameters.Push(string(parameter.VPWideChar)); + vtAnsiString: resultParameters.Push(string(PAnsiChar(parameter.VAnsiString))); + vtCurrency: resultParameters.Push(parameter.VCurrency^); +// vtVariant: resultParameters.Push(parameter.VVariant); + vtInterface: + if Supports(IInterface(parameter.VInterface), ILuaTable, table) then + resultParameters.Push(table); + + vtWideString: resultParameters.Push(string(PWideString(parameter.VWideString))); + vtInt64: resultParameters.Push(parameter.VInt64^); + vtUnicodeString: resultParameters.Push(string(PUnicodeString(parameter.VUnicodeString))); + else + raise ELuaUnsupportedParameterException.CreateFmt('Parameter type %d not supported (index: %d)', [parameter.VType, parameterIndex]); + end; + end; + + Result := resultParameters; +end; + + +class procedure TLuaHelpers.PushVariable(AState: lua_State; AVariable: ILuaVariable); +begin + PushVariable(AState, AVariable, AVariable.VariableType); +end; + + +class procedure TLuaHelpers.PushVariable(AState: lua_State; AVariable: ILuaVariable; AVariableType: TLuaVariableType); +begin + case AVariableType of + VariableNone: lua_pushnil(AState); + VariableBoolean: lua_pushboolean(AState, IfThen(AVariable.AsBoolean, 1, 0)); + VariableInteger: lua_pushinteger(AState, AVariable.AsInteger); + VariableNumber: lua_pushnumber(AState, AVariable.AsNumber); + VariableUserData: + if AVariable.AsUserData = nil then + lua_pushnil(AState) + else + lua_pushlightuserdata(AState, AVariable.AsUserData); + + VariableString: PushString(AState, AVariable.AsString); + VariableTable: PushTable(AState, AVariable.AsTable); + else + raise ELuaUnsupportedVariableException.CreateFmt('Variable type not supported: %d', [Ord(AVariableType)]); + end; +end; + + +class procedure TLuaHelpers.PushString(AState: lua_State; const AValue: string); +var + stringValue: PAnsiChar; + +begin + stringValue := AllocLuaString(AValue); + try + lua_pushlstring(AState, stringValue, Length(AValue)); + finally + FreeLuaString(stringValue); + end; +end; + + +class procedure TLuaHelpers.PushTable(AState: lua_State; ATable: ILuaTable); +var + pair: TLuaKeyValuePair; + +begin + lua_newtable(AState); + + for pair in ATable do + begin + PushVariable(AState, pair.Key); + PushVariable(AState, pair.Value); + lua_settable(AState, -3); + end; +end; + + +// Casting strings directly to PAnsiChar (via AnsiString) causes corruption +// with table values, at least in Delphi XE2. Can't really explain why, seems +// the input string goes out of scope, so let's just go through the motions +// to create a copy and be safe. +class function TLuaHelpers.AllocLuaString(const AValue: string): PAnsiChar; +begin + if Length(AValue) > 0 then + begin + GetMem(Result, Length(AValue) + 1); + StrPCopy(Result, AnsiString(AValue)); + end else + Result := nil; +end; + + +class procedure TLuaHelpers.FreeLuaString(AValue: PAnsiChar); +begin + FreeMem(AValue); +end; + +// If someone cares to reproduce this issue and optimize the code, use these +// two and the TableLuaFunction test should fail with a corrupted value +// (#11#0#0#0#11#0#0#0#11#0#0#0#11#0#0#0#11). +(* +class function TLuaHelpers.AllocLuaString(const AValue: string): PAnsiChar; +begin + if Length(AValue) > 0 then + Result := PAnsiChar(AnsiString(AValue)) + else + Result := nil; +end; + +class procedure TLuaHelpers.FreeLuaString(AValue: PAnsiChar); +begin +end; +*) + + + + +class procedure TLuaHelpers.RaiseLastLuaError(AState: lua_State); +var + errorMessage: string; + +begin + errorMessage := LuaToString(AState, -1); + lua_pop(AState, 1); + + raise ELuaException.Create(errorMessage); +end; + + +class function TLuaHelpers.LuaToString(AState: lua_State; AIndex: Integer): string; +var + len: NativeUint; + value: PAnsiChar; + stringValue: RawByteString; + +begin + value := lua_tolstring(AState, AIndex, @len); + SetString(stringValue, value, len); + + Result := string(stringValue); +end; + + +class function TLuaHelpers.CallFunction(AState: lua_State; AParameters: ILuaReadParameters): ILuaReadParameters; +var + stackIndex: Integer; + parameterCount: Integer; + parameter: ILuaVariable; + +begin + { Assumption: the function to call is the top item on the stack } + stackIndex := Pred(lua_gettop(AState)); + + parameterCount := 0; + if Assigned(AParameters) then + begin + parameterCount := AParameters.Count; + for parameter in AParameters do + PushVariable(AState, parameter); + end; + + if lua_pcall(AState, parameterCount, LUA_MULTRET, 0) <> 0 then + RaiseLastLuaError(AState); + + Result := TLuaResultParameters.Create(AState, lua_gettop(AState) - stackIndex); +end; + + +class function TLuaHelpers.NewRegistryKey: string; +begin + // This could be incremented on a per-State basis, but this'll do for now. + Result := Format('DelphiLuaWrapper_%d', [TInterlocked.Increment(RegistryKeyCounter)]); +end; + + + +{ TLuaImplicitVariable } +class operator TLuaImplicitVariable.Implicit(AValue: ILuaVariable): TLuaImplicitVariable; +begin + Result.Variable := AValue; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: Boolean): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: Integer): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: Double): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: Pointer): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(const AValue: string): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: ILuaTable): TLuaImplicitVariable; +begin + Result.Variable := TLuaVariable.Create(AValue); +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): ILuaVariable; +begin + Result := AValue.Variable; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): Boolean; +begin + Result := AValue.Variable.AsBoolean; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): Integer; +begin + Result := AValue.Variable.AsInteger; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): Double; +begin + Result := AValue.Variable.AsNumber; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): Pointer; +begin + Result := AValue.Variable.AsUserData; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): string; +begin + Result := AValue.Variable.AsString; +end; + + +class operator TLuaImplicitVariable.Implicit(AValue: TLuaImplicitVariable): ILuaTable; +begin + Result := AValue.Variable.AsTable; +end; + + +{ TLuaTableEnumerator } +constructor TLuaTableEnumerator.Create(AEnumerator: TEnumerator>); +begin + inherited Create; + + FEnumerator := AEnumerator; +end; + + +destructor TLuaTableEnumerator.Destroy; +begin + FreeAndNil(FEnumerator); + + inherited Destroy; +end; + + +function TLuaTableEnumerator.GetCurrent: TLuaKeyValuePair; +var + current: TPair; + +begin + current := FEnumerator.Current; + + Result.Key := current.Key; + Result.Value := current.Value; +end; + + +function TLuaTableEnumerator.MoveNext: Boolean; +begin + Result := FEnumerator.MoveNext; +end; + + +{ TLuaVariableEqualityComparer } +function TLuaVariableEqualityComparer.Equals(const Left, Right: ILuaVariable): Boolean; +begin + Result := (Left.VariableType = Right.VariableType); + if Result then + begin + case Left.VariableType of + VariableBoolean: Result := (Left.AsBoolean = Right.AsBoolean); + VariableInteger: Result := (Left.AsInteger = Right.AsInteger); + VariableNumber: Result := SameValue(Left.AsNumber, Right.AsNumber); + VariableUserData: Result := (Left.AsUserData = Right.AsUserData); + VariableString: Result := (Left.AsString = Right.AsString); + VariableTable: Result := (Left.AsTable = Right.AsTable); + end; + end; +end; + +function TLuaVariableEqualityComparer.GetHashCode(const Value: ILuaVariable): Integer; +var + i: Integer; + m: Extended; + e: Integer; + p: Pointer; + s: string; + +begin + Result := 0; + + // System.Generics.Defaults has a decent set of GetHashCode_ functions... + // ...which are of course private. + case Value.VariableType of + VariableBoolean: + Result := Ord(Value.AsBoolean); + VariableInteger: + begin + i := Value.AsInteger; + Result := BobJenkinsHash(i, SizeOf(i), 0); + end; + VariableNumber: + begin + // Denormalized floats and positive/negative 0.0 complicate things. + Frexp(Value.AsNumber, m, e); + if m = 0 then + m := Abs(m); + Result := BobJenkinsHash(m, SizeOf(m), 0); + Result := BobJenkinsHash(e, SizeOf(e), Result); + end; + VariableUserData: + begin + p := Value.AsUserData; + Result := BobJenkinsHash(p, SizeOf(p), 0); + end; + VariableString: + begin + s := Value.AsString; + if Length(s) > 0 then + Result := BobJenkinsHash(s[1], Length(s) * SizeOf(s[1]), 0); + end; + VariableTable: + begin + p := Pointer(Value.AsTable); + Result := BobJenkinsHash(p, SizeOf(p), 0); + end; + end; +end; + + +{ TLuaTable } +constructor TLuaTable.Create; +begin + inherited Create; + + FTable := TDictionary.Create(TLuaVariableEqualityComparer.Create); +end; + + +destructor TLuaTable.Destroy; +begin + FreeAndNil(FTable); + + inherited Destroy; +end; + + +function TLuaTable.GetEnumerator: ILuaTableEnumerator; +begin + Result := TLuaTableEnumerator.Create(FTable.GetEnumerator); +end; + + +function TLuaTable.HasValue(AKey: TLuaImplicitVariable): Boolean; +begin + Result := FTable.ContainsKey(AKey); +end; + +function TLuaTable.GetValue(AKey: TLuaImplicitVariable): ILuaVariable; +begin + Result := FTable[AKey]; +end; + + +procedure TLuaTable.SetValue(AKey, AValue: TLuaImplicitVariable); +begin + FTable.AddOrSetValue(AKey, AValue); +end; + + +{ TCustomLuaRegistration } +constructor TCustomLuaRegistration.Create(const AName: string); +begin + inherited Create; + + FName := AName; +end; + + +{ TLuaFunctionRegistration } +procedure TLuaFunctionRegistration.Apply(AState: lua_State; APushFunction: TLuaPushFunction); +var + functionName: PAnsiChar; + +begin + functionName := TLuaHelpers.AllocLuaString(Name); + try + APushFunction(Callback); + lua_setglobal(AState, functionName); + finally + TLuaHelpers.FreeLuaString(functionName); + end; +end; + +constructor TLuaFunctionRegistration.Create(const AName: string; ACallback: TLuaCFunction); +begin + inherited Create(AName); + + FCallback := ACallback; +end; + + +{ TLuaTableRegistration } +constructor TLuaTableRegistration.Create(const AName: string); +begin + inherited Create(AName); + + FFunctionTable := TLuaFunctionTable.Create; +end; + + +destructor TLuaTableRegistration.Destroy; +begin + FreeAndNil(FFunctionTable); + + inherited Destroy; +end; + + +procedure TLuaTableRegistration.RegisterFunction(const AName: string; AFunction: TLuaCFunction); +begin + FunctionTable.AddOrSetValue(AName, AFunction); +end; + + +procedure TLuaTableRegistration.Apply(AState: lua_State; APushFunction: TLuaPushFunction); +var + pair: TPair; + functionName: PAnsiChar; + tableName: PAnsiChar; + +begin + lua_newtable(AState); + + for pair in FunctionTable do + begin + functionName := TLuaHelpers.AllocLuaString(pair.Key); + try + lua_pushstring(AState, functionName); + APushFunction(pair.Value); + lua_settable(AState, -3); + finally + TLuaHelpers.FreeLuaString(functionName); + end; + end; + + tableName := TLuaHelpers.AllocLuaString(Name); + try + lua_setglobal(AState, tableName); + finally + TLuaHelpers.FreeLuaString(tableName); + end; +end; + + +{ TLuaScript } +constructor TLuaScript.Create(const AData: string); +begin + Create(TStringStream.Create(AData), soOwned); +end; + + +constructor TLuaScript.Create(const AStream: TStream; AOwnership: TStreamOwnership); +begin + inherited Create; + + FStream := AStream; + FStreamOwnership := AOwnership; +end; + + +destructor TLuaScript.Destroy; +begin + if Assigned(FBuffer) then + FreeMem(FBuffer); + + if FStreamOwnership = soOwned then + FreeAndNil(FStream); + + inherited Destroy; +end; + + +function TLuaScript.GetNextChunk(out ASize: NativeUint): PAnsiChar; +const + BufferSize = 4096; + +begin + if not Assigned(FBuffer) then + GetMem(FBuffer, BufferSize); + + ASize := FStream.Read(FBuffer^, BufferSize); + if ASize > 0 then + Result := FBuffer + else + Result := nil; +end; + + + +{ Callback functions } +function LuaWrapperAlloc(ud, ptr: Pointer; osize, nsize: size_t): Pointer; cdecl; +begin + Result := TLua(ud).DoAlloc(ptr, osize, nsize); +end; + + +function LuaWrapperReader(L: lua_State; ud: Pointer; var sz: Lua.size_t): PAnsiChar; cdecl; +var + script: PLuaScript; + +begin + script := ud; + Result := script^.GetNextChunk(sz); +end; + + +function LuaWrapperWriter(L: lua_State; p: Pointer; sz: size_t; ud: Pointer): Integer; cdecl; +var + stream: TStream; + +begin + stream := TStream(ud^); + try + stream.WriteBuffer(p^, sz); + Result := 0; + except + on E:EStreamError do + Result := 1; + end; +end; + + +function LuaWrapperFunction(L: lua_State): Integer; cdecl; +var + lua: TLua; + cookie: Integer; + +begin + Result := 0; + + lua := TLua(lua_touserdata(L, lua_upvalueindex(1))); + cookie := lua_tointeger(L, lua_upvalueindex(2)); + + if Assigned(lua) then + Result := lua.RunRegisteredFunction(cookie); +end; + + +{ TLua } +constructor TLua.Create; +begin + inherited Create; + + FAutoOpenLibraries := [TLuaLibrary.All]; + FRegistrations := TLuaRegistrationList.Create(True); + FRegisteredFunctions := TLuaRegisteredFunctionDictionary.Create; +end; + + +destructor TLua.Destroy; +begin + FreeAndNil(FRegisteredFunctions); + FreeAndNil(FRegistrations); + + if HasState then + DoClose; + + inherited Destroy; +end; + + +procedure TLua.LoadFromString(const AData: string; AAutoRun: Boolean; const AChunkName: string); +begin + LoadFromScript(TLuaScript.Create(AData), soOwned, AAutoRun, AChunkName); +end; + + +procedure TLua.LoadFromStream(AStream: TStream; AOwnership: TStreamOwnership; AAutoRun: Boolean; const AChunkName: string); +begin + LoadFromScript(TLuaScript.Create(AStream, AOwnership), soOwned, AAutoRun, AChunkName); +end; + + +procedure TLua.LoadFromFile(const AFileName: string; AAutoRun: Boolean; const AChunkName: string); +var + chunkName: string; + +begin + if Length(AChunkName) > 0 then + chunkName := AChunkName + else + chunkName := ExtractFileName(AFileName); + + LoadFromScript(TLuaScript.Create(TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone), soOwned), soOwned, AAutoRun, chunkName); +end; + + +procedure TLua.LoadFromScript(AScript: TLuaScript; AOwnership: TStreamOwnership; AAutoRun: Boolean; const AChunkName: string); +var + chunkName: PAnsiChar; + +begin + try + chunkName := TLuaHelpers.AllocLuaString(AChunkName); + try + if lua_load(State, LuaWrapperReader, @AScript, chunkName, nil) <> 0 then + TLuaHelpers.RaiseLastLuaError(State); + finally + TLuaHelpers.FreeLuaString(chunkName); + end; + + if not Loaded then + AfterLoad; + + if AAutoRun then + Run; + finally + if AOwnership = soOwned then + FreeAndNil(AScript); + end; +end; + + +procedure TLua.OpenLibraries(ALibraries: TLuaLibraries); +begin + if TLuaLibrary.All in ALibraries then + luaL_openlibs(State) + else + begin + if TLuaLibrary.Base in ALibraries then + luaL_requiref(State, 'base', luaopen_base, 1); + + if TLuaLibrary.Coroutine in ALibraries then + luaL_requiref(State, 'coroutine', luaopen_coroutine, 1); + + if TLuaLibrary.Table in ALibraries then + luaL_requiref(State, 'table', luaopen_table, 1); + + if TLuaLibrary.IO in ALibraries then + luaL_requiref(State, 'io', luaopen_io, 1); + + if TLuaLibrary.OS in ALibraries then + luaL_requiref(State, 'os', luaopen_os, 1); + + if TLuaLibrary.StringLib in ALibraries then + luaL_requiref(State, 'string', luaopen_string, 1); + + if TLuaLibrary.Bit32 in ALibraries then + luaL_requiref(State, 'bit32', luaopen_bit32, 1); + + if TLuaLibrary.Math in ALibraries then + luaL_requiref(State, 'math', luaopen_math, 1); + + if TLuaLibrary.Debug in ALibraries then + luaL_requiref(State, 'debug', luaopen_debug, 1); + + if TLuaLibrary.Package in ALibraries then + luaL_requiref(State, 'package', luaopen_package, 1); + end; +end; + + +procedure TLua.Run; +begin + CheckIsFunction; + + if lua_pcall(State, 0, 0, 0) <> 0 then + TLuaHelpers.RaiseLastLuaError(State); + + HasRun := True; +end; + + +procedure TLua.GetByteCode(AStream: TStream; APop: Boolean); +var + returnCode: Integer; + +begin + CheckIsFunction; + + try + returnCode := lua_dump(State, LuaWrapperWriter, @AStream); + if returnCode <> 0 then + raise ELuaException.CreateFmt('lua_dump returned code %d', [returnCode]); + finally + if APop then + lua_pop(State, 1); + end; +end; + + +procedure TLua.Capture(const AName: string); +var + name: PAnsiChar; + +begin + CheckIsFunction; + + // Create a new table to serve as the environment + lua_newtable(State); + + // Set the global AName to the new table + lua_pushvalue(State, -1); + name := TLuaHelpers.AllocLuaString(AName); + try + lua_setglobal(State, name); + finally + TLuaHelpers.FreeLuaString(name); + end; + + // Set the global environment as the table's metatable index, so calls to + // global functions and variables still work + lua_newtable(State); + TLuaHelpers.PushString(State, '__index'); + lua_pushglobaltable(State); + lua_settable(State, -3); + + lua_setmetatable(State, -2); + + // Set the new table as the environment (upvalue at index 1) + lua_setupvalue(State, -2, 1); + + if lua_pcall(State, 0, 0, 0) <> 0 then + TLuaHelpers.RaiseLastLuaError(State); +end; + + +function TLua.Call(const AFunctionName: string): ILuaReadParameters; +begin + Result := Call(AFunctionName, nil); +end; + + +function TLua.Call(const AFunctionName: string; AParameters: array of const): ILuaReadParameters; +begin + Result := Call(AFunctionName, TLuaHelpers.CreateParameters(AParameters)); +end; + + +function TLua.Call(const AFunctionName: string; AParameters: ILuaReadParameters): ILuaReadParameters; +var + functionName: PAnsiChar; + +begin + { Global functions are only present after the has run once: + http://lua-users.org/lists/lua-l/2011-01/msg01154.html } + if not HasRun then + Run; + + functionName := TLuaHelpers.AllocLuaString(AFunctionName); + try + lua_getglobal(State, functionName); + finally + TLuaHelpers.FreeLuaString(functionName); + end; + + Result := TLuaHelpers.CallFunction(State, AParameters); +end; + + +procedure TLua.CheckState; +begin + if not LuaLibLoaded then + LoadLuaLib; + + if not HasState then + DoNewState; +end; + + +procedure TLua.CheckIsFunction; +begin + if not lua_isfunction(State, -1) then + raise ELuaNoFunctionException.Create('No function on top of the stack, use the LoadFrom methods first'); +end; + + +procedure TLua.AfterLoad; +var + registration: TCustomLuaRegistration; + +begin + Loaded := True; + HasRun := False; + + { Register functions in the current environment } + for registration in Registrations do + DoRegistration(registration); +end; + + +function TLua.GetGlobalVariable(const AName: string): ILuaVariable; +var + name: PAnsiChar; + +begin + name := TLuaHelpers.AllocLuaString(AName); + try + lua_getglobal(State, name); + + Result := TLuaCachedVariable.Create(State, -1); + lua_pop(State, 1); + finally + TLuaHelpers.FreeLuaString(name); + end; +end; + + +procedure TLua.SetGlobalVariable(const AName: string; AVariable: TLuaImplicitVariable); +var + name: PAnsiChar; + +begin + name := TLuaHelpers.AllocLuaString(AName); + try + TLuaHelpers.PushVariable(State, AVariable); + lua_setglobal(State, name); + finally + TLuaHelpers.FreeLuaString(name); + end; +end; + + +procedure TLua.RegisterFunction(const AName: string; AFunction: TLuaCFunction); +begin + { Since anonymous methods are basically interfaces, we need to keep a reference around } + AddRegistration(TLuaFunctionRegistration.Create(AName, AFunction)); +end; + + +procedure TLua.RegisterFunctions(AObject: TObject; const ATableName: string; AIncludePublicVisibility: Boolean); + + { This wrapper is needed because Delphi's anonymous functions capture + variables, not values. We need a stable 'callback' here. } + function CaptureCallback(AMethod: TMethod): TLuaCFunction; inline; + begin + Result := TLuaCMethod(AMethod); + end; + +var + rttiType: TRttiType; + rttiMethod: TRttiMethod; + rttiParameters: TArray; + callback: TMethod; + tableRegistration: TLuaTableRegistration; + +begin + tableRegistration := nil; + if Length(ATableName) > 0 then + tableRegistration := TLuaTableRegistration.Create(ATableName); + + rttiType := FRttiContext.GetType(AObject.ClassType); + for rttiMethod in rttiType.GetMethods do + begin + if (rttiMethod.Visibility = mvPublished) or + (AIncludePublicVisibility and (rttiMethod.Visibility = mvPublic)) then + begin + rttiParameters := rttiMethod.GetParameters; + + { Check if one parameter of type ILuaContext is present } + if (Length(rttiParameters) = 1) and + (Assigned(rttiParameters[0].ParamType)) and + (rttiParameters[0].ParamType.TypeKind = tkInterface) and + (TRttiInterfaceType(rttiParameters[0].ParamType).GUID = ILuaContext) then + begin + callback.Code := rttiMethod.CodeAddress; + callback.Data := AObject; + + if Assigned(tableRegistration) then + tableRegistration.RegisterFunction(rttiMethod.Name, CaptureCallback(callback)) + else + AddRegistration(TLuaFunctionRegistration.Create(rttiMethod.Name, CaptureCallback(callback))); + end; + end; + end; + + if Assigned(tableRegistration) then + AddRegistration(tableRegistration); +end; + + +function TLua.DoAlloc(APointer: Pointer; AOldSize, ANewSize: NativeUint): Pointer; +begin + Result := DefaultLuaAlloc(nil, APointer, AOldSize, ANewSize); +end; + + +procedure TLua.DoNewState; +begin + FState := lua_newstate(@LuaWrapperAlloc, Pointer(Self)); + if not HasState then + raise ELuaInitException.Create('Failed to initialize new state'); + + OpenLibraries(AutoOpenLibraries); +end; + + +procedure TLua.DoClose; +begin + lua_close(FState); + FState := nil; +end; + + +procedure TLua.DoRegistration(ARegistration: TCustomLuaRegistration); +var + this: TLua; + +begin + this := Self; + + ARegistration.Apply(State, + procedure(AFunction: TLuaCFunction) + var + cookie: Integer; + + begin + cookie := GetRegisteredFunctionCookie; + RegisteredFunctions.Add(cookie, AFunction); + + lua_pushlightuserdata(State, this); + lua_pushinteger(State, Cookie); + lua_pushcclosure(State, @LuaWrapperFunction, 2); + end); +end; + +procedure TLua.AddRegistration(ARegistration: TCustomLuaRegistration); +begin + Registrations.Add(ARegistration); + + { Only register functions after Load, otherwise they'll not be available in the environment } + if Loaded then + DoRegistration(ARegistration); +end; + + +function TLua.GetRegisteredFunctionCookie: Integer; +begin + Inc(FRegisteredFunctionCookie); + Result := FRegisteredFunctionCookie; +end; + + +function TLua.RunRegisteredFunction(ACookie: Integer): Integer; +var + context: ILuaContext; + +begin + Result := 0; + + if RegisteredFunctions.ContainsKey(ACookie) then + begin + context := TLuaContext.Create(State); + + try + RegisteredFunctions[ACookie](context); + Result := context.Result.Count; + except + on E:Exception do + Result := luaL_error(State, PAnsiChar(AnsiString(E.Message)), nil); + end; + end; +end; + + +function TLua.GetHasState: Boolean; +begin + Result := Assigned(FState); +end; + + +function TLua.GetState: lua_State; +begin + CheckState; + Result := FState; +end; + + +procedure TLua.SetAutoOpenLibraries(const Value: TLuaLibraries); +begin + FAutoOpenLibraries := Value; +end; + + +{ TLuaParametersEnumerator } +constructor TLuaParametersEnumerator.Create(AParameters: ILuaReadParameters); +begin + inherited Create; + FParameters := AParameters; + FIndex := -1; +end; + +function TLuaParametersEnumerator.GetCurrent: ILuaVariable; +begin + Result := Parameters.Items[FIndex]; +end; + + +function TLuaParametersEnumerator.MoveNext: Boolean; +begin + Inc(FIndex); + Result := FIndex < Parameters.Count; +end; + + +{ TCustomLuaParameters } +function TCustomLuaParameters.GetEnumerator: ILuaParametersEnumerator; +begin + Result := TLuaParametersEnumerator.Create(Self); +end; + + +function TCustomLuaParameters.GetVariableType: TLuaVariableType; +begin + Result := GetDefaultVariable.GetVariableType; +end; + + +function TCustomLuaParameters.GetDataType: TLuaDataType; +begin + Result := GetDefaultVariable.GetDataType; +end; + + +function TCustomLuaParameters.GetAsBoolean: Boolean; +begin + Result := GetDefaultVariable.GetAsBoolean; +end; + + +function TCustomLuaParameters.GetAsInteger: Integer; +begin + Result := GetDefaultVariable.GetAsInteger; +end; + + +function TCustomLuaParameters.GetAsNumber: Double; +begin + Result := GetDefaultVariable.GetAsNumber; +end; + + +function TCustomLuaParameters.GetAsUserData: Pointer; +begin + Result := GetDefaultVariable.GetAsUserData; +end; + + +function TCustomLuaParameters.GetAsString: string; +begin + Result := GetDefaultVariable.GetAsString; +end; + + +function TCustomLuaParameters.GetAsTable: ILuaTable; +begin + Result := GetDefaultVariable.GetAsTable; +end; + + +function TCustomLuaParameters.GetAsFunction: ILuaFunction; +begin + Result := GetDefaultVariable.GetAsFunction; +end; + + +procedure TCustomLuaParameters.SetAsBoolean(ABoolean: Boolean); +begin + GetDefaultVariable.SetAsBoolean(ABoolean); +end; + + +procedure TCustomLuaParameters.SetAsInteger(AInteger: Integer); +begin + GetDefaultVariable.SetAsInteger(AInteger); +end; + + +procedure TCustomLuaParameters.SetAsNumber(ANumber: Double); +begin + GetDefaultVariable.SetAsNumber(ANumber); +end; + + +procedure TCustomLuaParameters.SetAsUserData(AUserData: Pointer); +begin + GetDefaultVariable.SetAsUserData(AUserData); +end; + + +procedure TCustomLuaParameters.SetAsString(AString: string); +begin + GetDefaultVariable.SetAsString(AString); +end; + + +procedure TCustomLuaParameters.SetAsTable(ATable: ILuaTable); +begin + GetDefaultVariable.SetAsTable(ATable); +end; + + +function TCustomLuaParameters.GetDefaultVariable: ILuaVariable; +begin + Result := GetItem(0); +end; + + +function TCustomLuaParameters.ToString: string; +var + parameterIndex: Integer; + +begin + Result := ''; + + for parameterIndex := 0 to Pred(GetCount) do + Result := Result + GetItem(parameterIndex).AsString; +end; + + +{ TLuaCFunctionParameters } +constructor TLuaStackParameters.Create(AState: lua_State; ACount: Integer); +begin + inherited Create; + + FState := AState; + FCount := ACount; + + if FCount = -1 then + FCount := lua_gettop(State); +end; + + +function TLuaStackParameters.GetCount: Integer; +begin + Result := FCount; +end; + + +function TLuaStackParameters.GetItem(Index: Integer): ILuaVariable; +begin + if (Index < 0) or (Index >= GetCount) then + raise ERangeError.CreateFmt('Invalid parameter index: %d', [Index]); + + Result := TLuaStackVariable.Create(State, Succ(Index)); +end; + + +{ TLuaCFunctionParameter } +constructor TLuaStackVariable.Create(AState: lua_State; AIndex: Integer); +begin + inherited Create; + + FState := AState; + FIndex := AIndex; +end; + + +function TLuaStackVariable.GetVariableType: TLuaVariableType; +begin + Result := TLuaHelpers.GetLuaVariableType(GetDataType); +end; + + +function TLuaStackVariable.GetDataType: TLuaDataType; +begin + Result := TLuaHelpers.GetLuaDataType(lua_type(State, Index)); +end; + + +function TLuaStackVariable.GetAsBoolean: Boolean; +begin + Result := (lua_toboolean(State, Index) <> 0); +end; + + +function TLuaStackVariable.GetAsInteger: Integer; +begin + Result := lua_tointeger(State, Index); +end; + + +function TLuaStackVariable.GetAsNumber: Double; +begin + Result := lua_tonumber(State, Index); +end; + + +function TLuaStackVariable.GetAsUserData: Pointer; +begin + Result := lua_touserdata(State, Index); +end; + + +function TLuaStackVariable.GetAsString: string; +begin + Result := TLuaHelpers.LuaToString(State, Index); +end; + + +function TLuaStackVariable.GetAsTable: ILuaTable; +begin + if not lua_istable(State, Index) then + Result := nil; + + if not Assigned(FTable) then + FTable := TLuaCachedTable.Create(State, Index); + + Result := FTable; +end; + + +function TLuaStackVariable.GetAsFunction: ILuaFunction; +begin + if not lua_isfunction(State, Index) then + Result := nil; + + Result := TLuaFunction.Create(State, Index); +end; + + +procedure TLuaStackVariable.SetAsBoolean(ABoolean: Boolean); +begin + lua_pushboolean(State, IfThen(ABoolean, 1, 0)); + lua_replace(State, Index); +end; + + +procedure TLuaStackVariable.SetAsInteger(AInteger: Integer); +begin + TLuaHelpers.PushVariable(State, Self, VariableInteger); + lua_replace(State, Index); +end; + + +procedure TLuaStackVariable.SetAsNumber(ANumber: Double); +begin + TLuaHelpers.PushVariable(State, Self, VariableNumber); + lua_replace(State, Index); +end; + + +procedure TLuaStackVariable.SetAsString(AString: string); +begin + TLuaHelpers.PushVariable(State, Self, VariableString); + lua_replace(State, Index); +end; + + +procedure TLuaStackVariable.SetAsTable(ATable: ILuaTable); +begin + TLuaHelpers.PushVariable(State, Self, VariableTable); + lua_replace(State, Index); +end; + + +procedure TLuaStackVariable.SetAsUserData(AUserData: Pointer); +begin + TLuaHelpers.PushVariable(State, Self, VariableUserData); + lua_replace(State, Index); +end; + + +{ TLuaResultParameters } +constructor TLuaResultParameters.Create(AState: lua_State; ACount: Integer); +var + parameterIndex: Integer; + +begin + inherited Create; + + FParameters := TList.Create; + + if ACount > 0 then + begin + FParameters.Capacity := ACount; + + for parameterIndex := ACount downto 1 do + FParameters.Add(TLuaCachedVariable.Create(AState, -ACount)); + + lua_pop(AState, ACount); + end; +end; + + +destructor TLuaResultParameters.Destroy; +begin + FreeAndNil(FParameters); + + inherited Destroy; +end; + + +function TLuaResultParameters.GetCount: Integer; +begin + Result := FParameters.Count; +end; + + +function TLuaResultParameters.GetItem(Index: Integer): ILuaVariable; +begin + Result := FParameters[Index]; +end; + + +{ TLuaVariable } +constructor TLuaVariable.Create; +begin +end; + + +constructor TLuaVariable.Create(ABoolean: Boolean); +begin + Create; + SetAsBoolean(ABoolean); +end; + + +constructor TLuaVariable.Create(AInteger: Integer); +begin + Create; + SetAsInteger(AInteger); +end; + + +constructor TLuaVariable.Create(ANumber: Double); +begin + Create; + SetAsNumber(ANumber); +end; + + +constructor TLuaVariable.Create(AUserData: Pointer); +begin + Create; + SetAsUserData(AUserData); +end; + + +constructor TLuaVariable.Create(const AString: string); +begin + Create; + SetAsString(AString); +end; + + +constructor TLuaVariable.Create(AFunction: ILuaFunction); +begin + Create; + VariableType := VariableFunction; + DataType := LuaFunction; + FunctionValue := AFunction; +end; + + +constructor TLuaVariable.Create(ATable: ILuaTable); +begin + Create; + SetAsTable(ATable); +end; + + +function TLuaVariable.GetVariableType: TLuaVariableType; +begin + Result := VariableType; +end; + + +function TLuaVariable.GetDataType: TLuaDataType; +begin + Result := DataType; +end; + + +function TLuaVariable.GetAsBoolean: Boolean; +begin + Result := BooleanValue; +end; + + +function TLuaVariable.GetAsInteger: Integer; +begin + Result := IntegerValue; +end; + + +function TLuaVariable.GetAsNumber: Double; +begin + Result := NumberValue; +end; + + +function TLuaVariable.GetAsUserData: Pointer; +begin + Result := UserDataValue; +end; + + +function TLuaVariable.GetAsString: string; +begin + Result := StringValue; +end; + + +function TLuaVariable.GetAsTable: ILuaTable; +begin + Result := TableValue; +end; + + +function TLuaVariable.GetAsFunction: ILuaFunction; +begin + Result := FunctionValue; +end; + + +procedure TLuaVariable.SetAsBoolean(ABoolean: Boolean); +begin + VariableType := VariableBoolean; + DataType := LuaBoolean; + BooleanValue := ABoolean; +end; + + +procedure TLuaVariable.SetAsInteger(AInteger: Integer); +begin + VariableType := VariableInteger; + DataType := LuaString; + IntegerValue := AInteger; +end; + + +procedure TLuaVariable.SetAsNumber(ANumber: Double); +begin + VariableType := VariableNumber; + DataType := LuaNumber; + NumberValue := ANumber; +end; + + +procedure TLuaVariable.SetAsUserData(AUserData: Pointer); +begin + VariableType := VariableUserData; + DataType := LuaLightUserData; + UserDataValue := AUserData; +end; + + +procedure TLuaVariable.SetAsString(AString: string); +begin + VariableType := VariableString; + DataType := LuaString; + StringValue := AString; +end; + + +procedure TLuaVariable.SetAsTable(ATable: ILuaTable); +begin + VariableType := VariableTable; + DataType := LuaTable; + TableValue := ATable; +end; + + +{ TLuaCachedVariable } +constructor TLuaCachedVariable.Create(AState: lua_State; AIndex: Integer); +begin + inherited Create; + + DataType := TLuaHelpers.GetLuaDataType(lua_type(AState, AIndex)); + VariableType := TLuaHelpers.GetLuaVariableType(FDataType); + + BooleanValue := (lua_toboolean(AState, AIndex) <> 0); + IntegerValue := lua_tointeger(AState, AIndex); + NumberValue := lua_tonumber(AState, AIndex); + UserDataValue := lua_touserdata(AState, AIndex); + + { While traversing a table, do not call lua_tolstring directly on a key, + unless you know that the key is actually a string. Recall that lua_tolstring + may change the value at the given index; this confuses the next call to + lua_next. + + http://www.lua.org/manual/5.2/manual.html#lua_next } + case lua_type(AState, AIndex) of + LUA_TSTRING: StringValue := TLuaHelpers.LuaToString(AState, AIndex); + LUA_TNUMBER: StringValue := FloatToStr(NumberValue); + end; + + if lua_istable(AState, AIndex) then + TableValue := TLuaCachedTable.Create(AState, AIndex); + + if lua_isfunction(AState, AIndex) then + FunctionValue := TLuaFunction.Create(AState, AIndex); +end; + + +{ TLuaCachedTable } +constructor TLuaCachedTable.Create(AState: lua_State; AIndex: Integer); +var + tableIndex: Integer; + key: ILuaVariable; + value: ILuaVariable; + +begin + inherited Create; + + lua_pushnil(AState); + + tableIndex := AIndex; + if AIndex < 0 then + Dec(tableIndex); + + while (lua_next(AState, tableIndex) <> 0) do + begin + key := TLuaCachedVariable.Create(AState, -2); + value := TLuaCachedVariable.Create(AState, -1); + + SetValue(key, value); + + { Remove value, keep key for the next iteration } + lua_pop(AState, 1); + end; +end; + + +{ TLuaReadWriteParameters } +constructor TLuaReadWriteParameters.Create; +begin + inherited Create; + + FParameters := TList.Create; +end; + + +destructor TLuaReadWriteParameters.Destroy; +begin + FreeAndNil(FParameters); + + inherited Destroy; +end; + + +function TLuaReadWriteParameters.GetCount: Integer; +begin + Result := FParameters.Count; +end; + + +function TLuaReadWriteParameters.GetItem(Index: Integer): ILuaVariable; +begin + Result := FParameters[Index]; +end; + + +procedure TLuaReadWriteParameters.Push(ABoolean: Boolean); +begin + FParameters.Add(TLuaVariable.Create(ABoolean)); +end; + + +procedure TLuaReadWriteParameters.Push(AInteger: Integer); +begin + FParameters.Add(TLuaVariable.Create(AInteger)); +end; + + +procedure TLuaReadWriteParameters.Push(ANumber: Double); +begin + FParameters.Add(TLuaVariable.Create(ANumber)); +end; + + +procedure TLuaReadWriteParameters.Push(AUserData: Pointer); +begin + FParameters.Add(TLuaVariable.Create(AUserData)); +end; + + +procedure TLuaReadWriteParameters.Push(const AString: string); +begin + FParameters.Add(TLuaVariable.Create(AString)); +end; + + +procedure TLuaReadWriteParameters.Push(ATable: ILuaTable); +begin + FParameters.Add(TLuaVariable.Create(ATable)); +end; + + +{ TLuaContext } +constructor TLuaContext.Create(AState: lua_State); +begin + inherited Create; + + FParameters := TLuaStackParameters.Create(AState); + FResult := TLuaStackWriteParameters.Create(AState); +end; + + +function TLuaContext.GetParameters: ILuaReadParameters; +begin + Result := FParameters; +end; + + +function TLuaContext.GetResult: ILuaWriteParameters; +begin + Result := FResult; +end; + + +{ TLuaResult } +constructor TLuaStackWriteParameters.Create(AState: lua_State); +begin + inherited Create; + + FState := AState; +end; + + +function TLuaStackWriteParameters.GetCount: Integer; +begin + Result := FCount; +end; + + +procedure TLuaStackWriteParameters.Push(ABoolean: Boolean); +begin + lua_pushboolean(State, IfThen(ABoolean, 1, 0)); + Pushed; +end; + + +procedure TLuaStackWriteParameters.Push(AInteger: Integer); +begin + lua_pushinteger(State, AInteger); + Pushed; +end; + + +procedure TLuaStackWriteParameters.Push(ANumber: Double); +begin + lua_pushnumber(State, ANumber); + Pushed; +end; + + +procedure TLuaStackWriteParameters.Push(AUserData: Pointer); +begin + if AUserData = nil then + lua_pushnil(State) + else + lua_pushlightuserdata(State, AUserData); + + Pushed; +end; + + +procedure TLuaStackWriteParameters.Push(const AString: string); +var + value: PAnsiChar; + +begin + value := TLuaHelpers.AllocLuaString(AString); + try + lua_pushlstring(State, value, Length(AString)); + Pushed; + finally + TLuaHelpers.FreeLuaString(value); + end; +end; + + +procedure TLuaStackWriteParameters.Push(ATable: ILuaTable); +begin + TLuaHelpers.PushTable(State, ATable); + Pushed; +end; + + +procedure TLuaStackWriteParameters.Pushed; +begin + Inc(FCount); +end; + + +{ TLuaCFunction } +constructor TLuaFunction.Create(AState: lua_State; AIndex: Integer); +var + actualIndex: Integer; + +begin + inherited Create; + + FState := AState; + + // There is no way to retrieve the function in a way that allows pushing + // it back later. Instead, make use of Lua's registry: + // http://stackoverflow.com/questions/1416797/reference-to-lua-function-in-c-c + FRegistryKey := TLuaHelpers.NewRegistryKey; + actualIndex := AIndex; + + // If the index is relative to the top, compensate for pushing the key + if actualIndex < 0 then + Dec(actualIndex); + + TLuaHelpers.PushString(AState, RegistryKey); + lua_pushvalue(AState, actualIndex); + lua_rawset(AState, LUA_REGISTRYINDEX); +end; + + +destructor TLuaFunction.Destroy; +begin + TLuaHelpers.PushString(State, RegistryKey); + lua_pushnil(State); + lua_rawset(State, LUA_REGISTRYINDEX); + + inherited Destroy; +end; + + +function TLuaFunction.Call: ILuaReadParameters; +begin + Result := Call(nil); +end; + + +function TLuaFunction.Call(AParameters: array of const): ILuaReadParameters; +begin + Result := Call(TLuaHelpers.CreateParameters(AParameters)); +end; + + +function TLuaFunction.Call(AParameters: ILuaReadParameters): ILuaReadParameters; +begin + TLuaHelpers.PushString(State, RegistryKey); + lua_rawget(State, LUA_REGISTRYINDEX); + + Result := TLuaHelpers.CallFunction(State, AParameters); +end; + +end. diff --git a/G940LEDControl/DelphiLua/readme.txt b/G940LEDControl/DelphiLua/readme.txt new file mode 100644 index 0000000..cf1b522 --- /dev/null +++ b/G940LEDControl/DelphiLua/readme.txt @@ -0,0 +1 @@ +Source: https://git.x2software.net/delphi/delphilua \ No newline at end of file diff --git a/G940LEDControl/Forms/MainFrm.dfm b/G940LEDControl/Forms/MainFrm.dfm index 2d46e27..c99ab00 100644 --- a/G940LEDControl/Forms/MainFrm.dfm +++ b/G940LEDControl/Forms/MainFrm.dfm @@ -375,6 +375,10 @@ object MainForm: TMainForm object tsConfiguration: TTabSheet Caption = ' Configuration ' ImageIndex = 2 + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 DesignSize = ( 442 452) @@ -529,6 +533,10 @@ object MainForm: TMainForm object tsAbout: TTabSheet Caption = 'About' ImageIndex = 1 + ExplicitLeft = 0 + ExplicitTop = 0 + ExplicitWidth = 0 + ExplicitHeight = 0 object lblVersionCaption: TLabel Left = 16 Top = 67 diff --git a/G940LEDControl/Forms/MainFrm.pas b/G940LEDControl/Forms/MainFrm.pas index 1fd6bb5..5f6f0eb 100644 --- a/G940LEDControl/Forms/MainFrm.pas +++ b/G940LEDControl/Forms/MainFrm.pas @@ -225,6 +225,7 @@ type implementation uses System.SysUtils, + System.Types, System.Win.ComObj, Vcl.Dialogs, Vcl.Graphics, @@ -241,6 +242,7 @@ uses ButtonFunctionFrm, ConfigConversion, FSXAutoLaunch, + FSXLEDFunctionProvider, FSXLEDFunctionProviderIntf, FSXResources, FSXSimConnectStateMonitor, @@ -248,6 +250,7 @@ uses LEDColorIntf, LEDFunctionIntf, LEDFunctionRegistry, + StaticLEDFunction, StaticResources; @@ -258,6 +261,9 @@ const DefaultProfileName = 'Default'; ProfilePostfixModified = ' (modified)'; + ScriptsPath = 'Scripts\'; + FSXScriptsPath = ScriptsPath + 'FSX\'; + UserDataPath = 'G940LEDControl\'; FilenameProfiles = UserDataPath + 'Profiles.xml'; FilenameSettings = UserDataPath + 'Settings.xml'; @@ -302,6 +308,7 @@ procedure TMainForm.FormCreate(Sender: TObject); var worker: IOmniWorker; + scriptPaths: TStringDynArray; begin FLog := TX2GlobalLog.Category('UI'); @@ -316,6 +323,16 @@ begin SetFSXState(TextFSXDisconnected, False); + + TLEDFunctionRegistry.Register(TStaticLEDFunctionProvider.Create); + + SetLength(scriptPaths, 2); + scriptPaths[0] := App.Path + FSXScriptsPath; + scriptPaths[1] := App.UserPath + UserDataPath + FSXScriptsPath; + + TLEDFunctionRegistry.Register(TFSXLEDFunctionProvider.Create(scriptPaths)); + + FEventMonitor := TOmniEventMonitor.Create(Self); Log.Info('Starting G940 LED state consumer thread'); diff --git a/G940LEDControl/G940LEDControl.dpr b/G940LEDControl/G940LEDControl.dpr index 0d25542..6321365 100644 --- a/G940LEDControl/G940LEDControl.dpr +++ b/G940LEDControl/G940LEDControl.dpr @@ -38,7 +38,10 @@ uses FSXLEDFunctionProviderIntf in 'Units\FSXLEDFunctionProviderIntf.pas', SimBaseDocumentXMLBinding in 'Units\SimBaseDocumentXMLBinding.pas', FSXAutoLaunch in 'Units\FSXAutoLaunch.pas', - ControlIntf in 'Units\ControlIntf.pas'; + ControlIntf in 'Units\ControlIntf.pas', + Lua.API in 'DelphiLua\Lua.API.pas', + Lua in 'DelphiLua\Lua.pas', + LuaLEDFunctionProvider in 'Units\LuaLEDFunctionProvider.pas'; {$R *.res} @@ -65,7 +68,6 @@ begin Application.ShowMainForm := not isRestarting; Application.Title := 'G940 LED Control'; Application.CreateForm(TMainForm, MainForm); - if isRestarting then MainForm.Visible := True; diff --git a/G940LEDControl/G940LEDControl.dproj b/G940LEDControl/G940LEDControl.dproj index 3fde1cf..a9bb03c 100644 --- a/G940LEDControl/G940LEDControl.dproj +++ b/G940LEDControl/G940LEDControl.dproj @@ -8,7 +8,7 @@ VCL 13.4 True - Release + Debug Win32 1 Application @@ -142,6 +142,9 @@ + + + Cfg_2 Base diff --git a/G940LEDControl/G940LEDControl.res b/G940LEDControl/G940LEDControl.res index 794a31cb403d97e600bd0b962ae83ad38dd037a7..4ed79be010dbe416f03dd8eaf98310de00f0dc36 100644 GIT binary patch delta 13 Vcmex!k8#UQ#t90WUocI{0RS^*2DbnJ delta 730 zcmaKq&r1S96vw|S5=xh%Q@1^IEYT35Q;=F1rja4tLds0AT~VVt?Z42WKf;bh*Zz<$ zoxJxCME03+H#MkP-pre~Z$9tG%*&he`R?4i!7wJdlE&jD#6gqNO2)0iG>m1L>#FIL zxu_$wlB%k(FEQ_;oADa|_>vs^NekgBZQH++0Z!p2 zkdM=dQw}L*kuAFx(WRUgvFGu*QdbT9??|V?u4yBj=ozvPWuP1Ez0kg|WzI!$f&%8g z&TIl*T-zM+EMSjtCa9}2br)DoFwZ9IlcX7GC1zlCSUqS{Mn)+a$suX{>srOXj9-hC zI?4wiyJT+aZ|M@a^hl^p+%tTxHhy+Fw7;sVXa#UJd{m{`P5KfQH7O~tV78(Z zdEFvr?ASIP0`zsRYdSQTYi|VluRH79x}$l; + TFSXLEDFunctionWorker = class; - TFSXLEDFunctionProvider = class(TCustomLEDFunctionProvider, IFSXLEDFunctionProvider, IFSXSimConnectObserver) + TFSXLEDFunctionProvider = class(TCustomLuaLEDFunctionProvider, IFSXLEDFunctionProvider, IFSXSimConnectObserver) private FSimConnect: TInterfacedObject; FSimConnectLock: TCriticalSection; FProfileMenuSimConnect: IFSXSimConnectProfileMenu; + FScriptSimConnect: TObject; protected - procedure RegisterFunctions; override; - function GetUID: string; override; - protected + function CreateLuaLEDFunction(AInfo: ILuaTable; AOnSetup: ILuaFunction): TCustomLuaLEDFunction; override; + + procedure InitInterpreter; override; + procedure SetupWorker(AWorker: TFSXLEDFunctionWorker; AOnSetup: ILuaFunction); + { IFSXSimConnectObserver } procedure ObserveDestroy(Sender: IFSXSimConnect); { IFSXLEDFunctionProvider } procedure SetProfileMenu(AEnabled: Boolean; ACascaded: Boolean); public - constructor Create; + constructor Create(const AScriptFolders: TStringDynArray); destructor Destroy; override; function GetSimConnect: IFSXSimConnect; end; - TCustomFSXFunction = class(TCustomMultiStateLEDFunction) + TFSXLEDFunction = class(TCustomLuaLEDFunction) private FProvider: TFSXLEDFunctionProvider; - FDisplayName: string; - FUID: string; protected + function GetDefaultCategoryName: string; override; + + function GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; override; procedure InitializeWorker(AWorker: TCustomLEDMultiStateFunctionWorker); override; property Provider: TFSXLEDFunctionProvider read FProvider; - protected - function GetCategoryName: string; override; - function GetDisplayName: string; override; - function GetUID: string; override; public - constructor Create(AProvider: TFSXLEDFunctionProvider; const ADisplayName, AUID: string); + constructor Create(AProvider: TFSXLEDFunctionProvider; AInfo: ILuaTable; AOnSetup: ILuaFunction); end; - TCustomFSXFunctionClass = class of TCustomFSXFunction; - - - TCustomFSXFunctionWorker = class(TCustomLEDMultiStateFunctionWorker) + TFSXLEDFunctionWorker = class(TCustomLuaLEDFunctionWorker) private FDataHandler: IFSXSimConnectDataHandler; - FDefinitionID: Cardinal; - FSimConnect: IFSXSimConnect; + FDefinitionID: TList; protected - procedure RegisterVariables(ADefinition: IFSXSimConnectDefinition); virtual; abstract; - - procedure SetSimConnect(const Value: IFSXSimConnect); virtual; - property DataHandler: IFSXSimConnectDataHandler read FDataHandler; - property DefinitionID: Cardinal read FDefinitionID; - property SimConnect: IFSXSimConnect read FSimConnect write SetSimConnect; + property DefinitionID: TList read FDefinitionID; protected + procedure AddDefinition(ADefinition: IFSXSimConnectDefinition); + procedure HandleData(AData: Pointer); virtual; abstract; public constructor Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); override; @@ -86,6 +81,7 @@ type implementation uses + System.Classes, System.SysUtils, X2Log.Global, @@ -100,37 +96,88 @@ uses type TCustomFSXFunctionWorkerDataHandler = class(TInterfacedObject, IFSXSimConnectDataHandler) private - FWorker: TCustomFSXFunctionWorker; + FWorker: TFSXLEDFunctionWorker; protected { IFSXSimConnectDataHandler } procedure HandleData(AData: Pointer); - property Worker: TCustomFSXFunctionWorker read FWorker; + property Worker: TFSXLEDFunctionWorker read FWorker; public - constructor Create(AWorker: TCustomFSXFunctionWorker); + constructor Create(AWorker: TFSXLEDFunctionWorker); end; + TLuaSimConnect = class(TPersistent) + private + FProvider: TFSXLEDFunctionProvider; + protected + property Provider: TFSXLEDFunctionProvider read FProvider; + public + constructor Create(AProvider: TFSXLEDFunctionProvider); + published + procedure Monitor(Context: ILuaContext); + end; + + +type + TLuaSimConnectType = record + TypeName: string; + Units: string; + DataType: SIMCONNECT_DATAType; + end; + +const + LuaSimConnectTypes: array[0..4] of TLuaSimConnectType = + ( + ( TypeName: 'Bool'; Units: FSX_UNIT_BOOL; DataType: SIMCONNECT_DATAType_INT32 ), + ( TypeName: 'Percent'; Units: FSX_UNIT_PERCENT; DataType: SIMCONNECT_DATAType_FLOAT64 ), + ( TypeName: 'Integer'; Units: FSX_UNIT_NUMBER; DataType: SIMCONNECT_DATAType_INT32 ), + ( TypeName: 'Float'; Units: FSX_UNIT_NUMBER; DataType: SIMCONNECT_DATAType_FLOAT64 ), + ( TypeName: 'Mask'; Units: FSX_UNIT_MASK; DataType: SIMCONNECT_DATATYPE_INT32 ) + ); + + +function GetUnits(const AType: string; out AUnits: string; out ADataType: SIMCONNECT_DATAType): Boolean; +var + typeIndex: Integer; + +begin + for typeIndex := Low(LuaSimConnectTypes) to High(LuaSimConnectTypes) do + if SameText(AType, LuaSimConnectTypes[typeIndex].TypeName) then + begin + AUnits := LuaSimConnectTypes[typeIndex].Units; + ADataType := LuaSimConnectTypes[typeIndex].DataType; + Exit(True); + end; + + Result := False; +end; + { TFSXLEDFunctionProvider } -constructor TFSXLEDFunctionProvider.Create; +constructor TFSXLEDFunctionProvider.Create(const AScriptFolders: TStringDynArray); begin - inherited Create; - FSimConnectLock := TCriticalSection.Create; + FScriptSimConnect := TLuaSimConnect.Create(Self); + + inherited Create(AScriptFolders); end; destructor TFSXLEDFunctionProvider.Destroy; begin + FreeAndNil(FScriptSimConnect); FreeAndNil(FSimConnectLock); inherited Destroy; end; +(* procedure TFSXLEDFunctionProvider.RegisterFunctions; begin + inherited RegisterFunctions; + { Systems } RegisterFunction(TFSXBatteryMasterFunction.Create( Self, FSXFunctionDisplayNameBatteryMaster, FSXFunctionUIDBatteryMaster)); RegisterFunction(TFSXDeIceFunction.Create( Self, FSXFunctionDisplayNameDeIce, FSXFunctionUIDDeIce)); @@ -193,6 +240,36 @@ begin { ATC } RegisterFunction(TFSXATCVisibilityFunction.Create(FSXProviderUID)); end; +*) + +function TFSXLEDFunctionProvider.CreateLuaLEDFunction(AInfo: ILuaTable; AOnSetup: ILuaFunction): TCustomLuaLEDFunction; +begin + Result := TFSXLEDFunction.Create(Self, AInfo, AOnSetup); +end; + + +procedure TFSXLEDFunctionProvider.InitInterpreter; +var + simConnectType: ILuaTable; + typeIndex: Integer; + +begin + inherited InitInterpreter; + + Interpreter.RegisterFunctions(FScriptSimConnect, 'SimConnect'); + + simConnectType := TLuaTable.Create; + for typeIndex := Low(LuaSimConnectTypes) to High(LuaSimConnectTypes) do + simConnectType.SetValue(LuaSimConnectTypes[typeIndex].TypeName, LuaSimConnectTypes[typeIndex].TypeName); + + Interpreter.SetGlobalVariable('SimConnectType', simConnectType); +end; + + +procedure TFSXLEDFunctionProvider.SetupWorker(AWorker: TFSXLEDFunctionWorker; AOnSetup: ILuaFunction); +begin + AOnSetup.Call([AWorker.UID]); +end; function TFSXLEDFunctionProvider.GetUID: string; @@ -245,81 +322,80 @@ end; -{ TCustomFSXFunction } -constructor TCustomFSXFunction.Create(AProvider: TFSXLEDFunctionProvider; const ADisplayName, AUID: string); +{ TFSXLEDFunction } +constructor TFSXLEDFunction.Create(AProvider: TFSXLEDFunctionProvider; AInfo: ILuaTable; AOnSetup: ILuaFunction); begin - inherited Create(AProvider.GetUID); + inherited Create(AProvider, AInfo, AOnSetup); FProvider := AProvider; - FDisplayName := ADisplayName; - FUID := AUID; end; -procedure TCustomFSXFunction.InitializeWorker(AWorker: TCustomLEDMultiStateFunctionWorker); -begin - (AWorker as TCustomFSXFunctionWorker).SimConnect := Provider.GetSimConnect; -end; - - -function TCustomFSXFunction.GetCategoryName: string; +function TFSXLEDFunction.GetDefaultCategoryName: string; begin Result := FSXCategory; end; -function TCustomFSXFunction.GetDisplayName: string; +function TFSXLEDFunction.GetWorkerClass: TCustomLEDMultiStateFunctionWorkerClass; begin - Result := FDisplayName; + Result := TFSXLEDFunctionWorker; end; -function TCustomFSXFunction.GetUID: string; +procedure TFSXLEDFunction.InitializeWorker(AWorker: TCustomLEDMultiStateFunctionWorker); +var + worker: TFSXLEDFunctionWorker; + begin - Result := FUID; + worker := (AWorker as TFSXLEDFunctionWorker); + worker.Provider := Provider; + + Provider.SetupWorker(worker, Setup); end; -{ TCustomFSXFunctionWorker } -constructor TCustomFSXFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string); +{ TFSXLEDFunctionWorker } +constructor TFSXLEDFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string); begin { We can't pass ourselves as the Data Handler, as it would keep a reference to this worker from the SimConnect interface. That'd mean the worker never gets destroyed, and SimConnect never shuts down. Hence this proxy class. } FDataHandler := TCustomFSXFunctionWorkerDataHandler.Create(Self); + FDefinitionID := TList.Create; inherited Create(AProviderUID, AFunctionUID, AStates, ASettings, APreviousState); end; -destructor TCustomFSXFunctionWorker.Destroy; +destructor TFSXLEDFunctionWorker.Destroy; +var + simConnect: IFSXSimConnect; + id: Cardinal; + begin - if DefinitionID <> 0 then - SimConnect.RemoveDefinition(DefinitionID, DataHandler); + if Assigned(Provider) and (DefinitionID.Count > 0) then + begin + simConnect := (Provider as TFSXLEDFunctionProvider).GetSimConnect; + + for id in DefinitionID do + simConnect.RemoveDefinition(id, DataHandler); + end; + + FreeAndNil(FDefinitionID); inherited Destroy; end; -procedure TCustomFSXFunctionWorker.SetSimConnect(const Value: IFSXSimConnect); -var - definition: IFSXSimConnectDefinition; - +procedure TFSXLEDFunctionWorker.AddDefinition(ADefinition: IFSXSimConnectDefinition); begin - FSimConnect := Value; - - if Assigned(SimConnect) then - begin - definition := SimConnect.CreateDefinition; - RegisterVariables(definition); - - FDefinitionID := SimConnect.AddDefinition(definition, DataHandler); - end; + DefinitionID.Add((Provider as TFSXLEDFunctionProvider).GetSimConnect.AddDefinition(ADefinition, DataHandler)); end; { TCustomFSXFunctionWorkerDataHandler } -constructor TCustomFSXFunctionWorkerDataHandler.Create(AWorker: TCustomFSXFunctionWorker); +constructor TCustomFSXFunctionWorkerDataHandler.Create(AWorker: TFSXLEDFunctionWorker); begin inherited Create; @@ -333,7 +409,65 @@ begin end; -initialization - TLEDFunctionRegistry.Register(TFSXLEDFunctionProvider.Create); +{ TLuaSimConnect } +constructor TLuaSimConnect.Create(AProvider: TFSXLEDFunctionProvider); +begin + inherited Create; + + FProvider := AProvider; +end; + + +procedure TLuaSimConnect.Monitor(Context: ILuaContext); +var + workerID: string; + variables: ILuaTable; + onData: ILuaFunction; + worker: TCustomLuaLEDFunctionWorker; + definition: IFSXSimConnectDefinition; + variable: TLuaKeyValuePair; + info: ILuaTable; + units: string; + dataType: SIMCONNECT_DATAType; + +begin + if Context.Parameters.Count < 3 then + raise ELuaScriptError.Create('Not enough parameters for SimConnect.Monitor'); + + if Context.Parameters[0].VariableType <> VariableString then + raise ELuaScriptError.Create('Context expected for SimConnect.Monitor parameter 1'); + + if Context.Parameters[1].VariableType <> VariableTable then + raise ELuaScriptError.Create('Table expected for SimConnect.Monitor parameter 2'); + + if Context.Parameters[2].VariableType <> VariableFunction then + raise ELuaScriptError.Create('Function expected for SimConnect.Monitor parameter 3'); + + workerID := Context.Parameters[0].AsString; + variables := Context.Parameters[1].AsTable; + onData := Context.Parameters[2].AsFunction; + + worker := Provider.FindWorker(workerID); + if not Assigned(worker) then + raise ELuaScriptError.Create('Context expected for SimConnect.Monitor parameter 1'); + + definition := Provider.GetSimConnect.CreateDefinition; + + for variable in variables do + begin + if variable.Value.VariableType = VariableTable then + begin + info := variable.Value.AsTable; + if info.HasValue('variable') and + info.HasValue('type') and + GetUnits(info.GetValue('type').AsString, units, dataType) then + begin + definition.AddVariable(info.GetValue('variable').AsString, units, dataType); + end; + end; + end; + + (worker as TFSXLEDFunctionWorker).AddDefinition(definition); +end; end. diff --git a/G940LEDControl/Units/FSXLEDFunctionWorker.pas b/G940LEDControl/Units/FSXLEDFunctionWorker.pas index f8f9303..3dfaa37 100644 --- a/G940LEDControl/Units/FSXLEDFunctionWorker.pas +++ b/G940LEDControl/Units/FSXLEDFunctionWorker.pas @@ -1,6 +1,7 @@ unit FSXLEDFunctionWorker; interface +(* uses OtlTaskControl, @@ -268,9 +269,10 @@ type constructor Create(const AProviderUID: string; const AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); override; destructor Destroy; override; end; - +*) implementation +(* uses System.Math, System.StrUtils, @@ -1181,6 +1183,6 @@ begin FVisible := visible; FOnStateChanged(visible); end; -end; +end;*) end. diff --git a/G940LEDControl/Units/LuaLEDFunctionProvider.pas b/G940LEDControl/Units/LuaLEDFunctionProvider.pas new file mode 100644 index 0000000..0dda51c --- /dev/null +++ b/G940LEDControl/Units/LuaLEDFunctionProvider.pas @@ -0,0 +1,332 @@ +unit LuaLEDFunctionProvider; + +interface +uses + System.Generics.Collections, + System.SysUtils, + System.Types, + + LEDFunction, + LEDFunctionIntf, + LEDStateIntf, + Lua; + +type + ELuaScriptError = class(Exception); + + TCustomLuaLEDFunctionWorker = class; + + + TCustomLuaLEDFunction = class(TCustomMultiStateLEDFunction) + private + FCategoryName: string; + FDisplayName: string; + FUID: string; + FScriptStates: ILuaTable; + FSetup: ILuaFunction; + protected + procedure RegisterStates; override; + + function GetDefaultCategoryName: string; virtual; + + { ILEDFunction } + function GetCategoryName: string; override; + function GetDisplayName: string; override; + function GetUID: string; override; + + property ScriptStates: ILuaTable read FScriptStates; + property Setup: ILuaFunction read FSetup; + public + constructor Create(AProvider: ILEDFunctionProvider; AInfo: ILuaTable; ASetup: ILuaFunction); + end; + + + TCustomLuaLEDFunctionProvider = class(TCustomLEDFunctionProvider) + private + FInterpreter: TLua; + FScriptFolders: TStringDynArray; + FWorkers: TDictionary; + protected + function CreateLuaLEDFunction(AInfo: ILuaTable; ASetup: ILuaFunction): TCustomLuaLEDFunction; virtual; abstract; + + procedure InitInterpreter; virtual; + procedure RegisterFunctions; override; + + procedure RegisterWorker(AWorker: TCustomLuaLEDFunctionWorker); + procedure UnregisterWorker(AWorker: TCustomLuaLEDFunctionWorker); + function FindWorker(const AUID: string): TCustomLuaLEDFunctionWorker; + + property Interpreter: TLua read FInterpreter; + property ScriptFolders: TStringDynArray read FScriptFolders; + property Workers: TDictionary read FWorkers; + public + constructor Create(const AScriptFolders: TStringDynArray); + destructor Destroy; override; + end; + + + TCustomLuaLEDFunctionWorker = class(TCustomLEDMultiStateFunctionWorker) + private + FProvider: TCustomLuaLEDFunctionProvider; + FUID: string; + + procedure SetProvider(const Value: TCustomLuaLEDFunctionProvider); + protected + property Provider: TCustomLuaLEDFunctionProvider read FProvider write SetProvider; + public + constructor Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string = ''); override; + destructor Destroy; override; + + property UID: string read FUID; + end; + + +implementation +uses + System.IOUtils, + + LEDColorIntf, + LEDState; + + +const + LuaLEDColors: array[TLEDColor] of string = + ( + 'Off', 'Green', 'Amber', 'Red', + 'FlashingGreenFast', 'FlashingGreenNormal', + 'FlashingAmberFast', 'FlashingAmberNormal', + 'FlashingRedFast', 'FlashingRedNormal' + ); + +function GetLEDColor(const AValue: string; ADefault: TLEDColor = lcOff): TLEDColor; +var + color: TLEDColor; + +begin + for color := Low(TLEDColor) to High(TLEDColor) do + if SameText(AValue, LuaLEDColors[color]) then + exit(color); + + Result := ADefault; +end; + + + +{ TCustomLuaLEDFunctionProvider } +constructor TCustomLuaLEDFunctionProvider.Create(const AScriptFolders: TStringDynArray); +begin + FWorkers := TDictionary.Create; + FInterpreter := TLua.Create; + FScriptFolders := AScriptFolders; + + InitInterpreter; + + inherited Create; +end; + + +destructor TCustomLuaLEDFunctionProvider.Destroy; +begin + FreeAndNil(FInterpreter); + FreeAndNil(FWorkers); + + inherited Destroy; +end; + + +procedure TCustomLuaLEDFunctionProvider.InitInterpreter; +var + table: ILuaTable; + color: TLEDColor; + +begin + Interpreter.RegisterFunction('RegisterFunction', + procedure(Context: ILuaContext) + var + info: ILuaTable; + setup: ILuaFunction; + + begin + if Context.Parameters.Count < 2 then + raise ELuaScriptError.Create('Not enough parameters for RegisterFunction'); + + if Context.Parameters[0].VariableType <> VariableTable then + raise ELuaScriptError.Create('Table expected for RegisterFunction parameter 1'); + + if Context.Parameters[1].VariableType <> VariableFunction then + raise ELuaScriptError.Create('Function expected for RegisterFunction parameter 2'); + + info := Context.Parameters[0].AsTable; + setup := Context.Parameters[1].AsFunction; + + if not info.HasValue('uid') then + raise ELuaScriptError.Create('"uid" value is required for RegisterFunction parameter 1'); + + RegisterFunction(CreateLuaLEDFunction(info, setup)); + end); + + table := TLuaTable.Create; + for color := Low(TLEDColor) to High(TLEDColor) do + table.SetValue(LuaLEDColors[color], LuaLEDColors[color]); + + Interpreter.SetGlobalVariable('LEDColor', table); + + // #ToDo1 -oMvR: 28-5-2017: SetState +end; + + +procedure TCustomLuaLEDFunctionProvider.RegisterFunctions; +var + scriptFolder: string; + scriptFile: string; + +begin + for scriptFolder in ScriptFolders do + if TDirectory.Exists(scriptFolder) then + for scriptFile in TDirectory.GetFiles(ScriptFolder, '*.lua') do + try + Interpreter.LoadFromFile(scriptFile); + except + on E:Exception do + Exception.RaiseOuterException(ELuaScriptError.CreateFmt('Error while loading script %s: %s', [scriptFile, E.Message])); + end; +end; + + +procedure TCustomLuaLEDFunctionProvider.RegisterWorker(AWorker: TCustomLuaLEDFunctionWorker); +begin + Workers.Add(AWorker.UID, AWorker); +end; + + +procedure TCustomLuaLEDFunctionProvider.UnregisterWorker(AWorker: TCustomLuaLEDFunctionWorker); +begin + Workers.Remove(AWorker.UID); +end; + + +function TCustomLuaLEDFunctionProvider.FindWorker(const AUID: string): TCustomLuaLEDFunctionWorker; +begin + if not Workers.TryGetValue(AUID, Result) then + Result := nil; +end; + + + +{ TCustomLuaLEDFunction } +constructor TCustomLuaLEDFunction.Create(AProvider: ILEDFunctionProvider; AInfo: ILuaTable; ASetup: ILuaFunction); +begin + FCategoryName := GetDefaultCategoryName; + FDisplayName := 'Unknown function'; + FSetup := ASetup; + + FUID := AInfo.GetValue('uid').AsString; + + if AInfo.HasValue('category') then + FCategoryName := AInfo.GetValue('category').AsString; + + if AInfo.HasValue('displayName') then + FDisplayName := AInfo.GetValue('displayName').AsString; + + FScriptStates := nil; + if AInfo.HasValue('states') then + FScriptStates := AInfo.GetValue('states').AsTable; + + // #ToDo1 -oMvR: 28-5-2017: application filter? + + inherited Create(AProvider.GetUID); +end; + + +function TCustomLuaLEDFunction.GetCategoryName: string; +begin + Result := FCategoryName; +end; + + +function TCustomLuaLEDFunction.GetDisplayName: string; +begin + Result := FDisplayName; +end; + + +function TCustomLuaLEDFunction.GetUID: string; +begin + Result := FUID; +end; + + +procedure TCustomLuaLEDFunction.RegisterStates; +var + state: TLuaKeyValuePair; + displayName: string; + defaultColor: TLEDColor; + info: ILuaTable; + +begin + if not Assigned(ScriptStates) then + exit; + + for state in ScriptStates do + begin + displayName := state.Key.AsString; + defaultColor := lcOff; + + if state.Value.VariableType = VariableTable then + begin + info := state.Value.AsTable; + if info.HasValue('displayName') then + displayName := info.GetValue('displayName').AsString; + + if info.HasValue('default') then + defaultColor := GetLEDColor(info.GetValue('default').AsString); + end; + + RegisterState(TLEDState.Create(state.Key.AsString, displayName, defaultColor)); + end; +end; + + +function TCustomLuaLEDFunction.GetDefaultCategoryName: string; +begin + Result := 'Other'; +end; + + +{ TCustomLuaLEDFunctionWorker } +constructor TCustomLuaLEDFunctionWorker.Create(const AProviderUID, AFunctionUID: string; AStates: ILEDMultiStateFunction; ASettings: ILEDFunctionWorkerSettings; const APreviousState: string); +var + workerGUID: TGUID; + +begin + if CreateGUID(workerGUID) <> 0 then + RaiseLastOSError; + + FUID := GUIDToString(workerGUID); + + inherited Create(AProviderUID, AFunctionUID, AStates, ASettings, APreviousState); +end; + +destructor TCustomLuaLEDFunctionWorker.Destroy; +begin + SetProvider(nil); + + inherited Destroy; +end; + + +procedure TCustomLuaLEDFunctionWorker.SetProvider(const Value: TCustomLuaLEDFunctionProvider); +begin + if Value <> FProvider then + begin + if Assigned(FProvider) then + FProvider.UnregisterWorker(Self); + + FProvider := Value; + + if Assigned(FProvider) then + FProvider.RegisterWorker(Self); + end; +end; + +end. diff --git a/G940LEDControl/Units/StaticLEDFunction.pas b/G940LEDControl/Units/StaticLEDFunction.pas index c3cb7df..076b4e2 100644 --- a/G940LEDControl/Units/StaticLEDFunction.pas +++ b/G940LEDControl/Units/StaticLEDFunction.pas @@ -114,8 +114,4 @@ begin Result := FState; end; - -initialization - TLEDFunctionRegistry.Register(TStaticLEDFunctionProvider.Create); - end. diff --git a/G940LEDControl/XSD/SimBaseDocument.settings.xml b/G940LEDControl/XSD/SimBaseDocument.settings.xml index 10e213f..20c2026 100644 --- a/G940LEDControl/XSD/SimBaseDocument.settings.xml +++ b/G940LEDControl/XSD/SimBaseDocument.settings.xml @@ -1,2 +1,2 @@ -SingleF:\Development\G940\G940LEDControl\Units\SimBaseDocumentXMLBinding.pas +Single..\Units\SimBaseDocumentXMLBinding.pas diff --git a/G940LEDControl/XSD/SimBaseDocument.xsd b/G940LEDControl/XSD/SimBaseDocument.xsd index f7996de..66360dd 100644 --- a/G940LEDControl/XSD/SimBaseDocument.xsd +++ b/G940LEDControl/XSD/SimBaseDocument.xsd @@ -1,6 +1,4 @@ - -