Lua-为什么C函数作为用户数据返回? [英] Lua - Why are C functions returned as userdata?
问题描述
我正在为引擎制作游戏脚本,并且正在使用一个元表将函数从表(存储玩家的自定义函数和数据)中的功能重定向到userdata对象(这是我的Player类的主要实现).用户可以使用self
来引用两者.
I'm working on game scripting for my engine and am using a metatable to redirect functions from a table (which stores custom functions and data for players) to a userdata object (which is the main implementation for my Player class) so that users may use self
to refer to both.
这是我在Player
类中的C#中进行绑定的方式:
This is how I do my binding in C# in the 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
类,我实现了一个方法bool IsCommandActive(string name)
.当我需要使用self
调用此方法时,它需要使用userdata
对象而不是表,否则会出现以下错误:
For my Player
class, I implement a method, bool IsCommandActive(string name)
. When I need to call this method using self
, it needs to use the userdata
object, rather than the table, otherwise I get the following error:
NLua.Exceptions.LuaScriptException:'实例方法'IsCommandActive' 需要一个非空的目标对象"
NLua.Exceptions.LuaScriptException: 'instance method 'IsCommandActive' requires a non null target object'
出于明显的原因.这是因为self
引用表,而不是userdata.因此,我实现了一个元表,以便它可以使用self
来引用任何一个.该实现取自此处,但这是我的特定变体(我的用户数据存储在名为data
的索引中:
For obvious reasons. This is because self
refers to the table, not the userdata. So I implemented a metatable so that it may use self
to refer to either. The implementation is taken from here, but here is my particular variant (my userdata is stored in an index called 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
的输出):
Now to the meat of my question. Notice how I print type(k)
and print(k)
under the elseif
. This is because I noticed that I was still getting the same error, so I wanted to do some debugging. When doing so, I got the following output (which I believe is for IsCommandActive
):
userdata: 0BD47190
不是要打印'function'
吗?为什么打印'userdata: 0BD47190'
?最后,如果确实如此,如何确定该值是否为C函数,以便进行适当的重定向?
Shouldn't it be printing 'function'
? Why is it printing 'userdata: 0BD47190'
? Finally, if that is indeed the case, how can I detect if the value is a C function so I may do the proper redirection?
推荐答案
在大量阅读了有关元表的知识之后,我设法解决了我的问题.
After lots of reading about metatables, I managed to solve my problem.
要回答标题中的问题,很显然,NLua决定做什么,并且具体取决于实现.在任何其他绑定中,它很可能返回为function
,但是对于NLua显然不是这种情况.
To answer the question in the title, it's apparently what NLua just decides to do and is implementation-specific. In any other bindings, it may very well return as function
, but such is apparently not the case for NLua.
关于我如何完成自己想要的事情,我必须定义元表__index
和__newindex
函数:
As for how I managed to accomplish what I wanted, I had to define the metatable __index
and __newindex
functions:
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
包装器表中未找到 ,则它会尝试从Player.data
中的userdata
检索它.如果那里不存在,那么Lua会做它的事情并返回nil
.
What __index
does is override how tables are indexed. In this implementation, if key
is not found in the Player
wrapper table, then it goes and tries to retrieve it from the userdata
in Player.data
. If it doesn't exist there, then Lua just does its thing and returns nil
.
就这样,我可以从userdata
中检索字段!但是,我很快开始注意到,如果我在Lua中设置了self.Pos
,则Player.Pos
不会不会在支持C#代码中进行更新.很快,我意识到这是因为Pos
在Player
包装器表中生成未命中,这意味着它正在为表创建一个新的Pos
字段,因为它实际上不存在!
And just like that, I could retrieve fields from the userdata
! I quickly began to notice, however, that if I set, for instance, self.Pos
in Lua, then the Player.Pos
would not update in the backing C# code. Just as quickly, I realized that this was because Pos
was generating a miss in the Player
wrapper table, which meant that it was creating a new Pos
field for the table since it actually did not exist!
这不是预期的行为,因此我也必须覆盖__newindex
.在此特定实现中,它检查Player.data
(userdata
)是否具有key
,如果是,则为该特定key
设置数据.如果userdata
中不存在它,则应该为Player
包装器表创建它,因为它应该是用户自定义Player
实现的一部分.
This was not the intended behavior, so I had to override __newindex
as well. In this particular implementation, it checks if the Player.data
(userdata
) has the key
, and if so, sets the data for that particular key
. If it does not exist in the userdata
, then it should create it for the Player
wrapper table because it should be part of the user's custom Player
implementation.
这篇关于Lua-为什么C函数作为用户数据返回?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!