2

I have a class called Entity, which has many functions like onPickup, onDrop, onUse etc. What I want to do is, write a script that defines all of these functions and make them callable from the C++ functions. So the functions defined in C++ would just be calling their corresponding Lua functions that have some functionality.

But here's the problem, I want every script that I write, for every Entity in the program to be working in it's own scope.

I'm using LuaBind, and I have no prior experience with Lua, so I'm a little lost here.

toujamaru
  • 111
  • 1
  • 5
  • 1
    Wait, are you asking how you can define classes that inherit Entity in Lua so that when you call the onXXX virtual methods your Lua methods are called? If yes - here's your link: http://www.rasterbar.com/products/luabind/docs.html#deriving-in-lua – sbk Apr 29 '11 at 20:30
  • @sbk, I think this was a good solution to the problem. If I have objects that behave differently, it's only right that I store them as separate classes. Thanks! – toujamaru May 11 '11 at 15:02
  • Do you want each script to run in it's own scope, or each function? Which version of Lua are you using? With Lua 5.2 it's very simple to set the environment of a script or function. This may make it easy to do what you're trying to accomplish. If I'm off base could you provide more details as to why you want each script to execute in it's own scope? – Zack The Human May 22 '11 at 18:16
  • Well, if I want to use some extra variables for an object, I can define those variables in the derived class and use them. So I write a script file for an Entity that defines a class, and then create it's objects in the global scope, where the main script is running. So any variable that I define in the class, stays in the class, and does not interfere with any variables defined in the main script file. – toujamaru May 31 '11 at 09:03

3 Answers3

3

I don't use lua bind but this may help. The idea is to register the lua functions in your C++ class and keep a reference to the lua function in your C++ class.

To define a lua function that is callable from C/C++ I use luaL_ref to store a reference to the callback function in my C++ object.

// a little helper function
template <typename T>
T *Lua_getUserData(lua_State *L) {
    assert(lua_isuserdata(L, 1) == 1);
    T **v = (T **) lua_touserdata(L, 1);
    assert(v != NULL);
    return *v;
}

int lua_FormRegisterMethods(lua_State *L) {
    Entity *f = Lua_getUserData<Entity>(L);
    assert(lua_istable(L, 2) == 1); // check the next parameter is a table
    lua_pushvalue(L,2); // dup the table
    f->LuaTable = luaL_ref(L, LUA_REGISTRYINDEX); // keep a reference to the table
    lua_getmetatable(L, 2); // get the metatable
    lua_pushstring(L, "OnClick"); 
    lua_rawget(L, -2); // get the OnClick Lua Function
    f->LuaMethod = luaL_ref(L, LUA_REGISTRYINDEX); // save a reference to it
    return 0;
}

and then you can get the lua method in your C++ event

lua_rawgeti( LuaInstance->L, LUA_REGISTRYINDEX, LuaMethod );
assert(lua_isfunction(LuaInstance->L, -1) == 1);

now you can call this function with self set to the table you saved earlier. hth

daven11
  • 2,905
  • 3
  • 26
  • 43
1

You can call a Lua function with, e.g.

int callLuaFunction(lua_State* lua_state) {
    return luabind::call_function<int>(lua_state, "myluafunction", param1);
}

if the Lua function returns an int and takes 1 parameter.

I'm pretty sure you can make as many lua_State's as you want. Just pass the correct one for the entity into call_function.

jarmond
  • 1,372
  • 1
  • 11
  • 19
  • yah, sure or you can do that :-) - are you suggesting to have a separate lua state for each entity instance? – daven11 Apr 29 '11 at 12:42
0

To fully implement this the way you will probably want to will require digging around a bit in some of the more esoteric bits of Lua. It is well worth the time though. I'll show a very trimmed down version of how I have handled this. Be warned, there are a lot of little bits all working together here - mainly saving and calling saved functions and using c++ objects as Lua user data.

First we need a c++ class which will store events handlers (lua functions) as lua references which are simple ints. I am using an array here but you could use whatever makes sense. The main thing happening here is that you want to be able to call a lua function which is referred to by the int reference.

#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
#include <string>
#include <iostream>
#include <assert.h>
using namespace std;

enum enum_event_types {
    ON_USE, ON_DROP, ON_WHATEVER, EVENT_COUNT
};

class Entity {
  private:  
    int events[EVENT_COUNT];
  public: 
    lua_State* lua;
    void setEventHandler(int event, int ref) {
        assert(event < EVENT_COUNT);
        events[event] = ref;
    }
    void callEventHandler(int event) {
        int error;
        assert(event < EVENT_COUNT);
            // to call the function we need to get it from the registry index
        lua_rawgeti(lua, LUA_REGISTRYINDEX, events[event]);
        error = lua_pcall(lua, 0, 0, 0); // use protected call for errors
        if (error) {
            printf("error: %s", lua_tostring(lua, -1));
            lua_pop(lua, 1); 
        }
    }
};

Now you want to expose your Entity class to Lua. If you are not familiar with how this is done there is a good article here. Essentially what is going on is that we are setting the user data returned from Entity.new() to a pointer to a pointer. This is so Lua does not garbage collect your object. Then create a meta table for "Entity" which will hold all of the methods exposed to Lua.

int L_newEntity(lua_State* L) {
    Entity **e = (Entity **)lua_newuserdata(L, sizeof(Entity *));
    *e = new Entity(); 
    (*e)->lua = L;
    lua_getglobal(L, "Entity");
    lua_setmetatable(L, -2); 
    return 1;
}

int L_setOnUse(lua_State* L) {
    Entity** e = (Entity**) lua_touserdata(L, 1);
    lua_pushvalue(L, 2);
    int ref = luaL_ref(L, LUA_REGISTRYINDEX);
    (*e)->setEventHandler(ON_USE, ref);
    return 0;
}

// this will be exposed to Lua as a table called Entity
static const luaL_Reg L_entityMethods[] = {
    {"new", L_newEntity},{"setOnUse", L_setOnUse},{NULL, NULL}
};

Now set up the Lua state and create the Entity table and create a test. The test will create an Entity and set its event handler to a Lua function passed to it. Finally test that the Lua function is being called by the c++ object.

int main() {
    Entity** e;
    int error;
    lua_State* L=lua_open();
    luaL_openlibs(L);
    luaL_register(L, "Entity", L_entityMethods); 
    lua_pushvalue(L,-1);
    lua_setfield(L, -2, "__index"); 
    lua_pop(L, 1);

    luaL_loadstring(L, 
    "e = Entity.new(); "
    "e:setOnUse(function()"
    "   print('Some of them want to use you')"
    "end);");
    error = lua_pcall(L, 0, 0, 0);
    if (error) {
        printf("error: %s", lua_tostring(L, -1));
        lua_pop(L, 1); /* errors must be popped from stack */
    }   
    lua_getglobal(L, "e");
    if (lua_isuserdata(L, 1)) {
        e = (Entity**) lua_touserdata(L, 1);
        (*e)->callEventHandler(ON_USE);
    }
    return 0;
}

This is far from complete. First of all if you ever need to set an event handler twice you will need to use luaL_unref to clear out the old reference first. Second you will probably want to pass some data about the event which occurred to the event handler. The current event handler does not take any data so gives the user of the api very little to go on. It should probably at least pass a reference to the object which is calling the event. This can be used to create very powerful and usable Apis in Lua. Good luck!

Nick Van Brunt
  • 15,244
  • 11
  • 66
  • 92