Actually included the rename which was supposed to happen in the last commit :)

Added RegisterFunctions method which takes an object and registers all compatible methods either globally or in a table
This commit is contained in:
Mark van Renswoude 2016-02-07 12:41:21 +00:00
parent 38866639be
commit cad2054373
4 changed files with 307 additions and 57 deletions

View File

@ -22,13 +22,14 @@
3. This notice may not be removed or altered from any source
distribution.
}
unit Lua.Wrapper;
unit Lua;
interface
uses
System.Classes,
System.Generics.Collections,
System.Generics.Defaults,
System.Rtti,
System.SysUtils,
Lua.API;
@ -223,14 +224,52 @@ type
TLuaCFunction = reference to procedure(Context: ILuaContext);
TLuaCMethod = procedure(Context: ILuaContext) of object;
TLuaPushFunction = reference to procedure(AFunction: TLuaCFunction);
TLuaRegisteredFunction = record
Name: string;
Callback: 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;
TLuaRegisteredFunctionDictionary = TDictionary<Integer, TLuaRegisteredFunction>;
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<string, TLuaCFunction>;
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<TCustomLuaRegistration>;
TLuaRegisteredFunctionDictionary = TDictionary<Integer, TLuaCFunction>;
TLuaScript = class(TObject)
@ -251,20 +290,21 @@ type
private
FState: lua_State;
FLoaded: Boolean;
FRegistrations: TLuaRegistrationList;
FRegisteredFunctions: TLuaRegisteredFunctionDictionary;
FRegisteredFunctionCookie: Integer;
FAutoOpenLibraries: TLuaLibraries;
FHasRun: Boolean;
function GetHasState: Boolean;
function GetState: lua_State;
function GetRegisteredFunctions: TLuaRegisteredFunctionDictionary;
FRttiContext: TRttiContext;
protected
function GetHasState: Boolean; virtual;
function GetState: lua_State; virtual;
function DoAlloc(APointer: Pointer; AOldSize, ANewSize: Cardinal): Pointer; virtual;
procedure DoNewState; virtual;
procedure DoClose; virtual;
procedure DoRegisterFunction(ACookie: Integer); virtual;
procedure DoRegistration(ARegistration: TCustomLuaRegistration); virtual;
procedure SetAutoOpenLibraries(const Value: TLuaLibraries); virtual;
protected
@ -272,12 +312,14 @@ type
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 RegisteredFunctions: TLuaRegisteredFunctionDictionary read GetRegisteredFunctions;
property Registrations: TLuaRegistrationList read FRegistrations;
property RegisteredFunctions: TLuaRegisteredFunctionDictionary read FRegisteredFunctions;
public
constructor Create;
destructor Destroy; override;
@ -290,6 +332,14 @@ type
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;
@ -339,7 +389,8 @@ type
implementation
uses
System.Math,
System.SyncObjs;
System.SyncObjs,
System.TypInfo;
type
@ -1039,6 +1090,91 @@ begin
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<string, TLuaCFunction>;
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
@ -1139,12 +1275,15 @@ 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;
@ -1353,15 +1492,15 @@ end;
procedure TLua.AfterLoad;
var
cookie: Integer;
registration: TCustomLuaRegistration;
begin
Loaded := True;
HasRun := False;
{ Register functions in the current environment }
for cookie in RegisteredFunctions.Keys do
DoRegisterFunction(cookie);
for registration in Registrations do
DoRegistration(registration);
end;
@ -1398,21 +1537,60 @@ end;
procedure TLua.RegisterFunction(const AName: string; AFunction: TLuaCFunction);
var
registeredFunction: TLuaRegisteredFunction;
cookie: Integer;
begin
{ Since anonymous methods are basically interfaces, we need to keep a reference around }
registeredFunction.Name := AName;
registeredFunction.Callback := AFunction;
AddRegistration(TLuaFunctionRegistration.Create(AName, AFunction));
end;
cookie := GetRegisteredFunctionCookie;
RegisteredFunctions.Add(cookie, registeredFunction);
{ Only register functions after Load, otherwise they'll not be available in the environment }
if Loaded then
DoRegisterFunction(cookie);
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<System.Rtti.TRttiParameter>;
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;
@ -1439,21 +1617,30 @@ begin
end;
procedure TLua.DoRegisterFunction(ACookie: Integer);
var
name: PAnsiChar;
procedure TLua.DoRegistration(ARegistration: TCustomLuaRegistration);
begin
name := TLuaHelpers.AllocLuaString(RegisteredFunctions[ACookie].Name);
try
lua_pushlightuserdata(State, Self);
lua_pushinteger(State, ACookie);
ARegistration.Apply(State,
procedure(AFunction: TLuaCFunction)
var
cookie: Integer;
lua_pushcclosure(State, @LuaWrapperFunction, 2);
lua_setglobal(State, name);
finally
TLuaHelpers.FreeLuaString(name);
end;
begin
cookie := GetRegisteredFunctionCookie;
RegisteredFunctions.Add(cookie, AFunction);
lua_pushlightuserdata(State, Self);
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;
@ -1475,7 +1662,7 @@ begin
begin
context := TLuaContext.Create(State);
RegisteredFunctions[ACookie].Callback(context);
RegisteredFunctions[ACookie](context);
Result := context.Result.Count;
end;
end;
@ -1494,15 +1681,6 @@ begin
end;
function TLua.GetRegisteredFunctions: TLuaRegisteredFunctionDictionary;
begin
if not Assigned(FRegisteredFunctions) then
FRegisteredFunctions := TLuaRegisteredFunctionDictionary.Create;
Result := FRegisteredFunctions;
end;
procedure TLua.SetAutoOpenLibraries(const Value: TLuaLibraries);
begin
FAutoOpenLibraries := Value;
@ -2264,5 +2442,4 @@ begin
Result := TLuaHelpers.CallFunction(State, AParameters);
end;
end.

View File

@ -4,8 +4,8 @@ uses
Forms,
DUnitTestRunner,
TestAPI in 'source\TestAPI.pas',
Lua.API in '..\Lua.API.pas',
Lua in '..\Lua.pas',
Lua.Wrapper in '..\Lua.Wrapper.pas',
TestWrapper in 'source\TestWrapper.pas';
{$R *.RES}

View File

@ -87,8 +87,8 @@
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="source\TestAPI.pas"/>
<DCCReference Include="..\Lua.API.pas"/>
<DCCReference Include="..\Lua.pas"/>
<DCCReference Include="..\Lua.Wrapper.pas"/>
<DCCReference Include="source\TestWrapper.pas"/>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>

View File

@ -18,6 +18,8 @@ type
procedure SetUp; override;
procedure TearDown; override;
procedure Print(AContext: ILuaContext);
property Lua: TLua read FLua;
property Printed: TStringBuilder read FPrinted;
published
@ -48,6 +50,9 @@ type
procedure ByteCode;
procedure Capture;
procedure DenyRequire;
procedure RegisterObject;
procedure RegisterObjectTable;
end;
@ -60,6 +65,17 @@ type
TProtectedLua = class(TLua);
TTestObject = class(TPersistent)
private
FOutput: TStringBuilder;
public
constructor Create(AOutput: TStringBuilder);
published
procedure Method1(AContext: ILuaContext);
procedure Method2(AContext: ILuaContext);
end;
{ TTestWrapper }
procedure TTestWrapper.SetUp;
begin
@ -69,11 +85,7 @@ begin
FLua := TLua.Create;
FLua.AutoOpenLibraries := [StringLib];
FLua.RegisterFunction('print',
procedure(AContext: ILuaContext)
begin
FPrinted.Append(AContext.Parameters.ToString);
end);
FLua.RegisterFunction('print', Print);
end;
@ -86,6 +98,12 @@ begin
end;
procedure TTestWrapper.Print(AContext: ILuaContext);
begin
FPrinted.Append(AContext.Parameters.ToString);
end;
procedure TTestWrapper.NewState;
begin
TProtectedLua(Lua).CheckState;
@ -424,6 +442,61 @@ begin
end;
procedure TTestWrapper.RegisterObject;
var
testObject: TTestObject;
begin
testObject := TTestObject.Create(FPrinted);
try
Lua.RegisterFunctions(testObject);
Lua.LoadFromString('Method1("Hello")'#13#10 +
'Method2("world!")');
CheckEquals('Method1:HelloMethod2:world!', Printed.ToString);
finally
FreeAndNil(testObject);
end;
end;
procedure TTestWrapper.RegisterObjectTable;
var
testObject: TTestObject;
begin
testObject := TTestObject.Create(FPrinted);
try
Lua.RegisterFunctions(testObject, 'Test');
Lua.LoadFromString('Test.Method1("Hello")'#13#10 +
'Test.Method2("world!")');
CheckEquals('Method1:HelloMethod2:world!', Printed.ToString);
finally
FreeAndNil(testObject);
end;
end;
{ TTestObject }
constructor TTestObject.Create(AOutput: TStringBuilder);
begin
inherited Create;
FOutput := AOutput;
end;
procedure TTestObject.Method1(AContext: ILuaContext);
begin
FOutput.Append('Method1:' + AContext.Parameters[0].AsString);
end;
procedure TTestObject.Method2(AContext: ILuaContext);
begin
FOutput.Append('Method2:' + AContext.Parameters[0].AsString);
end;
initialization
RegisterTest(TTestWrapper.Suite);