I turned 17 today! I won't technically be 17 for maybe three more hours, but for all intents and purposes...
I've been working a lot on that Nucleus engine I mentioned previously. Right now I'm getting to know the Lua/C API so I can write a C module that Nucleus can load as a native extension to Java, as a Lua interop module. At the moment it's standalone for testing purposes, while I struggle to learn how to bind C++ classes/objects into Lua to my tastes. I'm happy with what I have so far, but right now it can only bind functions, not other kinds of values. It's probably worth writing down what I've learned so far though.
The goal: Access instance methods of a C++ object from Lua.
The problem: Lua can only easily bind static methods. There needs to be some way to tell a function what instance it should act on.
The solution (simple in hindsight): store the object instance as a full userdatum, and call its methods in Lua using the colon operator (object:method(params)). This is really just syntactic sugar for object.method(object, params). Given that object is the instance, object is really just 'this'.
The above is the keystone to the solution. Obviously it won't do much without some support. For example, we still have to bind a static function for Lua to call. Furthermore, userdata does nothing useful without a metatable. So we have to register a metatable to our new userdatum, with a (static) method registered as its __index at the very least. This function will handle lookups into the userdatum; if I do "object.foo", __index will recieve obj and "foo", and return an appropriate value, even if it's just nil. This is done with a static array of name/pointer pairs to allow lookups based on name.
We expect "object.method" to call __index and return that particular method. But remember, these methods are instance methods, and Lua can't call those directly. So instead of returning the method directly, we'll create a static 'thunk' function to resolve the access and call our instance method. We'll register this thunking function multiple times, once for each instance method, and registering a pointer to a data structure as an upvalue each time. The data structure just holds a pointer-to-member; even though pointers-to-member can't be casted to void*, a pointer to a vanilla struct instance sure can. This is basically just a Lua version of std::bind1st, creating a new object (closure) each time.
Now, "object.method" will call into __index and get back our simple thunking method, with a unique upvalue representing the method to call. So, calling this method with parameters (and using the colon syntax to pass a hidden 'this') should successfully resolve to a call to an object's instance method: "object:method(1,2,3)".
The code is... a bit ugly right now. I want to try to clean it up a bit before I make it public, and hopefully add support for __index returning things other than a function. This is a hobby project, so while I know I could just go the C route and go with static functions in separate .c files, I like working with objects, and it's fun to figure out how this is all supposed to work. [smile]
Thanks for reading!
EDIT: May as well post what my goal code is on the Lua end. This is what I want to be able to do when the interop module is finished:
local server = atom.request("server/telnet")
server.handlers["connect"] = function(self, client)
local db = atom.request("database/mysql")
db:connect("localhost", "foologin", "barpasS")
local result = db:query("SELECT * FROM tbl WHERE IP='" .. client.IP .. "'")
if #result > 0 then
Ideally, the server.handlers table/udata would use a __newindex metamethod to tell when something's being assigned, and register the hooks appropriately. I think it's pretty intuitive, and I'm almost positive I can do everything in this snippet. Give me some time, people... [grin]
Mixed feelings on this one, though mainly I got pretty far ahead of myself. I've abandoned AdLib, because it was too much work and just not enough return, and I finally decided that, yes, it was indeed reinventing the wheel. I may as well explain what I had.
The idea was, you could take a generic page with some named placeholders in it, and replace those placeholders with values (either resulting from a CGI script or directly from Apache via GET and POST... which was why I wanted to do an Apache module). You might also have a definitions file somewhere else with blocks of content that you'd inline into the page (I'll give an example below). I called it "CSS for content", but the analogy "CSS is to styles as AdLib is to content" probably fits better, because you'd take oft-used blocks of HTML and place them in a file, and refer to them in a page if you wanted them in the page. I'll write out that example now...
Hello, ~!!name!!~. This is an "AdLib test page." ?>
The ~..placeholders..~ surrounding blocks of content define and name those blocks. So, given that definitions file, you might do something like this to create a page using those definitions.
~??Header??~The ~??placeholder??~ signified an inlining of a definition, and a ~!!placeholder!!~ inlines a value rather than a definition (say, from GET or POST, or from some other method). It's probably not hard to see how the page would have looked.
Another idea I had was that the AdLib engine might not just be used for output like that, but also input. You'd have a definitions file (possibly the same one used for output), and it would parse the page, matching the page back into the content blocks and coming up with the ~!!placeholder!!~ values that were used on the page. Used that way, it wouldn't be very different from taking two identical papers with identical words, cutting out a few words on one, and placing it over the other so you could see the words used on the back page.
Actually, that idea is what made me start on AdLib in the first place... I had a file I needed to get stuff from - a really, really big file - that did not lend itself to typical parsing. But every "page" in the file had either one kind of information or another kind, so I could split it into parts like that. In the end, though, it would have been just too much effort for not enough return.
At least that's one project off my shoulders. [grin]