{* 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
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;
{ 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);
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;
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;
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;
begin
//lua_upvalueindex
Result := 0;
end;
end.