Lua - Why are C functions returned as userdata?

匿名 (未验证) 提交于 2019-12-03 01:06:02

问题:

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.



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!