pub.lua example?
BlitzMax Forums/BlitzMax Programming/pub.lua example?
| ||
Has anyone got a simple example using pub.lua? What advantages and disadvantages does Lua have compared to BVM? |
| ||
It doesn't have any advantages that i know of, except the developers don't disappear for months at a time. |
| ||
try help->modules->System->lua scripting (docs for brl.maxlua) |
| ||
Wow, that is pretty cool. |
| ||
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 |
| ||
use lightuserdata/userdata to pass value you can use handletoobject /handlefromobject to pass the object handle |
| ||
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? |
| ||
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/ |
| ||
Looks pretty complicated. |
| ||
you mean you want LUA to create a blitz object? |
| ||
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. |
| ||
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 |
| ||
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? |
| ||
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. |
| ||
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() |
| ||
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 TypeThen 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. |
| ||
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! |
| ||
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. |
| ||
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) |
| ||
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. |
| ||
: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!") |
| ||
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!") |
| ||
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. |
| ||
Should be fixed now, and slightly less prone to failing due to dumb errors like that. |
| ||
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? |
| ||
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. |
| ||
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) |
| ||
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. |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
Ah. I did not realize you actually thought all this out ahead of time. Fantastic! |
| ||
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. |
| ||
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 |