pub.lua example?

BlitzMax Forums/BlitzMax Programming/pub.lua example?

JoshK(Posted 2009) [#1]
Has anyone got a simple example using pub.lua? What advantages and disadvantages does Lua have compared to BVM?


GW(Posted 2009) [#2]
It doesn't have any advantages that i know of, except the developers don't disappear for months at a time.


skidracer(Posted 2009) [#3]
try help->modules->System->lua scripting (docs for brl.maxlua)


JoshK(Posted 2009) [#4]
Wow, that is pretty cool.


JoshK(Posted 2009) [#5]
This is amazing. All the object methods and fields just automatically work.

What about returning an object to Lua? What if I wanted to do something like this?:

function main()
e=CreatePivot()
e.setposition(0,1,2)
end


DreamLoader(Posted 2009) [#6]
use lightuserdata/userdata to pass value
you can use handletoobject /handlefromobject to pass the object handle


JoshK(Posted 2009) [#7]
Here is my BMX app:
Strict

Type TVec3
	Field x:Float
	Field y:Float
	Field z:Float
EndType

Type TFoo
	
	Field position:TVec3=New TVec3
	
	Method SetPosition(x#,y#,z#)
		position.x=x
		position.y=y
		position.z=z
	EndMethod
	
EndType

Local o:TFoo
Local source:String
Local instance:TLuaObject
Local class:TLuaClass

o=New TFoo
LuaRegisterObject o,"myobject"

source=LoadString("myprogram.lua")
class=TLuaClass.Create(source)
instance=TLuaObject.Create(class,Null)
instance.Invoke(Null,Null)


And here is my Lua script:
myobject.setposition(1,2,3)
print(myobject.position.x)


This is really cool, but there is a whole missing functionality to create objects. How is this acheived?


N(Posted 2009) [#8]
This is really cool, but there is a whole missing functionality to create objects. How is this acheived?
I'm not sure if you can do that with MaxLua, since it doesn't seem to have any public methods to push/retrieve objects to/from the stack or otherwise really manipulate anything other than global objects. So far as I can see, it doesn't really deal with actually working with objects so much as it's a way to create singletons in Lua using BMax objects/methods.

An alternative is the code I wrote to wrap classes and such, but it requires you either add metadata to your types or manually expose them to the Lua VM using lua_implementtype. Can find the code for it at http://github.com/nilium/bmax-misc/tree/master under lua-reflection/


JoshK(Posted 2009) [#9]
Looks pretty complicated.


slenkar(Posted 2009) [#10]
you mean you want LUA to create a blitz object?


JoshK(Posted 2009) [#11]
I want Lua to call a Blitz function that creates a Blitz object, returns it, and allows Lua to do stuff to it.

This would be EXTREMELY powerful if it can be made to work right, even if there are remaining caveats.


slenkar(Posted 2009) [#12]
OK you create the function e.g. function createcar()

you return an integer handle of the object

e.g.
function createcar(luastate byte ptr)
c:car=new car
returnint=handlefromobject(c)

lua_pushint(luastate,returnint)
return 1
endfunction

then you have to create set and get functions for car,

function set_car_x(luastate byte ptr)
local x=lual_checkint(luastate,1)
local c:car=car(handletoobject(lual_checkint(luastate,2)))
c.x=x
return 0
endfunction


JoshK(Posted 2009) [#13]
That kind of wrapper can be auto-generated once you figure out all the intricacies of it, but you lose the OO aspect. Can you pass objects so that you get the nice OO features?


N(Posted 2009) [#14]
Looks pretty complicated.
The internals are. How you use it isn't really that complicated.

Basically, your BMax code:


And the script to use it:
-- Same as Local vec:Vec3 = New Vec3.InitWithValues(5,10,15)
local vec = NewVec3():InitWithValues(5,10,15)
local entity = NewTEntity()

entity:SetPosition(vec)

vec = entity:Position()
-- access fields
vec.x = 0
vec.y = 25
vec.z = 50

entity:SetPosition(vec)


And, just as a note, you would probably want to remove MaxLua.mod from brl.mod, since by default it's going to create a Lua VM regardless of whether or not you want it to.


JoshK(Posted 2009) [#15]
Wow, this is amazing!!! Thanks!

What if I want to expose a procedural function like Notify() or Print()?
Strict

Import "lua-reflection.bmx"

' Example types

' Tell the lref code that you want this type exposed to scripts
Type TVec3 {expose}

	Field x#, y#, z#

	Method ToString:String()
		Return x+", "+y+", "+z
	EndMethod

EndType

' Tell lref you want this type exposed to the script API, but you don't want the scripts to be able to access the fields
Type TEntity {expose}
	
	Field position:TVec3 = New TVec3
	
	Method SetPosition(position:TVec3)
		MemCopy Self.position,position,12
	EndMethod

	Method PrintPosition()
		Print position.ToString()
	EndMethod
	
EndType


' Main code
Print "Creating VM"
Local vm@ Ptr = luaL_newstate()

Print "Implementing types"
lua_implementtypes(vm)

Print "Running script"
If luaL_dofile(vm, "test.lua")
	Local err:String = "Error: "+lua_tostring(vm, -1)
	lua_close(vm)
	RuntimeError(err)
EndIf

Print "Done"

Print "Closing VM"
lua_close(vm)


local position=NewTVec3()

position.x=5
position.y=10
position.z=15

local entity = NewTEntity()

entity.SetPosition(position)

entity.PrintPosition()



N(Posted 2009) [#16]
What if I want to expose a procedural function like Notify() or Print()?
There are two ways to do that. First off, you can do it the straight Lua way and just write a glue function. I won't go into how that's done since it'd be easier to read the Lua manual for that, and you'd probably understand it better as a result (it's not really complicated). Secondly, using lref, you can do this:
' static = global object
' noclass = hide the global object and make the methods global
Type TCommonMethods {expose static noclass}
	' Rename the method to 'Print'
	Method _Print(s$) {rename="Print"}
		Print(s)
	End Method
	
	' Same case
	Method _Notify(s$) {rename="Notify"}
		Notify(s)
	End Method
End Type
Then you just call the functions like usual in Lua.

Note: renaming doesn't work on types. (It could, but I never actually thought to do it.)

Note 2: If brl.Reflection ever gets proper support for iterating over functions (which it's been shown it can do that, so it's mostly a matter of whether or not it's made official), then I'll probably have some code to automate this without wrapper objects as well.


JoshK(Posted 2009) [#17]
I found the "regular" way and it is definitely not appealing.

Your common methods class makes it very easy.

This is one of the coolest things I have ever come across. Thanks for laying all this out for everyone to use!


N(Posted 2009) [#18]
With your CommonMethods class, I can just call Print() in Lua? No object is needed?
You just call Print("Wimbleton") like normal. You don't need to create an object or anything for that (without getting into the technical aspect of how and why this works, anyway).

It's worth adding that this does use more memory than it would were you to use an example like Jeremy's, and it's always going to be slower than the normal way of doing this, since all of the calls and such are handled at runtime and it's going through the brl.Reflection module (which has a few safety checks to ensure you're not doing something incorrectly). So there is a trade-off between ease of use and speed/memory use.


JoshK(Posted 2009) [#19]
Any idea why this does not work? Calling position.Tostring() is fine, but entity.position.tostring() results in an error:
local position=NewTVec3()
position.x=5
position.y=10
position.z=15
Print(position.ToString())

local entity = NewTEntity()
entity.SetPosition(position)
Print(entity.position.ToString())

Notify("OK!")


Executing:lua2.exe
5.00000000, 10.0000000, 15.0000000
Releasing object
Releasing object
Releasing object
Releasing object
Error: test.lua:9: attempt to call field 'ToString' (a nil value)


N(Posted 2009) [#20]
Well, that's a bug. I think the changes should be in the repo now. It's kind of hard pushing changes under Windows. If they aren't in there yet, then I'll have to reboot into Mac OS.


JoshK(Posted 2009) [#21]
:D

Incredible.

It works. I can do all sorts of things:

local position=vec3(1,2,3)
position.x=5
position.y=10
position.z=15
Print(position.ToString())

local p=position.Normalize()
Print(p.ToString())

local entity = CreateEntity()--NewTEntity()
entity.SetPosition(position)
entity.position.Normalize()
--Print(entity.position.ToString())

Notify("OK!")


JoshK(Posted 2009) [#22]
I found a problem when an extending type is used:

Strict

Import "lua-reflection.bmx"

' Tell the lref code that you want this type exposed to scripts
Type TVec3 {expose}
	
	Field x#, y#, z#
	
	Method ToString:String()
		Return x+", "+y+", "+z
	EndMethod
	
	Method Normalize:TVec3()
		Local m:Float
		Local t:TVec3=New TVec3
		m=Sqr(x*x+y*y+z*z)
		t.x=x/m
		t.y=y/m
		t.z=z/m
		Return t
	EndMethod
	
EndType

' Tell lref you want this type exposed to the script API, but you don't want the scripts to be able to access the fields
Type TEntity {expose}
	
	Field position:TVec3=New TVec3
	
	Method SetPosition(position:TVec3)
		MemCopy Self.position,position,12
	EndMethod
	
EndType

Type TPivot Extends TEntity {expose}
EndType

' static = global object
' noclass = hide the global object and make the methods global
Type TCommonMethods {expose static noclass}
	
	Method _Print(text:String) {rename="Print"}
		Print(text)
	EndMethod
	
	Method _Notify(text:String) {rename="Notify"}
		Notify(text)
	EndMethod

	Method _CreateEntity:TEntity() {rename="CreateEntity"}
		Return New TEntity
	EndMethod
	
	Method _CreatePivot:TPivot() {rename="CreatePivot"}
		Return New TPivot
	EndMethod
	
	Method vec3:TVec3(x#=0,y#=0,z#=0)
		Local t:TVec3=New TVec3
		t.x=x
		t.y=y
		t.z=z
		Return t
	EndMethod
	
EndType

' Main code
Local vm@ Ptr = luaL_newstate()
lua_implementtypes(vm)
If luaL_dofile(vm, "test.lua")
	Local err:String = "Error: "+lua_tostring(vm, -1)
	lua_close(vm)
	RuntimeError(err)
EndIf
lua_close(vm)
End

test.lua:
local position=vec3(1,2,3)
position.x=5
position.y=10
position.z=15

--This works
local entity=CreateEntity()

--This does not
--local entity=CreatePivot()

entity.SetPosition(position)
Print(entity.position.x)

Notify("OK!")



N(Posted 2009) [#23]
And that is a bug resulting from a bad string. I'm going to make a few changes, I'll reply again when I've pushed them to the repo.


N(Posted 2009) [#24]
Should be fixed now, and slightly less prone to failing due to dumb errors like that.


JoshK(Posted 2009) [#25]
It's working great, with actual engine classes. :D

Let's talk about memory management. Say I do something like this:

entity.SetPosition(vec3(1,2,3))

The vec3() command creates and returns a new TVec3 object. When and how will this get cleaned up by Lua and BlitzMax? I see Lua has some kind of GC, but has this been confirmed to work with BMX?


N(Posted 2009) [#26]
Warning: This post is long-ish.

In terms of memory management, I'll try to explain this about as clearly as I can. To start off, Lua has its own garbage collector. Objects that are out of scope, unreferenced, and such are collected. It's pretty much the same end result as BMax, although the implementation details probably vary.

BMax objects are stored as userdata (blocks of memory) in tables. They're usually either 4 bytes or 8 bytes. Depends on what sizeof(BBObject*) returns, but it'll probably be four bytes unless you're running an x64 system. Either way, that's all accounted for. Brl.MaxLua works roughly the same way, since that's partly where I got the idea from as far as storing the object in a userdata goes (I could ramble about the other options I was looking at, but I won't). Kudos to Mark there.

The remainder of this only applies to applications that are not build with threading. How this all applies to threading, I'm not entirely sure, but I'm hoping that the threaded garbage collector BMax works fine in this scenario. There is probably a way to figure out when you're pushing an object that's already been pushed, but it would add a lot of unnecessary overhead and you'd only save eight bytes of memory tops. Given the surplus of memory these days, you're not really doing any great harm to have some duplicate userdata floating around.

Pushing a BMax object creates a userdata, increments the internal reference count for the object (refer to blitz_object.h and look at BBObject to see what I'm talking about), stores a pointer to the object in the userdata and attaches a metatable for garbage collection to the userdata. By default, all BMax objects are put inside of a table, and then methods for that object are also put in the table. Methods are also objects, so you can pass those around as objects if you want, since they all contain a reference to the userdata with the object, its type, TMethod, and so on. It's a nifty, unintended side-effect of how the methods are wrapped.

Multiple calls to push an object will create multiple userdata, so keep that in mind. If you find yourself pushing the same object multiple times in the same function, you should use lua_pushvalue instead to just push a reference to the object you've already pushed. Random note about efficiency.

Now, when Lua collects a userdata, it checks to see if the userdata has a metatable with an __gc function associated with it. All of the objects that lref deals with have that. So, Lua calls the gc function with the userdata as its only argument, and lref decrements the reference count on the object. Then, the rest is up to BMax's garbage collector and whether or not you handled the rest of the object's disposal properly (e.g., clearing any circular references). Again, this doesn't really apply to threaded builds.

I see Lua has some kind of GC, but has this been confirmed to work with BMX?
As I said, storing the object works pretty much the same way as brl.MaxLua. Lua's GC works, and BMax and Lua's GCs don't interact with each other. Lua isn't going to start trying to collect memory that it didn't allocate itself, because that would be very difficult to do already. I know this works with the non-threaded garbage collector, but as far as the threaded GC goes, I'm not sure. I think it should work, but I'm not familiar with the Hans Boehme GC and I'd really have to spend a ton of time studying it to figure it out, and I really do not have the mental capacity for that.

Worst case scenario is that you find out it doesn't work, notify me, and I go looking around to see how to make it work. I can think of several ways to work around any potential problems, but I'm not going to implement them unless I know there's a problem, since they'd all add a bit more overhead to managing the collection of objects.

Is it possible to rename type names as well as methods? NewVec3() would be nicer than NewTVec3(), IMO.
I mentioned this above, but not currently. It's possible and relatively easy, but I've really had enough of editing code under Windows, so it'll probably have to wait 'til tomorrow after I'm done playing Left 4 Dead. If you feel like trying to do it yourself, the best place to start is lua_implementtype(..), around lines 799, 805, and in LREF_ConstructBMaxObject at line 431. The only thing I'd ask is you make the changes public, but it's not a requirement since I'm not going to be a source Nazi like the GNU GPL folks over this.


Zeke(Posted 2009) [#27]
this is how i use maxlua:

Type TMytype
	Field name:String
	Field x:Int,y:Int
	
	Method Create:TMyType(name:String,x:Int,y:Int)
		Local t:TMytype=New tMytype
		t.name=name
		t.x=x
		t.y=y
		Return t
	End Method

	Method GetName:String()
		Return name
	End Method

End Type

LuaRegisterObject New TMytype,"mytype"

Local class:TLuaClass=TLuaClass.Create(LoadString("lua.lua"))
Local instance:TLuaObject=TLuaObject.Create(class,Null)

instance.invoke Null,Null


and luascript:
--lua test
luatype=mytype.create("newtype",15,20)

print("mytype name is "..luatype.getname())
print("mytype.x="..luatype.x)
luatype.x=luatype.x*2
print("mytype.x="..luatype.x)



JoshK(Posted 2009) [#28]
Thanks, Nilium. Regarding the renaming of types, don't worry about it, because Lua doesn't even ever need to know the actual type name, and I don't think I will be using the New() function for anything.

Zeke, that's very nice, but that was the first thing I tried in this thread and it barely allows any functionality.


JoshK(Posted 2009) [#29]
How can I invoke a function contained in a script? Something like "Lua_DoFunction(string,"myfunction")

Here's the docs:
http://www.lua.org/manual/5.0/manual.html#3.14

What I need is a lua object that can be called and recalled, instead of just evaluating a file or string. How do you create a "compiled" lua script object that isn't getting parsed every time it is invoked? I think I need to use lua_load and lua_pcall.


JoshK(Posted 2009) [#30]
I figured out how to call a function. You just first run the script, then you can call functions after that. You can push a value to pass to the function. How do I push a BlitzMax object?:
lua_getfield(vm,LUA_GLOBALSINDEX,"update")
lua_pushstring(vm,"test")
lua_call(vm,1,0)

It seems that pushing an object is a lot harder. This page claims to have the source code that tolua_pushobject() is performing. I tried converting it to BlitzMax but couldn't do the whole thing:
http://www.ogre3d.org/forums/viewtopic.php?f=10&t=43133

This sample script shows what I want this for:
function update( entity )
--do stuff
end

function MessageReceive( entity, name, value )
--do stuff
end

function Collision( entity, x, y, z )
--do stuff
end



N(Posted 2009) [#31]
How do I push a BlitzMax object?
lua_pushbmaxobject, oddly enough. There are little Rem:doc blocks in the code for documentation of functions and such as well. Those would be recommended reading.


JoshK(Posted 2009) [#32]
Ah. I did not realize you actually thought all this out ahead of time. Fantastic!


spacerat(Posted 2009) [#33]
You know, I recently made a module quite similar to Maxlua before I knew it existed, called luamax. It's currently early beta but it lets you do things like this:
Ball = {
   x=10
   y=30
}

function ball:Update()
   self.x=self.x+10
end

New(Ball)


http://www.spacerat.meteornet.net/downloads/luamax.mod.zip

See what you think.


JoshK(Posted 2009) [#34]
This demonstrates procedural Lua functions:
SuperStrict

Local source:String
source="n=myfunc(1,2)~n"
source:+"myfunc(n,4)~n"
'source:+"nonexistentfunction()~n"'uncomment this for error

Local L:Byte Ptr
L=luaL_newstate()
luaL_openlibs(L)

lua_register(L, "myfunc", myfunc)
If luaL_dostring(L,source)
	Print "Error: "+lua_tostring(L,-1)
Else
	Print "Everything is fine."
EndIf
lua_close(L)

Function myfunc:Int(L:Byte Ptr)
	Local x:Int = luaL_checkinteger(L, 1)
	Local y:Int = luaL_checkinteger(L, 2)
	Print x+","+y
	lua_pushnumber(L,3)
	Return 1
EndFunction