Lua – 为什么C函数作为userdata返回?
我正在为我的引擎开发游戏脚本,并使用metatable将函数从表(存储自定义函数和播放器数据)重定向到userdata对象(这是我的Player类的主要实现),以便用户可以用self
来指代两者。
这就是我在Player
类中使用C#进行绑定的方法:
state.NewTable("Player"); // Create Player wrapper table state["Player.data"] = this; // Bind Player.data to the Player class state.NewTable("mt"); // Create temp table for metatable state.DoString(@"mt.__index = function(self,key) local k = self.data[key] if key == 'data' or not k then return rawget(self, key) elseif type(k) ~= 'function' then print(type(k)) print(k) return k else return function(...) if self == ... then return k(self.data, select(2,...)) else return k(...) end end end end"); state.DoString("setmetatable(Player, mt)"); // Change Player's metatable
对于我的Player
类,我实现了一个方法, bool IsCommandActive(string name)
。 当我需要使用self
调用此方法时,它需要使用userdata
对象而不是表,否则我会收到以下错误:
NLua.Exceptions.LuaScriptException:’实例方法’IsCommandActive’需要非空目标对象’
原因很明显。 这是因为self
引用表而不是userdata。 所以我实现了一个metatable,以便它可以使用self
来引用它们。 实现是从这里开始的 ,但这是我的特定变体(我的userdata存储在一个名为data
的索引中:
mt.__index = function(self,key) local k = self.data[key] if key == 'data' or not k then return rawget(self, key) elseif type(k) ~= 'function' then print(type(k)) print(k) return k else return function(...) if self == ... then return k(self.data, select(2,...)) else return k(...) end end end end end
显然,我遵循使用setmetatable
。
现在我的问题。 请注意我如何在elseif
下打印type(k)
和print(k)
。 这是因为我注意到我仍然遇到同样的错误,所以我想做一些调试。 这样做时,我得到了以下输出(我认为是IsCommandActive
):
userdata: 0BD47190
不应该打印'function'
吗? 为什么打印'userdata: 0BD47190'
? 最后,如果确实如此,我如何检测该值是否为C函数,以便我可以进行正确的重定向?
属于C类的任何函数或对象都是userdata`
这不是真的。 函数是一个函数,无论它是原生的还是用Lua编写的。 检查本机函数的类型将打印“function”。
只是可能是你的绑定解决方案正在使用带有__call
元方法的userdata
,以向一个marshaller公开一些与之关联的状态/上下文。 但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。 使用Lua table
而不是userdata可以有效地完成它。 那么你会说“每个本地函数都是一个表”吗? 🙂
经过大量关于元表的阅读,我设法解决了我的问题。
为了回答标题中的问题,它显然是NLua刚刚决定做的,并且是特定于实现的。 在任何其他绑定中,它可能很好地返回function
,但对于NLua来说显然不是这样。
至于我如何设法完成我想要的,我必须定义metatable __index
和__newindex
函数:
state.NewTable("Player"); state["Player.data"] = this; state.NewTable("mt"); state.DoString(@"mt.__index = function(self,key) local k = self.data[key] local metatable = getmetatable(k) if key == 'data' or not k then return rawget(self, key) elseif type(k) ~= 'function' and (metatable == nil or metatable.__call == nil) then return k else return function(...) if self == ... then return k(self.data, select(2,...)) else return k(...) end end end end"); state.DoString(@"mt.__newindex = function(self, key, value) local c = rawget(self, key, value) if not c then local dataHasKey = self.data[key] ~= key if not dataHasKey then rawset(self, key, value) else self.data[key] = value end else rawset(self, key, value) end end"); state.DoString("setmetatable(Player, mt)");
__index
所做的是覆盖表的索引方式。 在此实现中,如果在Player
包装器表中找不到key
,则它会尝试从Player.data
的userdata
检索它。 如果那里不存在,那么Lua只做它的事情并返回nil
。
就这样,我可以从userdata
检索字段! 然而,我很快就注意到,如果我在Lua中设置self.Pos
,那么Player.Pos
将不会在支持C#代码中更新。 同样快,我意识到这是因为Pos
在Player
包装器表中产生了一个错过,这意味着它正在为表创建一个新的Pos
字段,因为它实际上并不存在!
这不是预期的行为,所以我不得不重写__newindex
。 在此特定实现中,它检查Player.data
( userdata
)是否具有key
,如果是,则设置该特定key
的数据。 如果它在userdata
中不存在,那么它应该为Player
包装器表创建它,因为它应该是用户的自定义Player
实现的一部分。