Introduction
When I started thinking about creating a scripting component for my game engine, my thoughts were centered around building my own compiler and VM for it to run on. However, as soon as I embarked upon this journey I realized the difficulties of actually creating such a language, and then having it run fast. It's a very difficult (or at least time consuming) task. Sure there are a myriad of tutorials, books, references, and other resources on the subject, but it's still a big project. There is, however, an alternative.
Lua is a scripting language that is both like and unlike C. It's rather simple, but very powerful and it does house complex features, such as metadata. The best thing about Lua (in my opinion at any rate) is that is can be embedded in a C or C++ application!
Yes, you can throw it right into your game engine and use it to perform operations, shunting data between the Lua stack and C (or C++). However, unless you do some fancy coding to integrate C++ data types into Lua you are restricted to C type strings, numbers (doubles), and tables (these are variant associative arrays). You can register functions to perform actions in Lua, but this is a tedious process, and your time could be better served working on that game of yours couldn't it? No, coding by hand takes far too long. Hence ToLua (and ToLua++).
ToLua is a sort of API/code generator that binds Lua to C code and functions by using a pkg file, a declaration file in which you simply transplant the public members, member functions, and non-member functions of the construct/routine you wish to call in Lua. ToLua++ (the focus of this article) takes this a step further and binds C++ constructs and functions to Lua. It will allow you to use your game engine classes in Lua as native types, and allow you to shunt data between C++ and Lua through the Lua stack while retaining the data structure. The possibilities are endless; you could create objects in Lua using the scripts as initializers, you could expose your object management class and control it from scripts, or you could expose your entire game engine, handling every detail from Lua. ToLua++ is relatively easy to setup, even if the documentation is somewhat lacking.
Getting and Setting up ToLua++.
You will need to obtain the Lua 5.13 library file. This can be done by obtaining the source and then building as a static library or it may be contained in ToLua++ demo packages pre-built.
ToLua++ itself will need to be downloaded and compiled.
I couldn't for the life of me follow the ToLua++ download instructions using python and Scons, however if you want to try it may be simpler than manually building the project source. There is also a good instruction set on building ToLua++.
The files that you want to end up with at the end of this little adventure are:
- tolua++.exe (or equivalent binary for linux folks)
- tolua++.h
- tolua++.lib (or a library file of similar name) And you'll want to snag these files (from Lua 5.13):
lua.h, lauxlib.h, luaconf.h, lualib.h, lua5.1.lib So now we have all of the files we need to setup a basic Lua interpreter and we have the ToLua++ tools to process pkg files and build our bindings files.
The next step is to actually build our package file. This is basically a header that you place the declarations of the classes or functions you want to expose in there. To clean a header file down to a pkg file simply remove all things private or protected and copy the declarations that you want to be able to use in Lua into the file. Like so:
If this is our header file:
#ifndef MYHEADER_H #define MYHEADER_H class MyClass { private: int x; public: int GetX(); }; #endif This would be our package file: class MyClass{ public: int GetX(); }; That's it, we're done. Of course, we should include our constructors so we can build the objects in Lua (we need to do this if we want to construct our object using 'new'). As an alternative to creating a new pkg file, you can simply add ToLua++ extraction tags around the code you want to expose in your header file. In my opinion this is a little less clear but might be more convenient.
The tags are so:
- tolua_export : exports a single line to the pkg file
- tolua_begin : exports all code between this and
- tolua_end : ends tolua_begin So the header file would simply be like so: #ifndef MYHEADER_H #define MYHEADER_H_ class MyClass { //tolua_export private: int number; public: void set_number(int number); //tolua_begin int get_number(); }; //tolua_end #endif This forms the equivalent of class MyClass{ int get_number(); }; Notice that I omitted a function. If you don't want to expose a function, simply don't include it. The next step would be to include this header in your pkg file. You can use an #include statement to accomplish this or include any other header files you feel you need to.
After the package file has been created, run ToLua++ using the console or terminal. To list the options, use tolua -h. For most cases you will probably want something that looks like this:
tolua++ -o myfile.cpp -H myfile.h myfile.pkg
To give your package a name, add the name as the first argument:
tolua++ mypack -o mypack.cpp -H mypack.h mypack.pkg
This should create the files you need to include (along with tolua++.h).
I will not discuss embedding Lua in your game engine here. Many wonderful tutorials exist for this kind of thing on the internet and GameDev.net, so I am going to assume that you do know how to create and initialize a Lua state.
The ToLua++ bindings need to be registered with the Lua state. This is done through the tolua__open(lua_State) function (if you didn't name your package). If the package is named than the function will look more like tolua_mypack_open(lua_State).
Now we've got a nice little Lua interpreter which can use our types as native types! Cool, huh? But, how do we get them out of Lua and back into our system, more than that, what if we don't want to construct them in Lua?
Well, there are two functions that deal with these particular issues. Lua comes packed with functions to translate data between C++ and the stack. ToLua++ adds a few functions which take bound classes (classes we put in our pkg file) and push them to the stack then rescue them again (In addition, it binds std::string to be a native type in ToLua).
Let's say I have a class called Actor, in a namespace Stage. Here is his definition:
namespace Stage{ class Actor{ private: int health; public: Actor(); ~Actor(); void SetHealth(int amt); }; } The methods I use to exchange data make use of the tolua_tousertype and tolua_pushusertype functions; each operates directly on the Lua stack. The function tolua_tousertype is used to extract data from the lua stack. It returns a void pointer which we cast to our type and operate on. Actor *my_actor = static_cast(tolua_tousertype(luastate,-1,0)); This call will take the top of the lua stack (index -1, the top) as a void pointer then cast it to our data type. my_actor can then be operated on, and if it is needed, passed back to Lua via tolua_pushusertype(luastate,static_cast(my_actor), ?Stage::Actor?); The string tells ToLua what kind of type it is. If Actor was not part of the Stage namespace I would only need to use the string ?Actor?. These functions can be wrapped in another function which you can then register with Lua. For more information on registering lua functions (and generally embedding lua) see An Introduction to Lua. Once the system has been setup in C++ you can construct and transfer objects from inside Lua. You can simply call any registered functions or exported functions (from the package file) directly in your script, as if they were part of Lua. To access items inside a namespace use the '.' operator on the name due to fact that the function is registered within a table, whose name is the name given on the command line. For example, if I wanted to access the Actor class I would use the statement Stage.Actor. To construct an actor object I would call its new operator:
foo = State.Actor:new(); After this I can call foo's member functions: foo:SetHealth(0);
Conclusion
This snippet only provides a cursory glance over the possibilities of ToLua++ and its export system. I am by no means an expert on Lua itself, I am still learning the language, and still in the process of embedding it into my own projects. I choose to mix manual bindings or 'glue' functions with ToLua++ exported functions in my sample program, while I am taking a more modular (no global functions) approach in my central project. I suggest you find a balance that works for you.
-Cheers & Good Luck
Austen Higgins-Cassidy
References/Further Reading Materials:
Lua
ToLua++
Installing ToLua (the hard way)
Another ToLua++
Comments or Criticisms: [email="austen@plasmasoftstudios.com"]austen@plasmasoftstudios.com[/email]