如何确定lightuserdata的类型?

时间:2017-05-25 17:36:09

标签: c++ lua scripting

我正在使用Lua C API将Lua Scripting添加到我的游戏引擎(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)格式不正确(nullptr&# 39; s,内存错误等)因为我将数据分配为GameObject。

2 个答案:

答案 0 :(得分:3)

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

如果放入lightuserdata的类型派生自同一个基类,并且您有运行时类型信息,则可以使用C ++语言查询对象的实际类型。

否则使用完整的用户数据是最好的方法。

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

返回一段新内存

new (mem) Type( params );

创建正确的类型

lua_setmetatable 

允许lua理解类型,可以在C / C ++中用lua_getmetatable查询

关于此的权威书籍是Programming in lua,建议使用。

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

   // 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.

答案 1 :(得分:1)

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

让我们看一下这样做的几种方法:

1。切换到完整的userdata ...(即让Lua包装你的对象)

完整的用户数据由Lua分配并获得所有花哨的Lua功能(元表,垃圾收集,关联"用户值",...)。这意味着您的值不仅仅是一个原始指针,而是一个包含许多插槽的完整结构,您可以在其中存储类型信息。

(a)......并使用metatables

到目前为止,最常见的方法是使用元表。 (这也允许在userdata上添加方法或覆盖运算符,但是metatable也可以完全为空。)使用元表的一种好方法是通过luaL_setmetatableluaL_checkudata。 (您可以通过luaL_newmetatable创建一个metatable,它可以像

一样使用
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参数是否为{{1}得到它(否则抛出错误)。

(b)...并使用' uservalue'槽

(这"用户值"主要是为了允许为一种类型的所有值提供单个共享元表,但仍然允许不同的每值信息,因此它通常会包含另一个表...因此,这种方法并不常见,只是为了完整性而包括在内。)

使用lua_setuservalue / lua_getuservalue,您只需在用户值'中存储任意Lua值即可。您的用户数据的插槽。如果您使用的是所有类型的枚举,则只需在该字段中存储整数即可。

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

Foo

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

2。手动包裹你的对象......

一个。 ...在C / ++(添加类型标签)

确保您推送到Lua的所有值共享一个公共标头。在C中,很容易使所有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; } 以一个(或多个)公共字段开头,C ++类可能不同。但你可以定义一个包装类型,如(C sample again ...)

struct

并且仅推送typedef struct { int tag; void *ptr; } Tagged; 值(可以在推送它们之前立即包装...或者总是默认情况下,甚至可能通过在您的值中内联类型标记并避免额外的指针间接)。获取值后,首先检查Tagged,然后再使用tag(或其余值,如果内联)。

湾...在Lua中(将所有内容包装在表格中)

确保在Lua中处理lightuserdata的任何地方,将其包装在表格中。通过这种方式,您再次拥有用于存储额外类型信息的插槽。

一个简单的方案是ptr< userdata指针> { ptr =<类型名称或数字ID或...> , type=。到目前为止,所有使用裸用户数据的函数现在都期望具有(至少)这两个字段的表,然后在提取和使用}之前检查v.type

3。保留(单独)查找表

无论是在Lua还是在C ++方面,您都可以保留从v.ptr / light userdata到type(ID)的地图。

在C ++方面,可能有一些现成的数据结构可供您使用。 (我不是C ++程序员,所以我不知道 - void*可能有用吗?要做到这一点是多么容易/多难,我不知道。)

在Lua方面,这就像创建&存储一个表(例如在状态的注册表中),然后通过std::map添加类型信息或通过lut[ptr] = tag进行检查(或者通过等效的Lua API函数序列(如果在C上完成)(+ +)方)。

如果在LUT上设置metatable以使密钥变弱(lut[ptr] == tag),则不再在Lua状态的其他位置引用的字段将被垃圾收集器自动释放。否则,您必须以某种方式手动管理它 - 最简单的方法可能是将__mode = "k"扩展为free(这可以无条件完成,不重要如果价值传递给Lua)。

变异1.a可能是最简单的。 (如果您无法保持Lua状态的固定,请尝试修复它,因为它会使事情变得更容易。)如果您无法修复锚定,则变体2.a或2.b可能有意义。变种1.b和3主要是学术性的...它们可能会在非常特殊的情况下拯救你,但通常不是最好的主意。

对于游戏而言,使用1.a(或者如果你从一开始就设计它并且确定你不需要metatables,请选择2.a.Metatables是Lua的核心功能之一,并且不使用它们你会错过许多东西。但如果你知道你正在做什么,那么使用没有元数据的Lua仍然是一个有效的选择。)