Lua - Why are C functions returned as userdata?

≡放荡痞女 提交于 2019-12-24 08:31:00

问题


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

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