3

I have an Actor that I want to give a Script.

Instead of having multiple shared-objects, I would like to have a single, top-level module that includes its own dependencies.

In other words, I want to be able to do this:

Actor  = require 'Actor'
Script = require 'Actor.Script'

script = Script("To be, or not to be ...");
actor  = Actor();

Both of these simply return functions which create userdata of both Actor and Actor.Script types. My problem is that, while I can get this code to load, it doesn't work as expected. It seems that Script simply returns an Actor userdata somehow.

print(script) => Actor 0x7fb7a240e998
print(actor)  => Actor 0x7fb7a240a478

I was expecting:

print(script) => Actor.Script 0x7fb7a240e998
print(actor)  => Actor 0x7fb7a240a478

If I 'bust-out' the code into two different modules, I get the expected results, but I would really like to have this in a single module.

I am compiling on OSX and Clang with:

clang -Wall -I./ -I/usr/local/include/ -bundle -undefined dynamic_lookup actor.c script.c -o Actor.so -L./ -L/usr/local/lib

Here is the code I am using:

actor.c:

#include "common.h"

#define ACTOR_LIB "Actor"

typedef struct Actor {
    struct Script *script;
} Actor;

/*
 * Allocate, initialize, and push a new Actor onto the stack.
 * Returns a pointer to that Actor.
 */
Actor*
lua_newactor (lua_State *L)
{
    Actor *actor = lua_malloc(L, sizeof(Actor));
    actor->script = NULL;
    return actor;
}

/*
 * Make sure the argument at index N is a actor and return it if it is.
 */
Actor*
lua_checkactor (lua_State *L, int index)
{
    return (Actor *) luaL_checkudata(L, index, ACTOR_LIB);
}

static int
actor_new (lua_State* L)
{
    Actor *actor = lua_newactor(L);
    lua_pushobject(L, actor, ACTOR_LIB);
    return 1;
}

static int
actor_print (lua_State* L)
{
    Actor *actor = lua_checkactor(L, 1);
    lua_pushfstring(L, "%s %p", ACTOR_LIB, actor);
    return 1;
}

static const luaL_Reg actor_methods[] = {
    {"__tostring", actor_print},
    { NULL, NULL }
};

int 
luaopen_Actor (lua_State * L)
{
    /* create metatable */
    luaL_newmetatable(L, ACTOR_LIB);

    /* metatable.__index = metatable */
    lua_pushvalue(L, -1);
    lua_setfield(L, -1, "__index");

    /* register methods */
    luaL_setfuncs(L, actor_methods, 0);

    /* Actor() => new Actor */
    lua_pushcfunction(L, actor_new);

    return 1;
}

script.c:

#include "common.h"

#define SCRIPT_LIB "Actor.Script"

typedef struct Script {
    const char *string;
} Script;

/*
 * Allocate a new Script to be passed around.
 */
Script *
lua_newscript (lua_State *L, const char *string)
{
    if (string == NULL)
        luaL_error(L, "`string` cannot be empty!");

    Script *script = (Script*) lua_malloc(L, sizeof(Script));
    script->string = string;
    return script;
}

/*
 * Make sure the argument at index N is a Script and return it if it is.
 */
Script *
lua_checkscript (lua_State *L, int index)
{
    return (Script *) luaL_checkudata(L, index, SCRIPT_LIB);
}

static int 
script_new (lua_State *L)
{
    const char *filename = luaL_checkstring(L, 1);
    Script *script = lua_newscript(L, filename);
    lua_pushobject (L, script, SCRIPT_LIB);
    return 1;
}

static int
script_print (lua_State* L)
{
    Script *script = lua_checkscript(L, 1);
    lua_pushfstring(L, "%s %p", SCRIPT_LIB, script);
    return 1;
}

static const luaL_Reg script_methods[] = {
    {"__tostring", script_print},
    { NULL, NULL }
};

int 
luaopen_Actor_Script (lua_State *L)
{
    /* create metatable */
    luaL_newmetatable(L, SCRIPT_LIB);

    /* metatable.__index = metatable */
    lua_pushvalue(L, -1);
    lua_setfield(L, -1, "__index");

    /* register methods */
    luaL_setfuncs(L, script_methods, 0);

    /* Push a function: Script(...) => new script */
    lua_pushcfunction(L, script_new);

    return 1;
}

common.h:

#ifndef COMMON
#define COMMON

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

/*
 * Allocates size_t memory on the given Lua state.
 * lua_newuserdata automatically pushes it, so we pop it.
 */
static
void *
lua_malloc (lua_State *L, size_t size)
{
    void *p = lua_newuserdata(L, size);
    lua_pop(L, 1);
    return p;
}

/*
 * Associate an object pointer with given name and push that onto the stack.
 */
static
void
lua_pushobject (lua_State *L, void *object_pointer, const char *metatable_name)
{
    lua_pushlightuserdata(L, object_pointer);
    luaL_getmetatable(L, metatable_name);
    lua_setmetatable(L, -2);
}

#endif
3
  • 3
    Don't pop the value created by lua_newuserdata() immediately, it is the only thing that prevents your userdata from being garbage-collected. Also, all light userdatas share a common metatable, that's why all your dangling pointers^W^W"objects" are of the same type. Commented Sep 20, 2015 at 0:59
  • @siffiejoe can you point me in the right direction for creating multiple objects for Lua in C and how metatables work? Commented Sep 20, 2015 at 5:19
  • 1
    Just get rid of the lua_pop and the lua_pushlightuserdata in common.h, and your code should work. Your only problem is that you use lightuserdata when you should use full userdata. Commented Sep 20, 2015 at 6:40

1 Answer 1

1

In this case, the module and submodule functions were correct in C. It follows the form of luaopen_Parent_Child_..._Descendant as the function name in C and Parent.Child.Descendant in Lua.

The problem here was that I was confusing lua_pushlightuserdata with lua_newuserdata in terms of what gets pushed onto the stack. lua_pushlightuserdata all share the same metatable, so I couldn't have separate objects.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.