Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to directly map a Lua variable to a C++ variable?

I am working on a game engine in C++ using Lua to define NPCs.

I can define a prototypic NPC like this:

orc =
{
    name = "Generic Orc",
    health = 100
}

function orc:onIdle()
    print("Orc idles...")
end

and then spawn an instance of "Orc" with entitySpawn(orc). This is a C++ function that reads values like health and name from the given table, creates an Entity object in C++ with the given values and in addition creates a Lua table for the specific NPC.

Now, I would like to have a direct connection between the orc.health variable in Lua and the mHealth member variable of the corresponding Entity object in C++, so I could assign a value in Lua and instantly use it in C++ and vice versa.

Is this even possible? Or do I have to make use of setter / getter functions? I have taken a look at light userdata and got to the point of storing a pointer to the C++ variable in Lua, but could not assign a value.

like image 972
Daerst Avatar asked Jun 09 '11 18:06

Daerst


1 Answers

This is possible. I'm assuming that entitySpawn returns the table that C++ created for the entity. I'll also assume that you can expose a function from C++ that takes an entity's table and returns the current health, and similarly for setting. (You can use a light userdata pointer to the C++ object as a member of this table to implement this.)

So the ugly way would look like this:

local myOrc = entitySpawn(orc)
local curHealth = getEntityHealth(myOrc)
setEntityHealth(myOrc, curHealth + 10)

To make this prettier, we can have some fun with metatables. First, we'll put the accessors for all the properties we care about.

entityGetters = {
    health = getEntityHealth,
    -- ...
}
entitySetters = {
    health = setEntityHealth,
    -- ...
}

Then we'll create a metatable that uses these functions to handle property assignments.

entityMetatable = {}

function entityMetatable:__index(key)
    local getter = entityGetters[key]
    if getter then
        return getter(self)
    else
        return nil
    end
end

function entityMetable:__newindex(key, value)
    local setter = entitySetters[key]
    if setter then
        return setter(self, value)
    end
end

Now you need to make entitySpawn assign the metatable. You would do this with the lua_setmetatable function.

int entitySpawn(lua_State* L)
{
    // ...
    // assuming that the returned table is on top of the stack
    lua_getglobal(L, "entityMetatable");
    lua_setmetatable(L, -2);
    return 1;
}

Now you can write it the nice way:

local myOrc = entitySpawn(orc)
myOrc.health = myOrc.health + 10

Note that this requires that the table that is returned by entitySpawn does not have a health property set. If it does, then the metatable will never be consulted for that property.

You can create the entityGetters, entitySetters, and entityMetatable tables, as well the __index and __newindex metamethods, in C++ instead of Lua if that feels cleaner to you, but the general idea is the same.

like image 157
John Calsbeek Avatar answered Nov 03 '22 16:11

John Calsbeek