tags:

views:

112

answers:

2

Consider the following example (a simple 2d vector lib). Here there is a single constructor function which returns an object table with methods. My issue with this approach is that it is creating new tables with every constructor. Is there a way to use a single instance of the table but change only the _data field which identifies the point being worked on by the methods? Is this a better approach?

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"

const char* test = 
"p1 = point(410, 680);"
"p2 = point(320, 120);"
"print('dot='..p1:dot(p2));"
"print('cross='..p1:cross(p2));";

typedef struct point_t {
    lua_Number x, y;
} point_t;

point_t* p_new(lua_Number x, lua_Number y) {
    point_t* p = malloc(sizeof(point_t));
    p->x = x;
    p->y = y;
    return p;
}

void lua_settabledata(lua_State *L , char * key , void * value) {
    lua_pushstring(L, key);
    lua_pushlightuserdata(L, value);
    lua_settable(L, -3);
}

void lua_settablefunction(lua_State *L, char * key , lua_CFunction value) {
    lua_pushstring(L, key);
    lua_pushcfunction(L, value);
    lua_settable(L, -3);
}

point_t* lua_topoint(lua_State *L, int index) {
    point_t* p;
    lua_pushstring(L, "_data");
    lua_gettable(L, index);
    p = lua_touserdata(L, -1);
    lua_pop(L, 1);
    assert(p);
    return p;
}

int l_dot(lua_State *L) {
    point_t* p1 = lua_topoint(L, 1);
    point_t* p2 = lua_topoint(L, 2);
    lua_pushnumber(L, p1->x*p2->x + p1->y*p2->y);
    return 1;
}

int l_cross(lua_State *L) {
    point_t* p1 = lua_topoint(L, 1);
    point_t* p2 = lua_topoint(L, 2);
    lua_pushnumber(L, p1->x*p2->y - p1->y*p2->x);
    return 1;
}

int l_setx(lua_State *L) {
    point_t* p = lua_topoint(L, 1);
    p->x = lua_tonumber(L, 2);
    return 0;
}

int l_sety(lua_State *L) {
    point_t* p = lua_topoint(L, 1);
    p->y = lua_tonumber(L, 2);
    return 0;
}

int l_getx(lua_State *L) {
    point_t* p = lua_topoint(L, 1);
    lua_pushnumber(L, p->x);
    return 1;
}

int l_gety(lua_State *L) {
    point_t* p = lua_topoint(L, 1);
    lua_pushnumber(L, p->y);
    return 1;
}

int l_point(lua_State* L) {
    lua_Number x = lua_tonumber(L, 1);
    lua_Number y = lua_tonumber(L, 2);
    lua_newtable(L);
    lua_settabledata(L , "_data", p_new(x, y));
    lua_settablefunction(L , "dot", l_dot);
    lua_settablefunction(L , "cross", l_cross);
    lua_settablefunction(L , "setx", l_setx);
    lua_settablefunction(L , "sety", l_sety);
    lua_settablefunction(L , "getx", l_getx);
    lua_settablefunction(L , "gety", l_gety);
    return 1;
}

int main() {
    lua_State* L = lua_open();
    luaL_openlibs(L);
    lua_register(L, "point", l_point);
    if (luaL_loadstring(L, test) || lua_pcall(L, 0, 0, 0))
       printf("error: %s", lua_tostring(L, -1));
    getchar();
    return 0;
}
A: 

Consider using tolua which allows your lua code to directly interact with specified portions of your c/c++ code.

DShook
+5  A: 

I've just skimmed your code, but it looks like each of your objects is a table containing a light userdatum with the instance data, as well as a bunch of C functions wrapped into Lua closures. Yes, this is very inefficient. A first improvement would be to not make a separate closure for each instance's use of the same C function. But we can do much better than even that.

You can't do what it sounds like you're imagining: you can't have a single table shared between objects but with one field different for each object. If one field is different then you've got a different table.

But you can divide up the shared data and the instance data in ways that are more efficient.

There's no need to create a table to hold your instance data. Just make your allocated structure into a (full, not light) userdatum. This is a bit smaller than a table. (I think 40 bytes vs 64 bytes on x86_64; plus the size of your allocated structure.)

You'll put all your methods into a single method table, and associate that one table with all of the userdata you return. Shared data can be associated with objects in a variety of ways. Since you want the methods to be accessible in Lua, you're going to want the method table to be assigned to M.__index, where M is the metatable of each instance object. All the objects can be assigned a single shared M, and M can contain the methods itself if you like (then M.__index will just be M).

Also in the metatable you'll want to put a __gc method, to free your allocated struct when the object is garbage-collected.

Have a look at the chapter of Progamming in Lua on userdata. Also take a look at the lv3 package at lhf's site. (He's one of the Lua authors.)

profjim
Super good answer. +1
Norman Ramsey