internal object[] Call(object[] args, IntPtr?stateOverride = null, bool isResuming = false) { var state = stateOverride ?? MainState; Debug.Assert(LuaApi.GetMainState(state) == MainState, "State override did not match main state."); Debug.Assert(isResuming || LuaApi.Type(state, -1) == LuaType.Function, "Stack doesn't have function on top."); var oldTop = isResuming ? 0 : LuaApi.GetTop(state) - 1; var numArgs = args.Length; if (oldTop + numArgs > LuaApi.MinStackSize && !LuaApi.CheckStack(state, numArgs)) { throw new LuaException("Not enough stack space for arguments."); } foreach (var arg in args) { PushObject(arg, state); } var status = isResuming ? LuaApi.Resume(state, MainState, numArgs) : LuaApi.PCallK(state, numArgs); if (status != LuaStatus.Ok && status != LuaStatus.Yield) { var errorMessage = LuaApi.ToString(state, -1); LuaApi.Pop(state, 1); throw new LuaException(errorMessage); } // This is a fast path for functions returning nothing since we avoid a SetTop call. var top = LuaApi.GetTop(state); if (top == oldTop) { return(EmptyObjectArray); } if (top + 1 > LuaApi.MinStackSize && !LuaApi.CheckStack(state, 1)) { throw new LuaException("Not enough scratch stack space."); } var results = ToObjects(oldTop + 1, top, state); LuaApi.SetTop(state, oldTop); return(results); }
/// <summary> /// Initializes a new instance of the <see cref="ObjectBinder"/> class for the given <see cref="Lua"/> environment. /// </summary> /// <param name="lua">The <see cref="Lua"/> environment.</param> public ObjectBinder(Lua lua) { _lua = lua; // The __index metamethod can be cached in certain situations, such as methods and events. This can significantly improve // performance. var wrapIndexFunction = (LuaFunction)lua.DoString(@" local error = error local cache = setmetatable({}, { __mode = 'k' }) return function(fn) return function(obj, index) local objcache = cache[obj] if objcache == nil then objcache = {} cache[obj] = objcache end if objcache[index] ~= nil then return objcache[index] end local success, iscached, result = fn(obj, index) if not success then error(iscached, 2) end if iscached then objcache[index] = result end return result end end")[0]; // To raise Lua errors, we will do so directly in Lua. Doing so by P/Invoking luaL_error is problematic because luaL_error // performs a longjmp, which can destroy stack information. _wrapFunction = (LuaFunction)lua.DoString(@" local error = error local function helper(success, ...) if not success then error(..., 2) end return ... end return function(fn) return function(...) return helper(fn(...)) end end")[0]; // Storing the LuaCFunction delegates prevents the .NET GC from garbage collecting them. _objectMetamethods = new Dictionary <string, LuaCFunction> { ["__call"] = CallObject, ["__index"] = IndexObject, ["__newindex"] = NewIndexObject, ["__add"] = AddObject, ["__sub"] = SubObject, ["__mul"] = MulObject, ["__div"] = DivObject, ["__mod"] = ModObject, ["__band"] = BandObject, ["__bor"] = BorObject, ["__bxor"] = BxorObject, ["__shr"] = ShrObject, ["__shl"] = ShlObject, ["__eq"] = EqObject, ["__lt"] = LtObject, ["__le"] = LeObject, ["__unm"] = UnmObject, ["__bnot"] = BnotObject, ["__gc"] = Gc, ["__tostring"] = ToString }; _proxyCallObjectDelegate = ProxyCallObject; _typeMetamethods = new Dictionary <string, LuaCFunction> { ["__call"] = CallType, ["__index"] = IndexType, ["__newindex"] = NewIndexType, ["__gc"] = Gc, ["__tostring"] = ToString }; _proxyCallTypeDelegate = ProxyCallType; NewMetatable(ObjectMetatable, _objectMetamethods); NewMetatable(TypeMetatable, _typeMetamethods); void NewMetatable(string name, Dictionary <string, LuaCFunction> metamethods) { LuaApi.NewMetatable(lua.MainState, name); foreach (var kvp in metamethods) { LuaApi.PushString(lua.MainState, kvp.Key); var isWrapped = kvp.Key != "__gc" && kvp.Key != "__tostring"; if (kvp.Key == "__index") { wrapIndexFunction.PushOnto(lua.MainState); } else if (isWrapped) { _wrapFunction.PushOnto(lua.MainState); } LuaApi.PushCClosure(lua.MainState, kvp.Value, 0); if (isWrapped) { LuaApi.PCallK(lua.MainState, 1, 1); } LuaApi.SetTable(lua.MainState, -3); } // Setting __metatable to false will hide the metatable, protecting it from getmetatable and setmetatable. LuaApi.PushString(lua.MainState, "__metatable"); LuaApi.PushBoolean(lua.MainState, false); LuaApi.SetTable(lua.MainState, -3); LuaApi.Pop(lua.MainState, 1); } }