Lua - 为什么 C 函数作为用户数据返回?
Lua - Why are C functions returned as userdata?
我正在为我的引擎编写游戏脚本,我正在使用元table将函数从table(为玩家存储自定义函数和数据)重定向到用户数据对象(这是我的播放器 class) 的主要实现,因此用户可以使用 self
来指代两者。
这就是我在 Player
class:
中使用 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
class,我实现了一个方法 bool IsCommandActive(string name)
。当我需要使用 self
调用此方法时,它需要使用 userdata
对象,而不是 table,否则我会得到以下错误:
NLua.Exceptions.LuaScriptException: 'instance method 'IsCommandActive'
requires a non null target object'
原因显而易见。这是因为 self
指的是 table,而不是用户数据。所以我实现了一个 metatable 以便它可以使用 self
来引用任何一个。该实现取自 here,但这是我的特定变体(我的用户数据存储在名为 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 函数以便我可以进行正确的重定向?
阅读了大量有关元tables 的资料后,我设法解决了我的问题。
要回答标题中的问题,显然是NLua刚刚决定做的事情,并且是implementation-specific。在任何其他绑定中,它很可能 return 与 function
一样,但 NLua.
显然不是这种情况
至于我如何设法完成我想要的,我必须定义元table __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
所做的是覆盖 table 的索引方式。在此实现中,如果在 Player
包装器 table 中找到 key
而不是 ,则它会尝试从 [=17] 中检索它=] 在 Player.data
中。如果它在那里不存在,那么 Lua 只是做它的事情并且 returns nil
.
就这样,我可以从 userdata
中检索字段!但是,我很快开始注意到,如果我在 Lua 中设置 self.Pos
,那么 Player.Pos
将 而不是 支持 C# 代码。同样迅速,我意识到这是因为 Pos
在 Player
包装器 table 中生成了一个未命中,这意味着它正在为 Pos
创建一个新字段=54=] 因为它实际上并不存在!
这不是预期的行为,所以我也不得不覆盖 __newindex
。在这个特定的实现中,它检查 Player.data
(userdata
) 是否有 key
,如果有,则为那个特定的 key
设置数据。如果它在 userdata
中不存在,那么它应该为 Player
包装器 table 创建它,因为它应该是用户自定义 Player
实现的一部分。
any functions or objects that are part of a C class are userdata`
这不是真的。函数就是函数,不管是原生的还是用Lua写的。检查本机函数的类型将打印 "function".
这可能是因为您的绑定解决方案正在使用 userdata
并在其上设置了 __call
元方法,以公开一个编组器与一些 state/context 相关联。但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。使用 Lua table
而不是 userdata 可以有效地完成相同的操作。那你会说 "every native function is a table" 吗? :)
我正在为我的引擎编写游戏脚本,我正在使用元table将函数从table(为玩家存储自定义函数和数据)重定向到用户数据对象(这是我的播放器 class) 的主要实现,因此用户可以使用 self
来指代两者。
这就是我在 Player
class:
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
class,我实现了一个方法 bool IsCommandActive(string name)
。当我需要使用 self
调用此方法时,它需要使用 userdata
对象,而不是 table,否则我会得到以下错误:
NLua.Exceptions.LuaScriptException: 'instance method 'IsCommandActive' requires a non null target object'
原因显而易见。这是因为 self
指的是 table,而不是用户数据。所以我实现了一个 metatable 以便它可以使用 self
来引用任何一个。该实现取自 here,但这是我的特定变体(我的用户数据存储在名为 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 函数以便我可以进行正确的重定向?
阅读了大量有关元tables 的资料后,我设法解决了我的问题。
要回答标题中的问题,显然是NLua刚刚决定做的事情,并且是implementation-specific。在任何其他绑定中,它很可能 return 与 function
一样,但 NLua.
至于我如何设法完成我想要的,我必须定义元table __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
所做的是覆盖 table 的索引方式。在此实现中,如果在 Player
包装器 table 中找到 key
而不是 ,则它会尝试从 [=17] 中检索它=] 在 Player.data
中。如果它在那里不存在,那么 Lua 只是做它的事情并且 returns nil
.
就这样,我可以从 userdata
中检索字段!但是,我很快开始注意到,如果我在 Lua 中设置 self.Pos
,那么 Player.Pos
将 而不是 支持 C# 代码。同样迅速,我意识到这是因为 Pos
在 Player
包装器 table 中生成了一个未命中,这意味着它正在为 Pos
创建一个新字段=54=] 因为它实际上并不存在!
这不是预期的行为,所以我也不得不覆盖 __newindex
。在这个特定的实现中,它检查 Player.data
(userdata
) 是否有 key
,如果有,则为那个特定的 key
设置数据。如果它在 userdata
中不存在,那么它应该为 Player
包装器 table 创建它,因为它应该是用户自定义 Player
实现的一部分。
any functions or objects that are part of a C class are userdata`
这不是真的。函数就是函数,不管是原生的还是用Lua写的。检查本机函数的类型将打印 "function".
这可能是因为您的绑定解决方案正在使用 userdata
并在其上设置了 __call
元方法,以公开一个编组器与一些 state/context 相关联。但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。使用 Lua table
而不是 userdata 可以有效地完成相同的操作。那你会说 "every native function is a table" 吗? :)