Sonntag, 22. Juli 2012

Lua C API Tutorial (part 3) Userdata

In previous tutorials we learned how make objects of basic datatypes and functions from C/C++ available in Lua. In this tutorial we will cover one of two userdata types. But first, what is userdata? Userdata is some sort of a generic datatype that represents complex C/C++ objects in Lua. So, to say it more directly: userdata is a way to expose user made C/C++ structs/classes to Lua.

I. Light & Full Userdata
As I already mentioned before there are 2 sorts of userdata in Lua. These sorts are full and light userdata. Unlike in the official Lua book we will start with light userdata first because it's easier to get started with. So, what's so special about it? While full userdata represents the exposed object by itself, light userdata is basically just a pointer to the exposed object in the memory, therefore, being an object inside lua, full userdata objects can be garbage collected by lua while light userdata is stored inside of C and won't be deleted by Lua's garbage collector. You also cannot associate metatables with light userdata, however light userdata is ideal for passing references to values around. E.g if you have a function that creates a window using some multimedia library and a function that draws an image inside the window then you can return a pointer to the created window as light userdata from one function and then pass it to the function that does the drawing stuff.

II. Light Userdata: An Example
Consider following scenario: you are building some sort of an RPG game where items are represented by a type called ItemDefinition. Now, what do you do if you want to get information about the items from within lua?
The goal of this example is to create an instance of ItemDefinition in C++ and then expose it as light userdata to lua. After that previously exposed functions will be used to print out information about the instance. Here is the source:


#include "iostream"
#include "lua5.1/lua.hpp"

struct ItemDefinition
{
    char* name;
    int price;
    bool stackable;
};

int getPrice(lua_State* L)
{
    ItemDefinition* def = (ItemDefinition*)lua_touserdata(L, 1);
    
    lua_pushnumber(L, def->price);
    return 1;
}

int isStackable(lua_State* L)
{
    ItemDefinition* def = (ItemDefinition*)lua_touserdata(L, 1);
    
    lua_pushboolean(L, def->stackable);
    return 1;
}

int getName(lua_State* L)
{
    ItemDefinition* def = (ItemDefinition*)lua_touserdata(L, 1);
    
    lua_pushstring(L, (const char*)def->name);
    return 1;
}

int main(int argc, char** argv)
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);
    
    ItemDefinition def;
    def.name = "Mana Potion";
    def.price = 120;
    def.stackable = true;
    
    lua_pushlightuserdata(L, (void*)&def);
    lua_setglobal(L, "def");
    
    luaL_Reg module[] = {
                                {"getPrice", &getPrice},
                                {"isStackable", &isStackable},
                                {"getName", &getName},
                                {NULL, NULL}
                        };
    luaL_register(L, "_G", module);
    
    luaL_dostring(L, "print('Type: \t\t'..type(def))");
    
    luaL_dostring(L,    "print('Name: \t\t'..getName(def));"
                        "print('Price: \t\t'..getPrice(def));"
                        "print('Stackable:\t'.. ( (isStackable(def) and 'yes') or 'no') );");
    
    lua_close(L);
    return 0;
}

Most of the code should be self explanatory. First we create an ItemDefinition structure with some properties. Nothing Lua-related so far. After that we define three functions that we will use from within Lua later. The line:

ItemDefinition* def = (ItemDefinition*)lua_touserdata(L, 1);

gets the pointer (our light userdata) from the stack and casts it to ItemDefinition* so we can get needed properties by accessing the instance using the def pointer. The same approach works also for full userdata.

lua_pushlightuserdata(L, (void*)&def);
lua_setglobal(L, "def");

These lines create a global variable in Lua that points to the def object inside the scope of our main function and should be understandable to you if you've read through the first tutorial.

After that we print the type of def along with values of its properties. The output of the program is:


Type:           userdata
Name:           Mana Potion
Price:          120
Stackable:      yes

Now, the only thing that probably also deserves some explanations is the third print command in the second luaL_dostring statement, which is an equivalent of what we would write in C as
printf("%s", (isStackable(def)? "yes" : "no") );

That's it for today. In the next tutorial we will make this example more object oriented by using full userdata and metatables. Bye.

2 Kommentare: