-=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- (c) WidthPadding Industries 1987 0|576|0 -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=- -=+=-
SoCoder -> Article Home -> Advanced Techniques


 
Cower
Created : 15 November 2009
 
Language : Blitz Max

Pushing BMax objects onto the Lua stack

Covers pushing BlitzMax objects onto the Lua stack by boxing them in a finalizable userdata object

This tends to come up more than any other question when it comes to Lua. How do you push objects onto the stack? There's a bunch of different ways. I can recall several people getting by via pushing integer handles onto the stack, but there are some downsides to that. For one, you have to release the object references yourself. Secondly, you have to constantly convert between the integer handle and the object. On the other hand, however, most people 'get' handles, so it's an easy route to work with. I'm going to be talking about the other route, the one where you actually push objects onto the stack in some sense, without having to map them to a handle.

So, objects in BlitzMax are somewhat special due to the use of a garbage collector. So, pushing an object onto the stack is simple - you push a pointer (in this first example, light userdata, as opposed to userdata) to the object. However, the object has to be retained before pushing it onto the stack and released when removing it from the stack. In other words, we need to be able to not only retain the object when we push it, but also release it when the pointer to the object (the userdata) is finalized in Lua. Consequently, a pointer, or light userdata isn't enough to manage a BlitzMax object in Lua.

The most important aspect here is the finalization, of course. There is only one data type in Lua that allows finalization: userdata. So, we need to wrap the object in this particular data type. Fortunately, this is easy, given that userdata is just a pointer to a block of memory. Take the following C code as an example:



This doesn't yet account for finalization - it simply retains the object, pushes a userdata onto the stack, and stores a pointer to the object in the memory associated with the userdata. The first part of handling objects is complete, and you can technically run an application with this. However, it should be noted that in this situation, every object you push is a memory leak. Finalization still has to be handled.

To introduce finalizer methods, the best way I can think to do it is to draw a comparison to BlitzMax. In BlitzMax, your finalizer method is Delete, as in this example:



Every object has a finalizer method, although it may be the default or inherited from a supertype. Regardless, given that we know what a finalizer method is, it's easy to understand then that you could draw up a similar example of a storage unit for objects, as we want to have in Lua, in BlitzMax like this:



In the above, a reference to the object is retained an object of type Container is initialized, and when the Container object is deleted, it's released (not in the sense of freeing memory, but rather informing the object that one less thing is referencing it). So, to do this in Lua, we need to make use of metatables. Metatables allow some features that languages like BlitzMax do not, particularly operator overloading (more than I care to list specifically). Of particular interest though is the __gc metamethod.

Metamethods are fields in a metatable that have functions, or in some cases tables (but we won't be concerned with that), assigned to them. There is nothing special about them, they're just given that name because they happen to be used in a metatable, rather than something else. In the case of __gc, the function would look something like this were it written in Lua:



Now, we have no way of releasing a reference to a BlitzMax object in Lua (we do, actually, but ignore that thought, because going through Lua is much more difficult). So, we'll retreat to the previous example in C of pushing a BlitzMax object. I should note that I use C in these cases because it's simply easier than writing equivalent code in BlitzMax.

So, assuming you know how to create tables using the Lua C API already (if you do not know, please read the reference manual), we can make a few adjustments to the lua_pushbmaxobject function above, and introduce a new function:



First off, we introduce the new finalizer method. It's fairly simple, four lines and it's done. Simply get the userdata containing a pointer to the object off the stack, release the reference to the stored object, set the stored value to nil, and return zero. I really want to stress the importance of returning zero in this case, because I've personally screwed up and forgotten it. Without it, you will have problems, and chances are Lua will end up with a lot of garbage on the stack. This seems to be more pronounced in Windows, which is probably a testament to all the tons of issues with Windows, but that aside, don't forget to return zero.

Secondly, we give the userdata a metatable in the revised lua_pushbmaxobject. There is nothing special about this aside from lua_setmetatable, which probably doesn't get much attention in tutorials. lua_setmetatable is essentially the same as lua_settable, but there's no key to be concerned with. It pops a table off the stack and sets that as the metatable of the value pointed to at the index (-2 in this case). Simple, and if you need to know more about that, read the various links to the manual I've littered throughout this.

Now, you might be wondering how to get a BlitzMax object off the stack, and the answer is fairly simple. Much like in the finalizer method above, you simply grab the userdata off the stack and get the object out of that. Doing this in BlitzMax is not entirely trivial, so it helps to have a function written for it in C:



Finally, you end up with the API in BlitzMax:



There are a few notes to be made about the above (in its entirety, not just the above block):
  • First off, this can be much more memory-intensive than if you were to cache the container userdata and reuse it every time you push that object onto the stack. LuGI does this, and its source code is freely available on github if you decide to look for examples. As far as I know, LuGI is the only set of modules for BlitzMax that will do this.
  • Secondly, there are no checks being made in any of the above examples to see if the object is in fact a userdata, if the position on the stack exists, etc. Sometimes they're unnecessary, as you're just working with other Lua functions that already do this and you only need to check the return values. Whether you consider it necessary or not is up to your judgment, and if you write flawless code all the time, maybe it's always unnecessary.
  • Thirdly, this does not magically give you access to fields and methods in Lua - that is more complex and in-depth than I'm willing to talk about publicly (due to my ability to look stupid, more than anything else). There are a variety of ways, most of them end up being fairly similar to each-other, but all of them will involve some manner of glue code. That's about it for now.

If any of the above seems poorly written, that's because it is. If you're on Google Wave, you can contribute to revisions in the BlitzMax and Lua wave (search for with: public tag:blitzmax tag:lua). If you think I've said something stupid, point it out, 'cause otherwise I'm just going to do it over and over again.

 

Comments