Lua's Stack.Data gets passed from lua to C/C++ and back again through lua's stack. It's essentially a communication channel. So, when I call a function, the function is placed on the stack, the first argument is then placed on the stack, and so on. This isn't a traditional stack, since normally, the only operations that are available on a stack are push and pop. These functions that I've described all access specific elements on the stack. So, like I was saying before, it's not really a stack, but we'll consider it a stack for now. But each operation accesses an element in this stack by an index. This index value can be either positive, or negative. Here's what the lua doc's have to say about that: A positive index represents an absolute stack position (starting at 1, not 0 as in C); a negative index represents an offset from the top of the stack. More specifically, if the stack has n elements, index 1 represents the first element (that is, the first element pushed onto the stack), index n represents the last element; index -1 also represents the last element (that is, the element at the top), and index -n represents the first element. We say that an index is valid if it lays between 1 and the stack top (that is, (1 <= abs(index) <= top)). Just as a side note, there are several additional functions that are grouped in with the lua_toXXX set of functions used for accessing the lua stack. Check the docs that come with lua for a reference. In a later tutorial, I'll dig into them. Anyway, where was I? Oh yeah, glue functions … If you look at my glue function l_addNPC: int l_addNPC( lua_State* luaVM) { theNPCManager->AddNPC( lua_tostring(luaVM, -1) ); lua_pushnumber( luaVM, 0 ); return 1; } So, essentially what I do is call the NPCManager's AddNPC method. As an argument, I grab the string that is currently the last item on lua's stack. Since AddNPC is a void method, I'm assuming that all goes well, and push a number, back onto lua's stack, essentially the return value. A similar process is done with DeleteNPC. OK, I now have the glue functions. Now how to let lua know that these functions exist? And how do we let lua know how many arguments that these functions take. Well, your in for a bit of a surprise. You can, with lua, send as many arguments as you want to into a function in lua. As well, any function in lua can return more than one result. That's right. Any lua or lua accessible function can return multiple values. That's called a Tuple. It's cool, but it can also be a bit unsettling at first. So, what about getting the binding between lua and C/C++? Well, it's not that hard. Essentially what we have to do is register the function to lua. The lua function lua_register provides that functionality for us. lua_register(L, n, f) where L: lua_State to register the function to N: the character name of the function exposed to lua F: the glue function. Surprisingly enough, lua_register() isn't a function, it's a macro. Here's what the macro expands to: (lua_pushcfunction(L, f), lua_setglobal(L, n)) lua_pushcfunction pushes a C function into lua_State. As well, the name of this function (n) is now added to the 'global' function name space. Now, anytime in lua we use that name, it will be associated with that function. It's a one to one relationship. So, with the lua_register macro, we have now exposed two lua functions called addNPC and deleteNPC. I can now use them in any lua script, so long as they are registered when I initialize lua. So, building upon the previous example, if you examine main.cpp, it's been modified as such: int main(int argc, char* argv[]) { lua_State* luaVM = lua_open(0); if (NULL == luaVM) { printf("Error Initializing lua\n"); return -1; } // initialize lua standard library functions lua_baselibopen(luaVM); lua_iolibopen(luaVM); lua_strlibopen(luaVM); lua_mathlibopen(luaVM); printf("Simple Functional lua interpreter\n"); printf("Based on lua version 4.0.1\n"); printf("Registering Custom C++ Functions.\n"); lua_register( luaVM, "addNPC", l_addNPC ); lua_register( luaVM, "deleteNPC", l_deleteNPC ); printf("Enter lua commands. type 'exit I've highlighted the changes in the code in blue to identify the changes between the two versions of this code. Next up, what would I do in lua? Well, it's surprisingly simple: -- Simple lua script -- comments indicated with a '--' -- New C functions exposed to lua -- addNPC("NPC Name") -- deleteNPC("NPC Name") addNPC("Joe"); addNPC("Sue"); addNPC("KillBot2000"); addNPC("BotToDelete"); addNPC("Krista"); addNPC("Brandon"); deleteNPC("BotToDelete"); Running the code produces the following results:
I can add and delete NPC's into the system. I then display the results as part of the engine, not the lua script. I can also change the functionality of this application simply by changing the script. I don't have to recompile a line of the engine. If I want to use the luac compiler, I can. I've included the compiled version of 'startup.lua' called 'startup.lub'. Change main.cpp to load this file, and you're cooking with gas. Final wordsOK, this has actually been a rather large document for me to write. It's taken me the better part of two days (thank god for long holiday weekends) to write this up. Included in the source code that accompanies this lesson is another project I've created that builds particles during startup. It's a bit more complex and I do plan on writing it up. But it's there for those of you that want to experiment past what I've written here. You can get the source to these projects at: http://gamestudies.cdis.org/~amatheson/lua_examples.zip All I can say is that experimenting with lua has been a very rewarding experience and has given a true insight into extending any application that you create. There are a lot more topics to discuss in dealing with scripting languages and I'll be revisiting it soon. Your comments on this lesson are always welcome, as well as suggestions/directions in which to take it. So have fun. Thanks go out to Rick Kerry (editor@rickkerry.com) for helping with proofing this document. References:Lua.org website: www.lua.org
About the authorAsh Matheson is the Department Head for Game Studies at the Center for Digital Imaging and Sound (CDIS) in Burnaby, BC, Canada. He's been teaching game programming there for two years. Previously, he's been a software engineer for Hummingbird Communications, Canada's second largest software development house. He's also been involved with several independent game companies as a development lead. You can contact him at amatheso@artschool.com. |
|