Passing a Table to Lua?

BlitzMax Forums/BlitzMax Programming/Passing a Table to Lua?

Gabriel(Posted 2007) [#1]
Is there a way to pass a table to Lua from BlitzMax? I can pass a Table from Lua to BlitzMax by returning the table from a function, calling that function from BMax and then pulling it off the stack with Lua_ToPointer. I've confirmed that this returns the correct point to the table.

However, passing it back, there is no Lua_PushPointer function ( at least not within the standard 5.12 Lua implementation. ) So I tried PushLightUserData since that is effectively your Byte Ptr-style swiss army knife of passing things around, but Lua does not seem to like this. My function ( which accepts a table as it's only parameter ) returns 1 instead of 0, and I can't seem to find an error code which corresponds with 1, so I'm not really sure what the error means.

Is there a better way to pass a table ( well pointer to a table ) to Lua from BMax?


Rozek(Posted 2007) [#2]
Gabriel,

from the Lua reference manual (describing lua_topointer):

Converts the value at the given acceptable index to a generic C pointer (void*). The value may be a userdata, a table, a thread, or a function; otherwise, lua_topointer returns NULL. Different objects will give different pointers. There is no way to convert the pointer back to its original value.

Typically this function is used only for debug information.


I have not yet tried to (transparently) pass a (referenced) object to BlitzMax - reason: beware of Lua's internal garbage collection!

Lua is free to remove any memory object whenever it likes to do so - and it will if there is no longer any (Lua) reference to that object. As a consequence, your BlitzMAX pointer to such an object may become invalid at any time, pointing to garbage afterwards.

This is why actually only values are exchanged via the Lua stack - and these are always copied (trivial for numbers, but the Lua API also automatically copies every string - which is important to know as soon as the strings grow...) The only exceptions from this rule are "static" items kept outside Lua (such as BlitzMax functions, Lua states, etc.)

I don't exactly know what you plan to do when thinking of passing a table to/from Lua - perhaps, a C closure might be what you want (although I've never tried that myself yet) or you may analyse the table and reconstruct it, or marshal/demarshal in any way.

In principle, you have two possiblities: converting your table into a value and copying it between Lua and BlitzMAX or creating a fixed reference to your table (within Lua!) and passing an accessor (e.g. a name or an index into another table) between Lua and BlitzMAX - using a Lua closure, you could even avoid globals for this task!

Enough for now - family has to be woken up!

Good success


Gabriel(Posted 2007) [#3]
Thanks Andreas, I never spotted that note in lua_topointer. What I was trying to do was have an object in Lua which mirrors the object I have in BlitzMax so that I can work with my BlitzMax game objects in either Lua or BlitzMax and each one knows about the other. I can only assume I'm going about this the wrong way, since you're writing OOP interfaces to many of BMax's inbuilt modules and you're evidently not doing this. So I'll give it a rethink. Thanks for your help.


Rozek(Posted 2007) [#4]
Gabriel,

my interface uses the "standard" Lua approach:

On the BlitzMAX side:
- I have "normal" BlitzMAX objects. In order to guarantee that these objects "survive" as long as the Lua side (see below) has a reference to them, I create a "handle" for every such object (this prevents the BlitzMAX garbage collection from removing the BlitzMAX objects even when they are no longer actively referenced from within BlitzMAX)
- BlitzMAX object handles are passed to/from Lua as "light userdata"
- there are BlitzMAX functions dealing with these objects (the first argument is a reference to such an object) which are registered within Lua and may be called from the Lua VM

On the Lua side:
- I have simple tables representing a Lua "object", with an associated metatable describing its behaviour (and additional functionality)
- these tables have a field "_peer" containing the "userdata" which actually references the BlitzMAX object

Whenever Lua wants to operate on a BlitzMAX object, it calls a BlitzMAX function (which has been registered within the Lua VM before) passing the "_peer" as its first object.

Using "light userdata" and "external" functions registered within the VM, objects of the runtime environment are completely transparent for Lua scripts. All operations are actually performed "outside" Lua and only triggered by the VM.

Note: it is also possible to associate userdata directly with a metatable, but I never tried that myself. I prefer the "_peer"-based approach...

But beware of garbage collection/memory management on either side! Not taking the life cycle of an object into account may cause problems which are extremely difficult to locate!

Good success!


Rozek(Posted 2007) [#5]
Another note:

you may do without "handles" if you can guarantee, that a BlitzMAX object remains referenced from within BlitzMAX as long as the Lua side has a "userdata" reference to it.

Personally, I don't believe such guarantees and prefer my "handle"-based approach (leaving it up to the Lua garbage collector to decide when a Lua object is no longer needed (look out for the "__gc" mettable event) and then calling a BlitzMAX function, which explicitly destroys the BlitzMAX handle)


taxlerendiosk(Posted 2007) [#6]
(look out for the "__gc" mettable event)

Though this only works for full userdata, not light userdata or tables.


Gabriel(Posted 2007) [#7]
Andreas, thank you for the detailed explanation. That all makes perfect sense, but I'm still missing one thing, which I think may be a conceptual misunderstanding on my part.

Let's suppose that all my BlitzMax objects have a method called Update() which does nothing but run whatever lua chunk has been associated with the object in question.

So I call the Lua Function ( let's assume I have various different update scripts and this one is called Update10 ) and pass the BMax object handle as light userdata. Now Lua has the BMax object handle, but it doesn't have a Lua object ( in this case table ) so I don't know where it gets the object from to start calling the "methods" you can fake with metatables. Do you keep a table which contains BMax object handles as the key and Lua "objects" as the value and retrieve the right object each time? It's just this concept of getting the matching lua object to the BlitzMax object handle that's got me stumped at the moment.

Again, thanks for all the help.


Rozek(Posted 2007) [#8]
Gabriel,

exactly that way: I have a "PeerTable", which uses the "light userdata" as its "key" and the corresponding Lua table as its "value".

Just don't forget to add every newly created object and remove it again when it gets garbage collected - and make the "PeerTable" weak, or you will never get rid of your objects!


Gabriel(Posted 2007) [#9]
Thanks Andreas. I guess you mean the table should have weak keys, rather than weak values?


Rozek(Posted 2007) [#10]
Gabriel,

no - just the other way round: the "values" are your Lua tables, if you make them "strong" they will never get collected. On the other hand, the Lua table always also has a (strong) reference to the "light userdata" (_Peer). For that reason, the "key" in the "Peertable" might be strong if you remove the entry yourself - if you can't guarantee that, make both "key" and "value" "weak".


Gabriel(Posted 2007) [#11]
Ah I see, thanks for the clarification.


taxlerendiosk(Posted 2007) [#12]
Light userdata is never collected - it's a pointer, a static value like a number. So, you can't really meaningfully set it "weak".


Gabriel(Posted 2007) [#13]
EDIT: Never mind, simple mistake on my part. It's working fine.

Thanks Andreas for all the assistance. I had a simple "class" in BMax which is matched in Lua now.