How to determine the type of lightuserdata?

I am adding Lua Scripting to my game engine (C ++) using the Lua C API.

I am pushing my objects using lua_pushlightuserdata(lua_State *L, void *p)

, and when I want to get the objects I am using lua_touserdata(lua_State *L, int idx)

, but I don’t know which object I am using lua_touserdata()

.

How can I check this?

Here's some sample code:

bool LuaScript::InitScript(const char* code, GameObject * container)
{
   CloseLua();

   bool ret = false;
   luaState = LuaNewState();
   luaL_openlibs(luaState);
   RegisterAPI(luaState);

   /*Load the code inside .lua script and set "this" to reference the GameObject 
   containing the script and "renderer" referencing SpriteRenderer class (GameObject component)
   to show you the example.*/
   if (luaL_loadstring(luaState, code) == 0) {
       lua_pushlightuserdata(luaState, container);
       lua_setglobal(luaState, "this");

       SpriteRenderer* spr = new SpriteRenderer();
       lua_pushlightuserdata(luaState, spr);
       lua_setglobal(luaState, "renderer");
       ret = LuaUtils::CallFunction(luaState, NULL);
   }
   else {
       LOG_WARNING("Cannot load lua script of '%s': %s", container->name.c_str(), lua_tostring(luaState, -1));
   }
   return ret;
}    

void LuaScript::RegisterAPI(lua_State* luaState)
{
   luaL_Reg GameObjectAPI[] = 
   {
       { "SetActive", SetGameObjectActive },
       { NULL, NULL }
   };
   LuaUtils::RegisterLibrary(luaState, GameObjectAPI, "gameObject");
}

int LuaScript::SetGameObjectActive(lua_State * luaState)
{
   int arguments = lua_gettop(luaState);
   if (arguments != 2) {
       LOG_WARNING("SetActive(GameObject, bool) takes 2 arguments!");
   }
   else {
       if (lua_islightuserdata(luaState, 1)) {
/*---> Here it the problem. I'm assuming that this data is a GameObject 
       but it can be other kind of data like SpriteRenderer.*/
           GameObject* go = (GameObject*)lua_touserdata(luaState, 1);
           bool active = lua_toboolean(luaState, 2);
           go->SetActive(active);
       }
   }
   return 0;
}

      

.lua Script:

function Start()
   gameObject.SetActive(this,false)
   gameObject.SetActive(renderer,false)
end

      

The above example gameObject.SetActive(this,false)

works because it this

is a GameObject, but gameObject.SetActive(renderer,false)

shouldn't work because it is renderer

not a GameObject. But it works because I don't know how to check if it is a GameObject or SpriteRenderer and the variable go

in the string GameObject* go = (GameObject*)lua_touserdata(luaState, 1);

inside LuaScript::SetGameObjectActive(lua_State * luaState)

is wrong (nullptr errors, memory errors, etc.), because I am assigning the data as a GameObject.

+3


source to share


2 answers


For light user data, Lua only stores a pointer. The information you are looking for does not exist, you will have to add it somehow.

Let's take a look at several ways to do this:

1.switch to complete user data ... (for example, let Lua terminate your objects)

The complete userdata is allocated by Lua and gets all the attractive Lua features (metatables, garbage collection, associated "user value", ...). This means that your value is not just a raw pointer, but a complete structure with many slots where you can store type information.

(a) ... and use metatables

The most common way is using metatables. (This also allows you to add methods or override operators based on your custom data, but the metatet can also be completely empty.) A good way to use metadata is luaL_setmetatable

and luaL_checkudata

. (You create a metatel via luaL_newmetatable

which is intended to be used like

if (luaL_newmetatable( L, "Foo" )) { // where 'Foo' is your type name
    // initialize metatable contents here (if any)
} // else metatable already exists

      

and then you can luaL_setmetatable( L, "Foo" );

set a metathet for the thing on top of the stack, or Foo *foo = luaL_checkudata( L, i, "Foo" );

to check that the i

th argument of your function is Foo

and get it (or else throw an error).

(b) ... and use the 'uservalue' slot

(This "user value" is mainly intended to have a single common meta tag for all values ​​of the same type, but it still allows information to be changed for each value, so it will usually contain another table ... as such, this approach is unusual and just for completeness.)

Using lua_setuservalue

/ lua_getuservalue

, you can simply store arbitrary Lua values ​​in the custom value slot of your custom data. If you have an enumeration of all the types you are using, you can simply store an integer in this field.

Approximate scheme (only minimally tested):

void settag( lua_State *L, lua_Integer tag ) {
    lua_pushinteger( L, tag );
    lua_setuservalue( L, -2 );
}

void *testtag( lua_State *L, int ud, lua_Integer tag ) {
    void *p = lua_touserdata( L, ud );
    if (p != NULL) { /* really is userdata */
        if (lua_getuservalue( L, ud ) == LUA_TNUMBER) { /* uservalue is number */
            if (lua_tointeger( L, -1 ) == tag) { /* and tag matches */
                return p;
            }
        }
    }
    return NULL;
}

void *checktag( lua_State *L, int ud, lua_Integer tag ) {
    void *p = testtag( L, ud, tag );
    if (p == NULL)  luaL_argerror( L, ud, "wrong userdata" );
    return p;
}

      

(For both of these complete approaches to user data, remember that Lua does garbage collection and frees your object if it is no longer on the Lua stack or stored in a table somewhere in Lua state!)

2.manually wrap your objects ...



ah .... in C / ++ (add type tags)

Make sure all the values ​​you push to Lua have a common title. In C it is easy to make everything struct

start with one (or more) common field (s), C ++ classes can be different. But you can just define the type of the shell, for example (sample C again ...)

typedef struct {
    int tag;
    void *ptr;
} Tagged;

      

and only push Tagged

values ​​(which can be wrapped just before pushing them ... or always the default, maybe even if you have an inline tag in your value and avoid the extra pointer-pointer). When returning a value, check first tag

before using ptr

(or the rest of the value if enabled).

b .... in Lua (wrap everything in tables)

Make sure that wherever you work with lightuserdata in Lua, you are wrapping it in tables. This way, you again have slots to store additional type information.

A simple scheme would be to have a { ptr =

<userdata pointer> , type=

<type name or numeric identifier or ...> }

. Every function that has so far accepted bare user data now expects a table with (at least) these two fields and can then check v.type

before fetching and using it v.ptr

.

3.save a (separate) lookup table

Either in Lua or C ++ side you can save the map from void*

/ light userdata to input (ID).

On the C ++ side, there is probably some kind of ready-made data structure that you can use to do this. (I am not a C ++ programmer, so I have no idea - std::map

can it work? How easy / difficult it is to get right, I don't know.)

On the Lua side, this is as simple as creating and storing a table (for example, in a state registry), then adding the type information by lut[ptr] = tag

or checking for lut[ptr] == tag

(or through an equivalent sequence of Lua API functions if done on the C (++) side).

If you set the metatet to LUT to make keys weak ( __mode = "k"

), fields that are no longer referenced anywhere in the Lua state will be automatically reclaimed by the garbage collector. Otherwise, you will have to manually manipulate it in some way - the easiest is to extend yours free

, and also install lut[ptr] = nil

(this can be done unconditionally, it doesn't matter if the value was ever passed to Lua).


Variation 1.a is probably the simplest. (If you have trouble keeping things pinned in Lua state, try fixing that because it will make things easier.) If you absolutely cannot fix your pinning, changing 2.a or 2.b might make sense. Variations 1.b and 3 are mostly academic ... they might save you in special circumstances, but generally not a good idea.

For games specifically use 1.a (or if you got it right from the start and are sure you don't need meta tags, skip to 2.a. Metatables are one of the core features of Lua, and by not using them you will be missing a lot of things. But using Lua without metatables might still be the right choice if you know what you are doing.)

0


source


It is better to use complete user data as it involves additional information. However, it is a little easier to use the light-user data as it avoids some code.

If the type you are putting in lightuserdata is from the same base class and you have runtime information, you can use C ++ to query the actual type of the object.

Otherwise, the best way would be to use the full userdata.

void * mem = lua_newuserdata( L, sizeof( Type) );

      

gives you back a new piece of memory

new (mem) Type( params );

      

creates the correct type

lua_setmetatable 

      



allows lua to understand the type and can be queried using lua_getmetatable

in C / C ++

Definitive books on this subject Programming in lua , which is recommended.

The simplest version of your code looks like this, it is not the most correct method ...

   // somewhere when setting up 
   luaL_newmetadata( L, "some string" );

      

Then when creating lua objects ....

   SpriteRenderer* spr = new SpriteRenderer(); 
   SpriteRenderer ** data = (SpriteRenderer**)lua_newuserdata( L, sizeof( SpriteRenderer *) ); // hold a pointer.  
   *data = spr; // now spr is in user data.
   luaL_getmetadata( L, "some string" );
   lua_setmetadata( L, -2 ); // set meta-data for this object.

      

Now you can check the type, the correct type ...

   luaL_checkudata( L, idx, "some string" ); // only works if the type is correct.

      

+2


source







All Articles