C++ 如何确定lightuserdata的类型?

C++ 如何确定lightuserdata的类型?,c++,lua,scripting,C++,Lua,Scripting,我正在使用luacapi将Lua脚本添加到我的游戏引擎(C++)中 我正在使用lua\u pushlightuserdata(lua\u State*L,void*p)推送我的对象,当我想要获取对象时,我使用lua\u-toserdata(lua\u State*L,int-idx),但我不知道使用lua\u-toserdata()得到的是什么对象 我该怎么查呢 下面是一些示例代码: bool LuaScript::InitScript(const char* code, GameObject

我正在使用luacapi将Lua脚本添加到我的游戏引擎(C++)中

我正在使用
lua\u pushlightuserdata(lua\u State*L,void*p)
推送我的对象,当我想要获取对象时,我使用
lua\u-toserdata(lua\u State*L,int-idx)
,但我不知道使用
lua\u-toserdata()
得到的是什么对象

我该怎么查呢

下面是一些示例代码:

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's 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脚本:

函数开始()
gameObject.SetActive(这个,false)
gameObject.SetActive(渲染器,false)
结束

在上面的示例中,
gameObject.SetActive(this,false)
起作用是因为
this
是一个游戏对象,但是
gameObject.SetActive(renderer,false)
不起作用,因为
renderer
不是游戏对象。但它是有效的,因为我不知道如何检查它是GameObject还是Spirterender,以及第
GameObject*go=(GameObject*)lua_-ToserData(luaState,1)行的变量
go
内部
LuaScript::SetGameObjectActive(lua_State*luaState)
格式不正确(nullptr、内存错误等),因为我将数据分配为游戏对象。

最好使用完整的用户数据,因为这与更多信息有关。但是,使用轻用户数据稍微容易一些,因为它避免了一些代码

如果您将LytUrSerDATA数据类型从同一基类派生,并且您有运行时类型信息,则可以使用C++语言查询对象的实际类型。 否则,使用完整的userdata是最好的方法

void * mem = lua_newuserdata( L, sizeof( Type) );
返回一段新的内存

new (mem) Type( params );
创建正确的类型

lua_setmetatable 
允许lua理解类型,并且可以使用C/C中的
lua\u getmetatable
进行查询++

这方面的权威书籍是推荐的

最简单的代码版本如下,这不是最正确的方法

   // somewhere when setting up 
   luaL_newmetadata( L, "some string" );
然后在创建lua对象时

   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.
现在您可以测试类型是否正确

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

对于light userdata,Lua只存储一个指针。您寻找的信息不存在,您必须以某种方式添加它

让我们看一下几种方法:

1.切换到FullUserData…(即让Lua包装您的对象) 完整的userdata由Lua分配,并获取所有高级Lua特性(元表、垃圾收集、关联的“用户值”…)。这意味着您的值不仅仅是一个原始指针,而是一个包含许多插槽的完整结构,您可以在其中存储类型信息

(a) …并使用元表 到目前为止,最常用的方法是使用元表。(这也允许在userdata上添加方法或重写运算符,但元表也可以完全为空。)使用元表的一个好方法是通过和。(您可以通过创建一个元表,该元表的用途如下:

if (luaL_newmetatable( L, "Foo" )) { // where 'Foo' is your type name
    // initialize metatable contents here (if any)
} // else metatable already exists
然后您可以
luaL_setmetatable(L,“Foo”);
将对象的元表设置在堆栈顶部,或者
Foo*Foo=luaL_checkudata(L,i,“Foo”);
检查函数的
i
第个参数是否为
Foo
,并获取它(或者抛出一个错误)

(b) …并使用“uservalue”插槽 (这种“用户值”主要是为了允许一种类型的所有值都有一个单一的共享元表,但仍然允许每个值的信息不同,因此它通常会保存另一个表……因此,这种方法并不常见,只是为了完整性而包括在内。)

使用/,您可以简单地将任意Lua值存储在userdata的“user value”槽中。如果您有一个所有使用类型的枚举,您可以简单地在该字段中存储一个整数

样本方案(仅经过最低限度的测试):

(对于这两种完整的userdata方法,请记住Lua会进行垃圾收集,如果对象不再位于Lua堆栈上或存储在Lua状态的某个表中,则会释放对象!)

2.手动包装您的对象… a、 …在C/++(添加类型标记)

确保所有您推到Lua的值共享一个公共头。在C中,很容易使所有的<代码>结构> <代码>从一个(或多个)公共字段开始,C++类可能不同。但是您可以定义一个包装类型,比如(C样本……)/P> 并且只推送带标签的

值(可以在推送值之前立即包装这些值…或者默认情况下总是这样,甚至可以将类型标记内联到您的值中并避免额外的指针间接指向)。返回值时,在使用
ptr
(或值的其余部分,如果已内联)

b、 …在Lua中(将所有内容包装在表格中) 确保在Lua中处理lightuserdata的任何地方都将其包装在表中。这样,您就可以再次使用插槽来存储额外的类型信息

一个简单的方案是使用
{ptr=
,type=
}
。到目前为止,每一个使用裸用户数据的函数现在都希望得到一个包含(至少)这两个字段的表,然后可以在提取和使用
v.ptr
之前检查
v.type

3.保留一个(单独的)查找表

无论是在Lua上还是在C++端,都可以将一个映射从“代码>空隙*<代码> /轻用户数据保存到类型(ID)。

<>在C++方面,可能有一些现成的数据结构,你可以用这个。(我不是C++程序员,所以我不知道——<代码> STD::MAP<代码>可能有用吗? 在Lua方面,这与创建和存储一个表(例如在州的注册表中)一样简单,然后通过
lut[ptr]=tag
添加类型信息,或者通过
lut[ptr]==tag
进行检查(或者如果在C(++)方面完成,则通过等价的LUAAPI函数序列)

如果在LUT上设置元表以使键变弱(
\uuu mode=“k”
),在Lua状态中不再引用的字段将自动显示
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;
}
typedef struct {
    int tag;
    void *ptr;
} Tagged;