我正在为我的引擎制作游戏脚本,并使用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
来引用它们。实现取自here,但这是我的特定变体(我的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
进行操作。
现在回答我的问题。请注意我如何在type(k)
下打印print(k)
和elseif
。这是因为我注意到我仍然遇到同样的错误,所以我想做一些调试。这样做时,我得到了以下输出(我认为是IsCommandActive
):
userdata: 0BD47190
不应该打印'function'
吗?为什么要打印'userdata: 0BD47190'
?最后,如果确实如此,我如何检测该值是否为C函数,以便我可以进行正确的重定向?
答案 0 :(得分:0)
经过大量关于元表的阅读后,我设法解决了我的问题。
为了回答标题中的问题,它显然是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
的作用是覆盖表的索引方式。在此实现中,如果在key
包装器表中找到Player
不,则它会尝试从userdata
Player.data
中检索它}。如果它不存在,那么Lua只是做它的事情并返回nil
。
就这样,我可以从userdata
中检索字段!然而,我很快就注意到,如果我在Lua中设置self.Pos
,那么Player.Pos
将在支持C#代码中不更新。同样快,我意识到这是因为Pos
在Player
包装器表中产生了一个未命中,这意味着它正在为表创建一个新的Pos
字段,因为它确实不存在!
这不是预期的行为,所以我也必须覆盖__newindex
。在此特定实现中,它会检查Player.data
(userdata
)是否具有key
,如果是,则设置该特定key
的数据。如果userdata
中不存在,则应为Player
包装器表创建它,因为它应该是用户自定义Player
实现的一部分。
答案 1 :(得分:0)
属于C类的任何函数或对象都是userdata`
事实并非如此。函数是一个函数,无论它是原生的还是用Lua编写的。检查本机函数的类型将打印“function”。
只是可能是你的绑定解决方案正在使用userdata
并在其上设置__call
元方法,以向marshaller公开与之关联的某些状态/上下文。但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。使用Lua table
而不是userdata可以有效地完成它。那么你会说“每个本地函数都是一个表”吗? :)