Intereting Posts

Lua – Почему функции C возвращаются как userdata?

Я работаю над сценарием для своего движка и использую метатет для перенаправления функций из таблицы (которая хранит пользовательские функции и данные для игроков) в объект userdata (который является основной реализацией для моего classа Player), чтобы пользователи могли используйте self чтобы ссылаться на оба.

Вот как я делаю свою привязку в C # в classе Player :

  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 

Для моего classа Player я реализую метод bool IsCommandActive(string name) . Когда мне нужно вызвать этот метод, используя self , ему нужно использовать объект userdata , а не таблицу, иначе я получаю следующую ошибку:

NLua.Exceptions.LuaScriptException: ‘метод экземпляра’ IsCommandActive ‘требует не нулевого целевого объекта’

По понятным причинам. Это связано с тем, что self ссылается на таблицу, а не на пользовательские данные. Поэтому я реализовал метатебель, чтобы он мог использовать self чтобы ссылаться на него. Реализация взята здесь , но вот мой конкретный вариант (мои пользовательские данные хранятся в индексе, называемом 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 , что я следую с помощью setmetatable .

Теперь к мясу моего вопроса. Обратите внимание, как я печатаю type(k) и print(k) в elseif . Это потому, что я заметил, что я все еще получаю ту же ошибку, поэтому мне захотелось сделать некоторую отладку. При этом я получил следующий результат (который, я считаю, для IsCommandActive ):

 userdata: 0BD47190 

Не следует ли печатать 'function' ? Почему он печатает 'userdata: 0BD47190' ? Наконец, если это действительно так, как я могу определить, является ли значение функцией C, поэтому я могу сделать правильное redirect?

    любые функции или объекты, которые являются частью classа C, являются userdata`

    Это неправда. Функция – это функция, независимо от того, является ли она родной или написана в Lua. Проверка типа нативной функции приведет к печати «функции».

    Это может быть только то, что ваше связующее решение использует пользовательские данные с установленным на нем __call , чтобы показать маршаллеру с некоторым связанным с ним состоянием / контекстом. Но это не означает, что каждая нативная функция является пользовательской датой или что каждая привязка lib будет реализована одинаково. Это можно сделать так же, используя table Lua вместо userdata. Не могли бы вы сказать, что «каждая нативная функция – это таблица»? 🙂

    После многих чтений о метаматериалах мне удалось решить мою проблему.

    Чтобы ответить на вопрос в названии, очевидно, что NLua просто решает сделать это и зависит от реализации. В любых других привязках он может очень хорошо вернуться как function , но это, по-видимому, не относится к NLua.

    Что касается того, как мне удалось выполнить то, что я хотел, мне пришлось определить __newindex функции __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 делает, переопределяет способ индексирования таблиц. В этой реализации, если key не найден в таблице обертки Player , он идет и пытается извлечь его из пользовательских данных в Player.data . Если он там не существует, то Lua просто делает свою вещь и возвращает nil .

    И просто так, я мог бы получить поля из userdata ! Однако я быстро начал замечать, что если я установил, например, self.Pos в Lua, то Player.Pos не будет обновляться в резервном коде C #. Так же быстро, я понял, что это потому, что Pos создавал пропущенную в таблице обложки Player , что означало, что он создавал новое поле Pos для таблицы, так как ее на самом деле не существовало!

    Это было не намеренное поведение, поэтому мне пришлось переопределить __newindex . В этой конкретной реализации он проверяет, имеет ли Player.data ( userdata ) key , и если да, то задает данные для этого конкретного key . Если он не существует в userdata , он должен создать его для таблицы обертки Player потому что он должен быть частью пользовательской реализации Player .