我正在使用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的方法,go
内GameObject* go = (GameObject*)lua_touserdata(luaState, 1);
行的变量LuaScript::SetGameObjectActive(lua_State * luaState)
格式不正确(nullptr&# 39; s,内存错误等)因为我将数据分配为GameObject。
答案 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只存储一个指针。您寻找的信息不存在,您必须以某种方式添加它。
让我们看一下这样做的几种方法:
完整的用户数据由Lua分配并获得所有花哨的Lua功能(元表,垃圾收集,关联"用户值",...)。这意味着您的值不仅仅是一个原始指针,而是一个包含许多插槽的完整结构,您可以在其中存储类型信息。
到目前为止,最常见的方法是使用元表。 (这也允许在userdata上添加方法或覆盖运算符,但是metatable也可以完全为空。)使用元表的一种好方法是通过luaL_setmetatable
和luaL_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}得到它(否则抛出错误)。
(这"用户值"主要是为了允许为一种类型的所有值提供单个共享元表,但仍然允许不同的每值信息,因此它通常会包含另一个表...因此,这种方法并不常见,只是为了完整性而包括在内。)
使用lua_setuservalue
/ lua_getuservalue
,您只需在用户值'中存储任意Lua值即可。您的用户数据的插槽。如果您使用的是所有类型的枚举,则只需在该字段中存储整数即可。
样本方案(仅经过最低限度的测试):
Foo
(对于这两种完整的userdata方法,请记住Lua进行垃圾收集,如果它不再在Lua堆栈上或存储在Lua状态的某个表中,它将释放你的对象!)
确保您推送到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中处理lightuserdata的任何地方,将其包装在表格中。通过这种方式,您再次拥有用于存储额外类型信息的插槽。
一个简单的方案是ptr
< userdata指针> { ptr =
<类型名称或数字ID或...> , type=
。到目前为止,所有使用裸用户数据的函数现在都期望具有(至少)这两个字段的表,然后在提取和使用}
之前检查v.type
。
无论是在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仍然是一个有效的选择。)