PDA

View Full Version : Lua Scripting...



M109uk
19-08-2007, 05:52 PM
Hi,

Im having a few issues with Lua at the moment,

Firstly:
My GUI controls are from the TLuaObject class, however when i call CallEvent my application crashes without a single warning, i have managed to trace it down to the following:


function TLuaObject.CallEvent(EventName : AnsiString; args: array of Variant; Results: PVariantArray) : Integer;
var
NArgs,
NResults,
i :Integer;
idx :Integer;
Res : Integer;
begin
if not EventExists(EventName) then
exit;
lua_rawgeti (L, LUA_REGISTRYINDEX, FLuaReference); // Place our object on the stack
idx := lua_gettop(L);
lua_pushliteral(L, PChar(EventName)); // Place the event name on the stack

>>---- HERE ----<<lua_gettable>>---- ----<<

//Put self on the stack
lua_rawgeti&#40;L, LUA_REGISTRYINDEX, FLuaReference&#41;;


I dont get an accessviolation or any kind of error, it just closes my application.



Secondly:
i would like to add a function into the script called 'GetControl' which will look like:


function GetControl&#40;Name&#58; AnsiString&#41;&#58; TXGUIBaseControl;


I have registered the TXGUIBaseControl as an Object in Lua but i have no idea where to go from here, any ideas?

Many thanks

jdarling
21-08-2007, 01:00 PM
Sorry it took so long to get back M109uk. Can you post up a simple sample showing the problem further? I'm guessing that you are instantiating the reference to the object from within Delphi/FPC and not creating it within a Lua Script. If this is the case you have to register the object instance with Lua as well. This is one of those problems that I ran into while building out the TLuaObject and am busy fixing in the next version.

If you are creating the object in your source code (not in the Lua Script) it should look something as follows:procedure InitObject;
var
MyObject : TMyLuaObject;
begin
lua_pushliteral(LuaInstance.LuaState, 'NameOfObject'); // Push the name to reference the object within Lua from
lua_newtable(LuaInstance.LuaState); // Create a table to register to
MyObject := TMyLuaObject.Create(LuaInstance.LuaState, nil); // Create the object
lua_settable(LuaInstance.LuaState, LUA_GLOBALSINDEX); // Register it back to globals so we can find it in Lua later
end;

If you haven't yet, you might also want to look at the "simple game engine" sample that I put up on the site. It shows using event callbacks at a very basic level.

Also, any interest in helping test the new version once its ready for a public test? I'm getting closer, and should have it ready in the next week or two.

M109uk
21-08-2007, 05:11 PM
No worries, it always better to get a late reply than not to get one at all :-p

That could be why then, i have the class types registered in Lua but not the objects them selves.. i shall have a try now :-)
Although my objects are based on the TLuaObject class.. do i still need to add this?

Can i assume that i place the "InitObject" procedure into the constructor of the base object?

For example i have:
- TBaseControl
|- TControl
-|- TWindow
|- TButton

if i call this in the TBaseControl class will lua scripts be able to tell if the object is for example a TButton class, or will i need to initialize it in each class?

Also is there a way to rename the object after it has been initialized in lua?

Many thanks

jdarling
21-08-2007, 05:24 PM
I wouldn't actually put this into the constructor of the object itself. If you put it in the constructor then when you create an object within Lua you will get all sorts of strange errors. Instead, try using the method below to register your objects into the Lua environment once they have been created:procedure RegisterObjectInstance(L: Plua_State; aClassName, InstanceName: AnsiString; ObjectInstance : TLuaObject);
var
P, E : TLuaObject;
n, idx, idx2, mt : Integer;
begin
n := lua_gettop(L);
if lua_type(L, 1) <> LUA_TTABLE then
lua_remove(L, 1);
if n = 1 then
P := LuaToTLuaObject(L, 1)
else
P := nil;

lua_newtable(L);
E := ObjectInstance; //NewCallback(L, P);
idx := lua_gettop(L);

lua_pushliteral(L, '_Self');
lua_pushinteger(L, Integer(Pointer(E)));
lua_rawset(L, idx);

lua_newtable(L);
mt := lua_gettop(L);

lua_pushliteral(L, PChar(aClassName));
lua_gettable(L, LUA_GLOBALSINDEX);
idx2 := lua_gettop(L);

LuaCopyTable(L, idx2, idx, mt);
lua_setmetatable(L, idx);

lua_pop(L, 1);

lua_settable(L, LUA_GLOBALSINDEX);
end;
(That should already be in LuaObject.pas)

To use it would look as follows:begin
MyObject := TMyLuaObject.Create(L, nil);
RegisterObjectInstance(L, 'TMyLuaObject', 'MyObject', MyObject);
end.

Renaming something within Lua itself isn't a trivial task as Lua doesn't retain/rely upon the names internally and doesn't support "removing" a name from the global space. What you can do is to create the new name, set it equal to the value of the old name, then set the old names value equal to NIL.

The following code is completely untested, but should work :)procedure RenameLuaGlobal(L : PLua_State; FromName, ToName : AnsiString);
begin
lua_pushliteral(L, PChar(ToName)); // Push the new name
lua_pushliteral(L, PChar(FromName)); // Push the existing name
lua_gettable(L, LUA_GLOBALSINDEX); // Get the value and put it on the stack
lua_settable(L, LUA_GLOBALSINDEX); // Set the new name equal to the value
lua_pushliteral(L, PChar(FromName)); // Push the existing name
lua_pushnil(L); // Push a NIL onto the stack
lua_settable(L, LUA_GLOBALSINDEX); // Set the old name equal to NIL
end;

Note, that any "name" that doesn't have a value within Lua will return a NIL. This means that the above code is basically the same as renaming the variable.

M109uk
21-08-2007, 05:40 PM
i have placed the code with in my control managers class, which looks like:


function TXGUIControls.Add&#40;const Classname&#58; String&#41;&#58; Cardinal;
var
cItem&#58; CXGUIControl;
nItem&#58; TXGUIBaseControl;
Str&#58; String;
i&#58; Integer;
begin
Result &#58;= 0;

If &#40;fOwner <Nil> -1 Do
Begin
Inc&#40;i&#41;;
Str &#58;= Lowercase&#40;Classname+InttoStr&#40;i&#41;&#41;;
End;

&#123;$IFDEF X_SCRIPTS&#125;
nItem &#58;= cItem.Create&#40;fOwner, _XScript.LuaState, Nil&#41;; // Create the object
nItem.Name &#58;= Str;
RegisterObjectInstance&#40;_XScript.LuaState, nItem.ClassName, Str, nItem&#41;;
&#123;$ELSE&#125;
nItem &#58;= cItem.Create&#40;fOwner&#41;;
&#123;$ENDIF&#125;
Result &#58;= UniqueID;
nItem.fID &#58;= Result;

inherited Add&#40;nItem&#41;;
If &#40;nItem Is TXGUIWindow&#41; Then UpdateZ;
end;


but now i get an access violation from Lua5.1.dll, no doubt i done something dumb :-p

to be more specific, it crashes here:


procedure RegisterObjectInstance&#40;L&#58; Plua_State; aClassName, InstanceName&#58; AnsiString; ObjectInstance &#58; TLuaObject&#41;;
var
P, E &#58; TLuaObject;
n, idx, idx2, mt &#58; Integer;
begin
n &#58;= lua_gettop&#40;L&#41;;
if lua_type&#40;L, 1&#41; <> LUA_TTABLE then
lua_remove&#40;L, 1&#41;; <---------------------- HERE
if n = 1 then
P &#58;= LuaToTLuaObject&#40;L, 1&#41;
else
P &#58;= nil;

M109uk
21-08-2007, 06:50 PM
Ok i have managed to find why i was getting the access violation, i needed to load a script with 'ClassSupport.lua', how ever now i get to the following line:



if n = 1 then
P &#58;= LuaToTLuaObject&#40;L, 1&#41; <------------------ HERE
else
P &#58;= nil;


in the same function as above.

I dont get an error or anything, the whole application just closes with a single message.

jdarling
21-08-2007, 07:10 PM
Can you upload a simple sample showing the problem and the source? Its hard to put the pieces together on my end without a place to start. The other problem is that I'm looking at the version of pLua that is on my hard drive and I can guarantee that its different from what your working from :). It may be time to release a pre-candidate.

M109uk
21-08-2007, 07:55 PM
Ok no problem, i have created a quick and simple application.

can be downloaded here: http://www.pulse-soft.oneuk.com/LuaTest.zip

i have also included the dll and the lua units.

Many thanks

jdarling
22-08-2007, 01:18 PM
M109uk, please download the zip file from: http://www.eonclash.com/LUA/LuaTest.zip

NOTE: I had to convert your Delphi project to a FPC project. This means it won't compile in Delphi any more, so extract it to a new directory and copy/paste the changes in Unit1.pas to your existing Unit1.pas. Then copy the Lua support pas files into your project folder (LuaUtils.pas, LuaWrapper.pas, LuaObject.pas).

I updated LuaObject to contain a new constructor for creating a named object in code (don't know why I never did that before). Removed many writeln statements I had in place (just an oops on my part, I always write console apps it would seem). And Updated your source code quite a bit.

One thing to note, always WRAP your Execute and Execute* statements in Try Excepts. This is the only way you will ever see your error messages :).

Comments are in place about what was wrong, and I only found two real mistakes in your Delphi code. In one method you were returning a 1 when you didn't put anything on the stack. This causes a stack overflow and a kernal panic thus the app fails. The other was not wrapping your executes as that caused you not to see the exception that was being thrown about your Lua code being incorrect :). In Lua you had XObj:Click but you forgot the () after Click. Those arn't optional in Lua :).

The file mentioned above should compile and run just fine, in fact there is a compiled version in there. you might also consider looking at the serialize routine I put in your master lua file for future use :). Finally, you didn't need ClassSupport.lua since you were not creating child class types within Lua itself.

Hopefully this will all make more sense as the new version comes out, as I said, I learned a LOT before I started on the new version and many mistakes are being fixed. Any interest in helping with the testing :) ?

M109uk
22-08-2007, 01:52 PM
Thanks very much, it works great :-D

on a note, delphi gave errors regarding your new constructors for the TLuaObject class, the virtual needs to be after the overload directive.

haha figures i'd forget something like that, i have only had a quick look at lua scripts, i will have to look at the the documentation at somepoint.

Yeah it seems i have a very bad habit of not using the try and except statements :oops: a habit i should break!

a quick question, i would like to have a function in lua that i can place either a name or an ID and get the object as a result, i have the following code so far:


function xscGetControl&#40;L&#58; Plua_State&#41;&#58; Integer; cdecl;
var
Str&#58; AnsiString;
ID&#58; Cardinal;
Ctrl&#58; TXGUIBaseControl;
begin
Result &#58;= 0;
If lua_gettop&#40;L&#41; <> 1 Then
luaL_error&#40;L, 'XGUIGetControl expects 1 parameter &#40;control ID/name&#41; and returns GUI control.'&#41; Else
Begin
Str &#58;= LuaToString&#40;L, 1&#41;;
If isNumeric&#40;Str&#41; Then
Ctrl &#58;= _XGUI.GetControl&#40;StrToInt&#40;Str&#41;&#41; Else
Ctrl &#58;= _XGUI.GetControl&#40;Str&#41;;
PushTLuaObject&#40;L, Ctrl&#41;;
Result &#58;= 1;
End;
end;

procedure RegisterXGUIMethods;
begin
_XScript.RegisterLUAMethod&#40;'GetControl', @xscGetControl&#41;;
end;


Lua code:


X = GetControl&#40;"name"&#41;


EDIT:
No worries, i have managed to get the above code working now :)

Of course i will be more than happy with helping :)

M109uk
22-08-2007, 05:14 PM
Hmm well it seems im having some problems with the following script:

Pascal code:


function xscGetControl&#40;L&#58; Plua_State&#41;&#58; Integer; cdecl;
var
Str&#58; AnsiString;
ID&#58; Cardinal;
Ctrl&#58; TXGUIBaseControl;
begin
Result &#58;= 0;
If lua_gettop&#40;L&#41; <> 1 Then
luaL_error&#40;L, 'XGUIGetControl expects 1 parameter &#40;control ID/name&#41; and returns GUI control.'&#41; Else
Begin
Str &#58;= LuaToString&#40;L, 1&#41;;
If isNumeric&#40;Str&#41; Then
Ctrl &#58;= _XGUI.GetControl&#40;StrToInt&#40;Str&#41;&#41; Else
Ctrl &#58;= _XGUI.GetControl&#40;Str&#41;;

PushTLuaObject&#40;L, Ctrl&#41;;
Result &#58;= 1;
End;
end;

procedure RegisterXGUIMethods;
begin
_XScript.RegisterLUAMethod&#40;'GetControl', @xscGetControl&#41;;
end;


Lua code:


Log&#40;"Button#1 OnClick"&#41;
chkbox = GetControl&#40;"checkbox1"&#41;
if chkbox == nil then
Log&#40;"Failed to get checkbox1!"&#41;
else
if chkbox.Enabled == true then
chkbox.Enabled = false
else
chkbox.Enabled = true
end
end


it does'nt seem to be doing anything, on some occasions it has given me an access violation.

Out of curiosity do i have to tell lua what the class is of the resulting type?

jdarling
23-08-2007, 05:05 PM
M1, give this a try:function xscGetControl(L: Plua_State): Integer; cdecl;
var
Str: AnsiString;
ID: Cardinal;
Ctrl: TXGUIBaseControl;
begin
Result := 0;
If lua_gettop(L) <> 1 Then
luaL_error(L, 'XGUIGetControl expects 1 parameter (control ID/name) and returns GUI control.') Else
Begin
ctrl := nil;
if lua_isnumber(L, 1) then
ctrl := _XGUI.GetControl(lua_tointeger(L, 1))
else
ctrl := _XGUI.GetControl(AnsiString(lua_tostring(L, 1)));
if assigned(ctrl) then
ctrl.PushSelf
else
lua_pushnil(L));
Result := 1;
End;
end;

procedure RegisterXGUIMethods;
begin
_XScript.RegisterLUAMethod('GetControl', @xscGetControl);
end;

If that doesn't work, then look at your code in _XGUI.GetControl as you may have a problem there. Remember that calling ToString automatically changes the Lua Stack variable to a string type within Lua, so be very careful when using this call :). Also notice that I changed over to standard Lua methods, this is advised as some of the methods within the LuaUtils calls make calls to ToString you may not realize :). Again this is all being fixed in the next version :(.

M109uk
23-08-2007, 07:24 PM
thats great thanks :)
it works great now.. it was'nt to do with my function because i checked it and it was returning an object, but it just was'nt returning the object. but all is working great now thanks.

just waiting for the next problem lol