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

我使用lua_pushlightuserdata(lua_State *L, void *p)推送对象,当我想使用lua_touserdata(lua_State *L, int idx)来获取对象时,但是我不知道我使用lua_touserdata()获取了什么对象。

我该如何检查?

这是一些示例代码:

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

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

在上面的示例中,gameObject.SetActive(this,false)有效,因为this是一个GameObject,但是gameObject.SetActive(renderer,false)不起作用,因为renderer不是GameObject。但这是可行的,因为我不知道一种检查它是GameObject还是SpriteRenderer的方法,而且goGameObject* go = (GameObject*)lua_touserdata(luaState, 1);行的LuaScript::SetGameObjectActive(lua_State * luaState)变量ojit_code格式不正确(nullptr,内存错误等),因为我正在将数据分配为GameObject。

最佳答案

对于轻量级用户数据,Lua仅存储一个指针。您寻找的信息不存在,您将不得不以某种方式添加它。

让我们看一下执行此操作的几种方法:

1.切换到完整的用户数据…(即让Lua包装您的对象)

完整的用户数据由Lua分配,并获得Lua所有的特色功能(元数据,垃圾回收,关联的“用户值”等)。这意味着您的值不仅是原始指针,而且是具有许多插槽的完整结构,您可以在其中存储类型信息。

(a)…并使用元表

到目前为止,最常见的方法是使用元表。 (这还允许在用户数据上添加方法或重写运算符,但元表也可以完全为空。)使用元表的一种好方法是通过 luaL_setmetatable luaL_checkudata 。 (您可以通过 luaL_newmetatable 创建一个元表,该表应按如下方式使用

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 th参数是否为Foo并获取它(否则抛出错误)。

(b)…并使用“uservalue”插槽

(此“用户值”主要旨在允许为一种类型的所有值拥有一个共享的元表,但仍允许不同的按值信息,因此它通常会保存另一张表……因此,这种方法并不常见,仅用于完整性。)

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

样本方案(仅经过最少测试):
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;
}

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

2.手动包裹您的物体…

一种。 …在C/++中(添加类型标签)

确保您推送到Lua的所有值共享一个公共(public) header 。在C语言中,使所有struct都以一个(或多个)公共(public)字段开头很容易,C++类可能有所不同。但是您可以定义一个包装器类型,例如(再次使用C样本…)
typedef struct {
    int tag;
    void *ptr;
} Tagged;

并且仅推送Tagged值(可以在推送它们之前将其包装起来……或者默认情况下始终保留,甚至可能通过在值中内嵌type标签并避免额外的指针间接操作来实现)。返回值时,请先检查tag,然后再使用ptr(或其他值,如果内联)。

b。 …在Lua中(将表中的所有内容包装起来)

确保在Lua中处理lightuserdata的任何地方,都将其包装在表中。这样,您又有了存储额外类型信息的插槽。

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

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

在Lua或C++端,您都可以将void*/light userdata中的 map 保留为类型(ID)。

在C++方面,可能有一些现成的数据结构可用于此目的。 (我不是C++程序员,所以我不知道– std::map可能有用吗?我不知道这样做的难易程度。)

在Lua方面,这很简单,例如创建和存储表(例如在州注册表中),然后通过lut[ptr] = tag添加类型信息或通过lut[ptr] == tag检查类型(或者在C(+ +)一侧)。

如果在LUT上设置一个元表以使键变弱(__mode = "k"),则垃圾收集器将自动释放Lua状态中其他位置不再引用的字段。否则,您将必须以某种方式进行手动管理-最简单的方法可能是扩展free以同时设置lut[ptr] = nil(这可以无条件地进行,无论该值是否已传递给Lua都无关紧要)。

版本1.a可能是最简单的。 (如果您无法将事物 anchor 定在Lua状态,请尝试解决此问题,因为这样做会使事情变得容易得多。)如果您绝对无法修复 anchor 定,则版本2.a或2.b可能有意义。变体1.b和3大多是学术性的……它们可能在非常特殊的情况下为您省钱,但通常不是最好的主意。

对于特定的游戏,请使用1.a(或从一开始就设计它并确保不需要元表,请选择2.a。元表是Lua的核心功能之一,请不要使用它们。您会错过很多东西。但是,如果您知道自己在做什么,那么使用不带元表的Lua仍然是一个有效的选择。)

关于c++ - 如何确定lightuserdata的类型?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44186569/

10-12 13:09
查看更多