{* 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$
* $Id$
*}
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
Func: String; //< Name of the global that holds the function
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(L: PLua_State): 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
CallinProcess: boolean; //< true if a chain call is in process, to prepare unhooking during calls
HooksToRemove: array of PHook; // hooks to delete after chaincall
procedure RemoveWaitingHooks;
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; Func: 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; override;
end;
{ the default function for THookableEvent.PrepareStack it don't pass any arguments }
function PrepareStack_Dummy(L: PLua_State): 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; Func: String);
var
Item: PHook;
P: TLuaPlugin;
begin
P := LuaCore.GetPluginById(Parent);
if (P <> nil) then
begin
// get mem and fill it w/ data
New(Item);
Item.Handle := NextHookHandle;
Inc(NextHookHandle);
Item.Parent := Parent;
Item.Func := Func;
// 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;
end;
{ removes hooks in HookstoRemove array from chain }
procedure THookableEvent.RemoveWaitingHooks;
function IsInArray(Cur: PHook): boolean;
var I: Integer;
begin
Result := false;
for I := 0 to high(HooksToRemove) do
if (HooksToRemove[I] = Cur) then
begin
Result := true;
Break;
end;
end;
var
Cur, Prev: PHook;
begin
Prev := nil;
Cur := LastHook;
while (Cur <> nil) do
begin
if (IsInArray(Cur)) then
begin //we found the hook
if (prev <> nil) then
Prev.Next := Cur.Next
else //last hook found
LastHook := Cur.Next;
//free hooks memory
Dispose(Cur);
if (prev <> nil) then
Cur := Prev.Next
else
Cur := LastHook;
end
else
begin
Prev := Cur;
Cur := Prev.Next;
end;
end;
SetLength(HooksToRemove, 0);
end;
{ unhook by plugin. push true or error string to lua stack }
procedure THookableEvent.UnHook(L: Plua_State; hHook: Integer);
var
Cur, Prev: PHook;
Len: integer;
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
if not CallinProcess then
begin // => remove it
if (prev <> nil) then
Prev.Next := Cur.Next
else //last hook found
LastHook := Cur.Next;
//free hooks memory
Dispose(Cur);
end
else
begin // add to list of hooks to remove
Len := Length(HooksToRemove);
SetLength(HooksToRemove, Len + 1);
HooksToRemove[Len] := Cur;
end;
//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);
if (Prev <> nil) then
Cur := Prev.Next
else
Cur := LastHook;
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
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;
P: TLuaPlugin;
begin
Result := nil;
CallinProcess := true;
Cur := LastHook;
While (Cur <> nil) do
begin
P := LuaCore.GetPluginById(Cur.Parent);
lua_pushboolean(P.LuaState, Breakable);
if (P.CallFunctionByName(Cur.Func, 1 + PrepareStack(P.LuaState), LUA_MULTRET))
and Breakable
and (lua_gettop(P.LuaState) > 0) then
begin //Chain Broken
Result := P.LuaState;
Break;
end;
Cur := Cur.Next;
end;
RemoveWaitingHooks;
CallinProcess := false;
end;
{ the default function for THookableEvent.PrepareStack it don't pass any arguments }
function PrepareStack_Dummy(L: PLua_State): 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;
var
Name: string;
Event: THookableEvent;
hHook: integer;
begin
Result := 0;
if not lua_isTable(L, 1) then
LuaL_Error(L, 'Can''t find hook table in LuaHook_Unhook. Please call Unhook with method seperator (colon) instead of a point.');
// get event name
Lua_GetField(L, 1, 'Event');
if not lua_isString(L, -1) then
LuaL_Error(L, 'Can''t get event name in LuaHook_Unhook');
Name := Lua_ToString(L, -1);
// get event by name
Event := LuaCore.GetEventbyName(Name);
// free stack slots
Lua_pop(L, Lua_GetTop(L));
if (Event = nil) then
LuaL_Error(L, PAnsiChar('event ' + Name + ' does not exist (anymore?) in LuaHook_Unhook'));
// get the hookid
hHook := lua_ToInteger(L, lua_upvalueindex(1));
Event.UnHook(L, hHook);
end;
end.