问题
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.
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
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: 'instance method 'IsCommandActive' requires a non null target object'
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
Which I follow by using setmetatable
, obviously.
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
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?
回答1:
any functions or objects that are part of a C class are userdata`
This is not true. Function is a function, no matter if it's native or written in Lua. Checking the type of a native function would print "function".
It's just it can be that your binding solution is using userdata
with __call
metamethod set on it, to expose a marshaller with some state/context associated with it. But it doesn't mean that every native function is a userdata, or that every binding lib will be implemented same way. It could be done effectively the same using Lua table
instead of userdata. Would you be telling then that "every native function is a table"? :)
回答2:
After lots of reading about metatables, I managed to solve my problem.
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.
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)");
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
.
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!
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.
来源:https://stackoverflow.com/questions/49220242/lua-why-are-c-functions-returned-as-userdata