From a7d1d94cbae98f8cd804f453d24bd8036e314518 Mon Sep 17 00:00:00 2001 From: whiteshark0 Date: Fri, 17 Apr 2009 16:45:41 +0000 Subject: lua plugin interface implemented it offers some basic environment to the loaded plugins see ./game/plugins/PluginDescription.odt for further explantation. git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@1674 b956fd51-792f-4845-bead-9b4dfca2ff2c --- Lua/game/plugins/PluginDescription.odt | Bin 0 -> 18415 bytes Lua/src/base/UMain.pas | 11 +- Lua/src/lua/UHookableEvent.pas | 267 ++++++++++ Lua/src/lua/ULuaCore.pas | 864 +++++++++++++++++++++++++++++++++ Lua/src/ultrastardx.dpr | 2 + 5 files changed, 1142 insertions(+), 2 deletions(-) create mode 100644 Lua/game/plugins/PluginDescription.odt create mode 100644 Lua/src/lua/UHookableEvent.pas create mode 100644 Lua/src/lua/ULuaCore.pas (limited to 'Lua') diff --git a/Lua/game/plugins/PluginDescription.odt b/Lua/game/plugins/PluginDescription.odt new file mode 100644 index 00000000..8ed8def9 Binary files /dev/null and b/Lua/game/plugins/PluginDescription.odt differ diff --git a/Lua/src/base/UMain.pas b/Lua/src/base/UMain.pas index 2da1ca39..d0a9afee 100644 --- a/Lua/src/base/UMain.pas +++ b/Lua/src/base/UMain.pas @@ -183,6 +183,8 @@ uses UGraphicClasses, UPluginDefs, UPlatform, + ULuaCore, + UHookableEvent, ULuaGl, ULuaLog, ULuaTexture, @@ -417,8 +419,13 @@ begin chr(0)) ); } - Log.LogStatus('Running Core', 'Initialization'); - //Core.Run; + LuaCore := TLuaCore.Create; + + + LuaCore.BrowseDir(PluginPath); + LuaCore.DumpPlugins; + + //------------------------------ //Start- Mainloop diff --git a/Lua/src/lua/UHookableEvent.pas b/Lua/src/lua/UHookableEvent.pas new file mode 100644 index 00000000..cc7cc443 --- /dev/null +++ b/Lua/src/lua/UHookableEvent.pas @@ -0,0 +1,267 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/branches/experimental/Lua/src/lua/ULuaGl.pas $ + * $Id: ULuaGl.pas 1555 2009-01-11 18:19:42Z Hawkear $ + *} + +unit UHookableEvent; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses ULua; + +type + { Record holding information about a hook of an event } + PHook = ^THook; + THook = record + Handle: Integer; //< Handle to identify the hook, e.g. for unhooking by plugin + Parent: Integer; //< Lua Core Handle this hook belongs to + + Next: PHook; //< Next Hook in list (nil for the first) + end; + + { procedure is called before each call to the hooking lua functions, to push values on stack + returns the number of pushed arguments} + PrepareStackProc = Function: Integer; + + { class representing a hookable event } + THookableEvent = class + private + iHandle: Integer; //< used to unregister at lua core + LastHook: PHook; //< last hook in hook list, first to be called + NextHookHandle: Integer; //< handle to identify next hook + + sName: String; //< the events name + + PrepareStack: PrepareStackProc; //< prepare stack procedure passed to constructor + public + constructor Create(Name: String; const Proc: PrepareStackProc = nil); + + property Name: String read sName; //< returns the events name + property Handle: Integer read iHandle; //< returns the events name + + procedure Hook(L: Plua_State; Parent: Integer; Proc: String); //< pushes hook object/table to the lua stack + procedure UnHook(L: Plua_State; hHook: Integer); //< unhook by plugin. push true or error string to lua stack + + procedure UnHookByParent(Parent: Integer); //< deletes all hooks by a specified parent (unhook by core) + + function CallHookChain(Breakable: Boolean): PLua_State; //< calls the events hookchain. if breakable, plugin can breake the chain by returning a value != 0 or false or nil + + destructor Destroy; + end; + +{ the default function for THookableEvent.PrepareStack it don't pass any arguments } +function PrepareStack_Dummy: Integer; + +{ function in resulting hook table. it calls the unhook command of the event on plugins demand } +function LuaHook_UnHook(L: Plua_State): Integer; cdecl; + +implementation +uses ULuaCore; + +constructor THookableEvent.Create(Name: String; const Proc: PrepareStackProc); +begin + inherited Create; + + Self.sName := Name; + + if (@Proc = nil) then + Self.PrepareStack := @PrepareStack_Dummy + else + Self.PrepareStack := Proc; + + //init LastHook pointer w/ nil + LastHook := nil; + NextHookHandle := 1; + + iHandle := LuaCore.RegisterEvent(Self); +end; + +destructor THookableEvent.Destroy; +var + Prev: PHook; + Cur: PHook; +begin + //delete all hooks + Cur := LastHook; + While (Cur <> nil) do + begin + Prev := Cur; + Cur := Prev.Next; + + Dispose(Prev); + end; + + //remove from luacores list + LuaCore.UnRegisterEvent(iHandle); + + inherited; +end; + +{ adds hook to events list and pushes hook object/table to the lua stack } +procedure THookableEvent.Hook(L: Plua_State; Parent: Integer; Proc: String); + var + Item: PHook; +begin + // get mem and fill it w/ data + New(Item); + Item.Handle := NextHookHandle; + Inc(NextHookHandle); + + Item.Parent := Parent; + + // add at front of the hook chain + Item.Next := LastHook; + LastHook := Item; + + //we need 2 free stack slots + lua_checkstack(L, 2); + + //create the hook table, we need 2 elements (event name and unhook function) + lua_createtable(L, 0, 2); + + //push events name + lua_pushstring(L, PAnsiChar(Name)); + + //add the name to the table + lua_setfield(L, -2, 'Event'); + + //push hook id to the stack + lua_pushinteger(L, Item.Handle); + + //create a c closure, append one value from stack(the id) + //this will pop both, the function and the id + lua_pushcclosure(L, LuaHook_UnHook, 1); + + //add the function to our table + lua_setfield(L, -2, 'Unhook'); + + //the table is left on the stack, it is our result +end; + +{ unhook by plugin. push true or error string to lua stack } +procedure THookableEvent.UnHook(L: Plua_State; hHook: Integer); + var + Cur, Prev: PHook; +begin + if (hHook < NextHookHandle) and (hHook > 0) then + begin + //Search for the Hook + Prev := nil; + Cur := LastHook; + + while (Cur <> nil) do + begin + if (Cur.Handle = hHook) then + begin //we found the hook => remove it + if (prev <> nil) then + Prev.Next := Cur.Next + else //last hook found + LastHook := Cur.Next; + + //free hooks memory + Dispose(Cur); + + //indicate success + lua_pushboolean(L, True); + exit; //break the chain and exit the function + end; + Prev := Cur; + Cur := Prev.Next; + end; + + lua_pushstring(L, PAnsiChar('handle already unhooked')); //the error description + end + else + lua_pushstring(L, PAnsiChar('undefined hook handle')); //the error description +end; + +{ deletes all hooks by a specified parent (unhook by core) } +procedure THookableEvent.UnHookByParent(Parent: Integer); + var + Cur, Prev: PHook; +begin + Prev := nil; + Cur := LastHook; + + While (Cur <> nil) do + begin + If (Cur.Parent = Parent) then + begin //found a hook from parent => remove it + If (Prev <> nil) then + Prev.Next := Cur.Next + Else + LastHook := Cur.Next; + + Dispose(Cur); + + Cur := Prev.Next; + end + else //move through the chain + begin + Prev := Cur; + Cur := Prev.Next; + end; + end; +end; + +{ calls the events hookchain. if breakable, plugin can breake the chain + by returning a value other than 0 or false or nil + breakable is pushed as the first parameter to the hooking functions + if chain is broken the LuaStack is returned, with all results left + you may call lua_clearstack } +function THookableEvent.CallHookChain(Breakable: Boolean): Plua_State; + var + Cur: PHook; +begin + +end; + +{ the default function for THookableEvent.PrepareStack it don't pass any arguments } +function PrepareStack_Dummy: Integer; +begin + Result := 0; +end; + +{ function in resulting hook table. it calls the unhook command of the event on plugins demand } +function LuaHook_UnHook(L: Plua_State): Integer; cdecl; +begin + //lua_upvalueindex + Result := 0; +end; + +{ this is a helper in case an evenet owner don't has no use for the results + returns number of popped elements } +function Lua_ClearStack(L: Plua_State): Integer; + var I: Integer; +begin + Result := lua_gettop(L); + lua_pop(L, Result); +end; + +end. \ No newline at end of file diff --git a/Lua/src/lua/ULuaCore.pas b/Lua/src/lua/ULuaCore.pas new file mode 100644 index 00000000..9f9188c1 --- /dev/null +++ b/Lua/src/lua/ULuaCore.pas @@ -0,0 +1,864 @@ +{* UltraStar Deluxe - Karaoke Game + * + * UltraStar Deluxe is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * $URL: https://ultrastardx.svn.sourceforge.net/svnroot/ultrastardx/branches/experimental/Lua/src/lua/ULuaGl.pas $ + * $Id: ULuaGl.pas 1555 2009-01-11 18:19:42Z Hawkear $ + *} + +unit ULuaCore; + +interface + +{$IFDEF FPC} + {$MODE Delphi} +{$ENDIF} + +{$I switches.inc} + +uses SysUtils, ULua, UHookableEvent; + +type + { this exception is raised when the lua panic function + is called. Only in case we use call instead of pcall. + it has the lua error string in its message attribute } + ELuaException = class(Exception); + + { record represents item of Eventlist of TLuaCore } + PEventListItem = ^TEventListItem; + TEventListItem = record + Event: THookableEvent; + Next: PEventListItem; + end; + + { record represents a module } + TLuaModule = record + Name: String; + Functions: Array of luaL_reg; //modules functions, w/ trailing nils this time + end; + + TLuaPlugin_Status = (psNone, psRunning, psClosed, psErrorOnLoad, psErrorOnCall, psErrorInInit, psErrorOnRun); + { class represents a loaded plugin } + TLuaPlugin = class + private + iId: Integer; + Filename: WideString; + State: Plua_State; //< all functions of this plugin are called with this Lua state + bPaused: Boolean; //< If true no lua functions from this state are called + ErrorCount: Integer; //< counts the errors that occured during function calls of this plugin + + sName: String; + sVersion: String; + sAuthor: String; + sURL: String; + + sStatus: TLuaPlugin_Status; + public + constructor Create(Filename: WideString; Id: Integer); + + property Id: Integer read iId; + property Name: String read sName; + property Version: String read sVersion; + property Author: String read sAuthor; + property Url: String read sUrl; + + property Status: TLuaPlugin_Status read sStatus; + property CountErrors: Integer read ErrorCount; + + property LuaState: Plua_State read State; + + procedure Load; + + procedure Register(Name, Version, Author, Url: String); + function HasRegistred: Boolean; + + procedure PausePlugin(doPause: Boolean); + property Paused: boolean read bPaused write PausePlugin; + + { calls the lua function in the global w/ the given name. + the arguments to the function have to be pushed to the stack + before calling this function. + the arguments and the function will be removed from stack + results will not be removed. + if result is false there was an error calling the function + if ReportErrors is true the errorstring is popped from stack + and written to error.log otherwise it is left on stack} + function CallFunctionByName(Name: String; const nArgs: Integer = 0; const nResults: Integer = 0; const ReportErrors: Boolean = True): Boolean; + procedure ClearStack; + + procedure Unload; //< Destroys the Luastate, and frees as much mem as possible, w/o destroying the class and important information + + destructor Destroy; + end; + + { class managing the plugins w/ their LuaStates, the events and modules + it also offers the usdx table to the plugins w/ some basic functionality + like self unload or hook getting} + TLuaCore = class + private + EventList: PEventListItem; //< pointer to first registred Event, ordered by name + EventHandles: Array of String; //< Index is Events handle, value is events name. if length(value) is 0 handle is considered unregistred + + Plugins: Array of TLuaPlugin; + protected + Modules: Array of TLuaModule; //< modules that has been registred, has to be proctected because fucntions of this unit need to get access + + function GetModuleIdByName(Name: String): Integer; //returns id of given module, or -1 if module is not found + public + constructor Create; + destructor Destroy; + + procedure BrowseDir(Dir: WideString); //< searches for files w/ extension .usdx in the specified dir and tries to load them w/ lua + procedure LoadPlugin(Filename: WideString); //< tries to load filename w/ lua and creates the default usdx lua environment for the plugins state + + function GetPluginByName(Name: String): TLuaPlugin; + function GetPluginById(Id: Integer): TLuaPlugin; + + { this function adds a module loader for your functions + name is the name the script needs to write in its require() + Functions is an array of lua calling compatible functions + w/o trailing nils! } + procedure RegisterModule(Name: String; const Functions: Array of luaL_reg); + + function RegisterEvent(Event: THookableEvent): Integer; //< adds the event to eventlist and returns its handle + procedure UnRegisterEvent(hEvent: Integer); //< removes the event from eventlist by handle + + function GetEventbyName(Name: String): THookableEvent; //< tries to find the event w/ the given name in the list + function GetEventbyHandle(hEvent: Integer): THookableEvent; //< tries to find the event w/ the given handle + + procedure PrepareState(L: Plua_State); + + procedure DumpPlugins; //< prints plugin runtime information w/ Log.LogStatus + end; + +//some luastyle functions to call from lua scripts +{ register global, used by plugins to identify + register(plugin name, plugin version, [plugin author], [plugin homepage]) + can only be called once since the global "register" is niled by the function + returns true on success. (name does not exist)} +function TLuaPlugin_Register (L: Plua_State): Integer; cdecl; + +{ moduleloader for usdx.* modules + stored in package.loaders[3] + package.loaders[3] (module name) + returns a function to load the requested module or a error + description(string) when the module is not found } +function TLuaCore_ModuleLoader (L: Plua_State): Integer; cdecl; + +{ loads module specified by a cfunction upvalue to + usdx.modulename and returns it. + loadmodule(module name) } +function TLuaCore_LoadModule (L: Plua_State): Integer; cdecl; + +{ custom lua panic function + it writes error string to error.log and raises an ELuaException + that may be caught } +function TLua_CustomPanic (L: Plua_State): Integer; cdecl; + + +var + LuaCore: TLuaCore; + +implementation +uses ULog, UPlatform; + +constructor TLuaCore.Create; +begin + inherited; + + //init EventList w/ nil + EventList := nil; +end; + +destructor TLuaCore.Destroy; +var + Cur: PEventListItem; + Prev: PEventListItem; +begin + SetLength(EventHandles, 0); + + //delete event list + Cur := EventList; + + While(Cur <> nil) do + begin + Prev := Cur; + Cur := Prev.Next; + + Dispose(Prev); + end; + + inherited; +end; + +{ searches for files w/ extension .usdx in the specified + dir and tries to load them w/ lua } +procedure TLuaCore.BrowseDir(Dir: WideString); + var + Files: TDirectoryEntryArray; + I: Integer; +begin + try + Files := Platform.DirectoryFindFiles(Dir, '.usdx', true) + except + Log.LogError('Couldn''t deal with directory/file: ' + Dir + ' in TLuaCore.BrowseDir') + end; + + for I := 0 to High(Files) do + if (Files[I].IsDirectory) then + BrowseDir(Dir + Files[i].Name) //browse recursive + else if (Files[I].IsFile) then + LoadPlugin(Dir + Files[i].Name); +end; + +{ tries to load filename w/ lua and creates the default + usdx lua environment for the plugins state } +procedure TLuaCore.LoadPlugin(Filename: WideString); + var + Len: Integer; +begin + Len := Length(Plugins); + SetLength(Plugins, Len + 1); + Plugins[Len] := TLuaPlugin.Create(Filename, Len); + Plugins[Len].Load; +end; + +{ returns Plugin on success nil on failure } +function TLuaCore.GetPluginByName(Name: String): TLuaPlugin; + var + I: Integer; +begin + Result := nil; + Name := lowercase(Name); + + For I := 0 to High(Plugins) do + If (lowercase(Plugins[I].Name) = Name) then + begin + Result := GetPluginById(I); + Exit; + end; +end; + +{ returns Plugin on success nil on failure } +function TLuaCore.GetPluginById(Id: Integer): TLuaPlugin; +begin + If (Id >= 0) AND (Id <= High(Plugins)) then + Result := Plugins[Id] + Else + Result := nil; +end; + +{ this function adds a module loader for your functions + name is the name the script needs to write in its require() + Functions is an array of lua calling compatible functions + w/o trailing nils! } +procedure TLuaCore.RegisterModule(Name: String; const Functions: Array of luaL_reg); + var + Len: Integer; + FuncLen: Integer; + I: Integer; +begin + Len := Length(Modules); + SetLength(Modules, Len + 1); + Modules[Len].Name := Name; + + FuncLen := Length(Functions); + SetLength(Modules[Len].Functions, FuncLen + 1); + + For I := 0 to FuncLen-1 do + Modules[Len].Functions[I] := Functions[I]; + + Modules[Len].Functions[FuncLen].name := nil; + Modules[Len].Functions[FuncLen].func := nil; +end; + +{ adds the event to eventlist and returns its handle + called by THookableEvent on creation } +function TLuaCore.RegisterEvent(Event: THookableEvent): Integer; +var + Cur, Prev, Item: PEventListItem; +begin + If (Event <> nil) and (Length(Event.Name) > 0) then + begin + Result := Length(EventHandles); + SetLength(EventHandles, Result + 1); //get Handle and copy it to result + + EventHandles[Result] := Event.Name; + + //create eventlist item + New(Item); + Item.Event := Event; + + //search for a place for this event in alphabetical order + Prev := nil; + Cur := EventList; + + While (Cur <> nil) and (CompareStr(Cur.Event.Name, EventHandles[Result]) < 0) do + begin + Prev := Cur; + Cur := Prev.Next; + end; + + //found the place => add new item + if (Prev <> nil) then + Prev.Next := Item + else //first item + EventList := Item; + + Item.Next := Cur; + end + else + Result := -1; +end; + +{ removes the event from eventlist by handle } +procedure TLuaCore.UnRegisterEvent(hEvent: Integer); + var + Cur, Prev: PEventListItem; +begin + If (hEvent >= 0) AND (hEvent <= High(EventHandles)) AND (Length(EventHandles[hEvent]) > 0) then + begin //hEvent in bounds and not already deleted + //delete from eventlist + Prev := nil; + Cur := EventList; + + While (Cur <> nil) and (CompareStr(Cur.Event.Name, EventHandles[hEvent]) < 0) do + begin + Prev := Cur; + Cur := Prev.Next; + end; + + If (Cur <> nil) and (Cur.Event.Name = EventHandles[hEvent]) then + begin //delete if found + Prev.Next := Cur.Next; // remove from list + Dispose(Cur); // free memory + end; + + //delete from handle array + EventHandles[hEvent] := ''; + end; +end; + +{ tries to find the event w/ the given name in the list } +function TLuaCore.GetEventbyName(Name: String): THookableEvent; + var + Cur: PEventListItem; +begin + Result := nil; + + if (Length(Name) > 0) then + begin + //search in eventlist + Cur := EventList; + + While (Cur <> nil) and (CompareStr(Cur.Event.Name, Name) < 0) do + begin + Cur := Cur.Next; + end; + + If (Cur <> nil) and (Cur.Event.Name = Name) then + begin //we found what we want to find + Result := Cur.Event; + end; + end; +end; + +{ tries to find the event w/ the given handle } +function TLuaCore.GetEventbyHandle(hEvent: Integer): THookableEvent; +begin + If (hEvent >= 0) AND (hEvent <= High(EventHandles)) AND (Length(EventHandles[hEvent]) > 0) then + begin //hEvent in bounds and not already deleted + Result := GetEventByName(EventHandles[hEvent]); + end + else + Result := nil; +end; + +{ prepares the given already opened Lua state with the + basic usdx environment, e.g.: base and package Modules, + usdx moduleloaders and usdx table } +procedure TLuaCore.PrepareState(L: Plua_State); +begin + //load basic lib functionality + lua_pushcfunction(L, luaopen_base); + lua_call(L, 0, 0); + lua_pop(L, lua_gettop(L)); //pop the results + + //load module functionality + lua_pushcfunction(L, luaopen_package); + lua_call(L, 0, 0); + lua_pop(L, lua_gettop(L)); //pop the results + + { adds the loader for the other standard lib to package.preload table + plugins can call e.g. require('math') if they need math functionality } + + // we need 3 free stack slots + lua_checkstack(L, 3); + + // get package table + lua_getglobal (L, PChar('package')); + + // get package.preload table + lua_getfield (L, -1, PChar('preload')); + + {**** add string lib } + + // push loader function + lua_pushcfunction(L, luaopen_string); + + // set package.preload.x loader + lua_setfield (L, -2, PChar('string')); + + {**** add table lib } + + // push loader function + lua_pushcfunction(L, luaopen_table); + + // set package.preload.x loader + lua_setfield (L, -2, PChar('table')); + + {**** add math lib } + + // push loader function + lua_pushcfunction(L, luaopen_math); + + // set package.preload.x loader + lua_setfield (L, -2, PChar('math')); + + {**** add os lib } + + // push loader function + lua_pushcfunction(L, luaopen_os); + + // set package.preload.x loader + lua_setfield (L, -2, PChar('os')); + + //pop package.preload table from stack + lua_pop(L, 1); + + // get package.loaders table + lua_getfield (L, -1, PChar('loaders')); + + {**** Move C-Library and all-in-one module loader backwards, + slot 3 is free now } + // get package.loaders[4] function + lua_getfield (L, -1, '4'); + + // and move it to package.loaders[5] + lua_setfield (L, -2, '5'); + + // get package.loaders[3] function + lua_getfield (L, -1, '3'); + + // and move it to package.loaders[4] + lua_setfield (L, -2, '4'); + + {**** now we add the core module to package.loaders[3] } + lua_pushcfunction(L, TLuaCore_ModuleLoader); + + // and move it to package.loaders[3] + lua_setfield (L, -2, '3'); + + + //pop both package and package.loaders tables from stack + lua_pop(L, 2); + +end; + +{ returns id of given module, or -1 if module is not found } +function TLuaCore.GetModuleIdByName(Name: String): Integer; + var + I: Integer; +begin + Result := -1; + + for I := 0 to High(Modules) do + if (Modules[I].Name = Name) then + begin + Result := I; + Exit; + end; +end; + +{ moduleloader for usdx.* modules + stored in package.loaders[3] + package.loaders[3] (module name) + returns a function to load the requested module or an error + description(string) when the module is not found } +function TLuaCore_ModuleLoader (L: Plua_State): Integer; cdecl; + var + Name: String; + ID: Integer; +begin + Result := 1; //we will return one value in every case (or never return in case of an error) + + if (lua_gettop(L) >= 1) then + begin + // pop all arguments but the first + if (lua_gettop(L) > 1) then + lua_pop(L, lua_gettop(L)-1); + + + if (lua_IsString(L, 1)) then + begin //we got the name => go get it + Name := lua_toString(L, 1); + + //we need at least 6 letters + //and first 5 letters have to be usdx. + if (Length(Name) > 5) and (copy(Name, 1, 5)='usdx.') then + begin + ID := LuaCore.GetModuleIdByName(copy(Name, 6, Length(Name) - 5)); + If (ID >= 0) then + begin //found the module -> return loader function + lua_pushinteger(L, Id); + lua_pushcclosure(L, TLuaCore_LoadModule, 1); + //the function is the result, so we leave it on stack + end + else + lua_pushString(L, PChar('usdx module "' + Name + '" couldn''t be found')); + end + else + lua_pushString(L, PChar('module doesn''t have "usdx." prefix')); + + end + else + luaL_argerror(L, 1, PChar('string expected')); + end + else + luaL_error(L, PChar('no modulename specified in usdx moduleloader')); +end; + +{ loads module specified by a cfunction upvalue to + usdx.modulename and returns it. + loadmodule(module name) } +function TLuaCore_LoadModule (L: Plua_State): Integer; cdecl; + var + Id: Integer; +begin + if (not lua_isnoneornil(L, lua_upvalueindex(1))) then + begin + Id := lua_ToInteger(L, lua_upvalueindex(1)); + + luaL_register(L, PChar('usdx.' + LuaCore.Modules[Id].Name), @LuaCore.Modules[Id].Functions[0]); + + Result := 1; //return table + end + else + luaL_error(L, PChar('no upvalue found in LuaCore_LoadModule')); +end; + +{ prints plugin runtime information w/ Log.LogStatus } +procedure TLuaCore.DumpPlugins; + function PluginStatusToString(Status: TLuaPlugin_Status): String; + begin + Case Status of + psNone: Result := 'not loaded'; + psRunning: Result := 'running'; + psClosed: Result := 'closed'; + psErrorOnLoad: Result := 'error during load'; + psErrorOnCall: Result := 'error during call'; + psErrorInInit: Result := 'error in plugin_init()'; + psErrorOnRun: Result := 'error on function call'; + else Result := 'unknown'; + end; + end; + var + I: Integer; +begin + //print table header + Log.LogError(' # ' + #09 + ' name ' + #09 + ' version ' + #09 + ' status ' + #09 + ' paused ' + #09 + ' #errors ', 'plugins'); + + + For I := 0 to High(Plugins) do + Log.LogError(' ' + IntToStr(Plugins[I].Id) + ' ' + #09 + + ' ' + Plugins[I].Name + ' ' + #09 + + ' ' + Plugins[I].Version + ' ' + #09 + + ' ' + PluginStatusToString(Plugins[I].Status) + ' ' + #09 + + ' ' + BoolToStr(Plugins[I].Paused, true) + ' ' + #09 + + ' ' + IntToStr(Plugins[I].CountErrors) + ' ', 'plugins'); + + If (High(Plugins)<0) then + Log.LogError(' no plugins loaded '); +end; + +// Implementation of TLuaPlugin +//-------- +constructor TLuaPlugin.Create(Filename: WideString; Id: Integer); +begin + inherited Create; + Self.iId := Id; + Self.Filename := Filename; + + // set some default attributes + Self.bPaused := False; + Self.ErrorCount := 0; + Self.sName := 'not registred'; + Self.sStatus := psNone; +end; + +destructor TLuaPlugin.Destroy; +begin + Unload; + inherited; +end; + +{ does the main loading part + can not be called by create, because Plugins[Id] isn't defined there } +procedure TLuaPlugin.Load; +begin + // create Lua state for this plugin + State := luaL_newstate; + + //set our custom panic function if s/t went wrong along the init + //we don't expect + lua_atPanic(State, TLua_CustomPanic); + + if (luaL_loadfile(State, PChar(String(Self.Filename))) = 0) then + begin // file loaded successful + { note: we run the file here, but the environment isn't + set up now. it just causes the functions to + register in globals and runs the code in the file + body. At least there should be no code, it could + neither use functions from baselibs nor load libs + with require, this code would be useless. } + if (lua_pcall(State, 0, 0, 0) = 0) then + begin // file called successful + + //let the core prepare our state + LuaCore.PrepareState(State); + + // set register function + lua_checkstack(State, 2); + lua_pushinteger(State, Id); + lua_pushcclosure(State, TLuaPlugin_Register, 1); + lua_setglobal(State, PChar('register')); + + // now run the plugin_init function + // plugin_init() if false or nothing is returned plugin init is aborted + if (CallFunctionByName('plugin_init', 0, 1)) then + begin + If (HasRegistred) AND (lua_toBoolean(State, 1)) then + begin + sStatus := psRunning; + ClearStack; + end + else + Unload; + end + else + begin + sStatus := psErrorInInit; + Log.LogError('error in plugin_init: ' + Filename, 'lua'); + Unload; + end; + end + else + begin + sStatus := psErrorOnLoad; + Log.LogError('unable to call file: ' + Filename, 'lua'); + Unload; + end; + + end + else + begin + sStatus := psErrorOnLoad; + Log.LogError('unable to load file: ' + Filename, 'lua'); + Unload; + end; +end; + +procedure TLuaPlugin.Register(Name, Version, Author, Url: String); +begin + sName := Name; + sVersion := Version; + sAuthor := Author; + sURL := Url; +end; + +{ returns true if plugin has called register } +function TLuaPlugin.HasRegistred: Boolean; +begin + Result := (Self.sName = 'not registred'); +end; + +procedure TLuaPlugin.PausePlugin(doPause: Boolean); +begin + bPaused := doPause; +end; + +{ calls the lua function in the global w/ the given name. + the arguments to the function have to be pushed to the stack + before calling this function. + the arguments and the function will be removed from stack + results will not be removed. + if result is false there was an error calling the function, + if ReportErrors is true the errorstring is popped from stack + and written to error.log otherwise it is left on stack} +function TLuaPlugin.CallFunctionByName(Name: String; const nArgs: Integer; const nResults: Integer; const ReportErrors: Boolean): Boolean; +begin + Result := false; + if not bPaused then + begin + // we need at least one stack slot free + lua_checkstack(State, 1); + + // lua_getglobal(State, PChar(Name)); //this is just a macro: + lua_getfield(State, LUA_GLOBALSINDEX, PChar(Name)); + + If (lua_isfunction(State, -1)) then + begin //we got a function + // move function in front of the arguments (if any) + if (nArgs > 0) then + lua_insert(State, -(nArgs + 1)); + + // call it! + if (lua_pcall(State, nArgs, nResults, 0) = 0) then + Result := true //called w/o errors + else //increase error counter + Inc (ErrorCount); + end + else + begin //we have to pop the args and the field we pushed from stack + lua_pop(State, nArgs + 1); + //leave an errormessage on stack + lua_pushstring(State, Pchar('could not find function named ' + Name)); + end; + end + else + begin //we have to pop the args from stack + lua_pop(State, nArgs); + //leave an errormessage on stack + lua_pushstring(State, PChar('plugin paused')); + end; + + if (not Result) AND (ReportErrors) then + Log.LogError(lua_toString(State, -1), 'lua/' + sName); +end; + +{ removes all values from stack } +procedure TLuaPlugin.ClearStack; +begin + if (lua_gettop(State) > 0) then + lua_pop(State, lua_gettop(State)); +end; + +{ Destroys the Luastate, and frees as much mem as possible, + w/o destroying the class and important information } +procedure TLuaPlugin.Unload; +begin + if (State <> nil) then + begin + if (Status in [psRunning, psErrorOnRun]) then + CallFunctionByName('plugin_unload', 1, 0, False); + + ClearStack; + lua_close(State); + State := nil; //don't forget to nil it ;) + + if (sStatus = psRunning) then + sStatus := psClosed; + end; +end; + +function TLuaPlugin_Register (L: Plua_State): Integer; cdecl; + var + Id: Integer; + P: TLuaPlugin; + Name, Version, Author, Url: String; +begin + if (lua_gettop(L) >= 2) then + begin // we got at least name and version + if (not lua_isNumber(L, lua_upvalueindex(1))) then + luaL_Error(L, PChar('upvalue missing')); + + if (not lua_isString(L, 1)) then + luaL_ArgError(L, 1, 'string expected'); + + if (not lua_isString(L, 2)) then + luaL_ArgError(L, 1, 'string expected'); + + Id := lua_ToInteger(L, lua_upvalueindex(1)); + + //get version and name + Name := lua_tostring(L, 1); + Version := lua_tostring(L, 2); + + //get optional parameters + if (lua_isString(L, 3)) then //author + Author := lua_toString(L, 3) + else + begin + Author := 'unknown'; + end; + + // homepage + if (lua_isString(L, 4)) then + Url := lua_toString(L, 4) + else + begin + Url := ''; + end; + + //clear stack + if (lua_gettop(L) > 0) then + lua_pop(L, lua_gettop(L)); + + //call register + P := LuaCore.GetPluginById(Id); + if (P <> nil) then + P.Register(Name, Version, Author, Url) + Else + luaL_error(L, PChar('wrong id in upstream')); + + // remove function from global register + lua_pushnil(L); + lua_setglobal(L, PChar('register')); + + // return true + Result := 1; + lua_pushboolean(L, True); + end + else + luaL_error(L, PChar('not enough arguments, at least 2 expected. in TLuaPlugin_Register')); +end; + +{ custom lua panic function + it writes error string to error.log and raises an ELuaException + that may be caught } +function TLua_CustomPanic (L: Plua_State): Integer; cdecl; + var + Msg: String; +begin + if (lua_isString(L, -1)) then + Msg := lua_toString(L, -1) + else + Msg := 'undefined lua panic'; + + Log.LogError(Msg, 'lua'); + + raise ELuaException.Create(Msg);; + + Result := 0; +end; + + + +end. \ No newline at end of file diff --git a/Lua/src/ultrastardx.dpr b/Lua/src/ultrastardx.dpr index c42d8215..296a330d 100644 --- a/Lua/src/ultrastardx.dpr +++ b/Lua/src/ultrastardx.dpr @@ -149,6 +149,8 @@ uses ULuaLog in 'lua\ULuaLog.pas', ULuaTextGL in 'lua\ULuaTextGL.pas', ULuaTexture in 'lua\ULuaTexture.pas', + UHookableEvent in 'lua\UHookableEvent.pas', + ULuaCore in 'lua\ULuaCore.pas', //------------------------------ //Includes - Menu System -- cgit v1.2.3