using System; using System.IO; using System.Reflection; using System.Collections.Generic; using System.Collections.Concurrent; using System.Runtime.InteropServices; using KeraLua; using NLua.Method; using NLua.Exceptions; using NLua.Extensions; #if __IOS__ || __TVOS__ || __WATCHOS__ using ObjCRuntime; #endif using LuaState = KeraLua.Lua; using LuaNativeFunction = KeraLua.LuaFunction; namespace NLua { public class ObjectTranslator { // Compare cache entries by exact reference to avoid unwanted aliases private class ReferenceComparer : IEqualityComparer { public new bool Equals(object x, object y) { if (x != null && y != null && x.GetType() == y.GetType() && x.GetType().IsValueType && y.GetType().IsValueType) return x.Equals(y); // Special case for boxed value types return ReferenceEquals(x, y); } public int GetHashCode(object obj) { return obj.GetHashCode(); } } private static readonly LuaNativeFunction _registerTableFunction = RegisterTable; private static readonly LuaNativeFunction _unregisterTableFunction = UnregisterTable; private static readonly LuaNativeFunction _getMethodSigFunction = GetMethodSignature; private static readonly LuaNativeFunction _getConstructorSigFunction = GetConstructorSignature; private static readonly LuaNativeFunction _importTypeFunction = ImportType; private static readonly LuaNativeFunction _loadAssemblyFunction = LoadAssembly; private static readonly LuaNativeFunction _ctypeFunction = CType; private static readonly LuaNativeFunction _enumFromIntFunction = EnumFromInt; // object to object # readonly Dictionary _objectsBackMap = new Dictionary(new ReferenceComparer()); // object # to object (FIXME - it should be possible to get object address as an object #) readonly Dictionary _objects = new Dictionary(); readonly ConcurrentQueue finalizedReferences = new ConcurrentQueue(); internal EventHandlerContainer PendingEvents = new EventHandlerContainer(); MetaFunctions metaFunctions; List assemblies; internal CheckType typeChecker; internal Lua interpreter; /// /// We want to ensure that objects always have a unique ID /// int _nextObj; public MetaFunctions MetaFunctionsInstance => metaFunctions; public Lua Interpreter => interpreter; public IntPtr Tag => _tagPtr; readonly IntPtr _tagPtr; public ObjectTranslator(Lua interpreter, LuaState luaState) { _tagPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int))); this.interpreter = interpreter; typeChecker = new CheckType(this); metaFunctions = new MetaFunctions(this); assemblies = new List(); CreateLuaObjectList(luaState); CreateIndexingMetaFunction(luaState); CreateBaseClassMetatable(luaState); CreateClassMetatable(luaState); CreateFunctionMetatable(luaState); SetGlobalFunctions(luaState); } /* * Sets up the list of objects in the Lua side */ private void CreateLuaObjectList(LuaState luaState) { luaState.PushString("luaNet_objects"); luaState.NewTable(); luaState.NewTable(); luaState.PushString("__mode"); luaState.PushString("v"); luaState.SetTable(-3); luaState.SetMetaTable(-2); luaState.SetTable((int)LuaRegistry.Index); } /* * Registers the indexing function of CLR objects * passed to Lua */ private void CreateIndexingMetaFunction(LuaState luaState) { luaState.PushString("luaNet_indexfunction"); luaState.DoString(MetaFunctions.LuaIndexFunction); luaState.RawSet(LuaRegistry.Index); } /* * Creates the metatable for superclasses (the base * field of registered tables) */ private void CreateBaseClassMetatable(LuaState luaState) { luaState.NewMetaTable("luaNet_searchbase"); luaState.PushString("__gc"); luaState.PushCFunction(MetaFunctions.GcFunction); luaState.SetTable(-3); luaState.PushString("__tostring"); luaState.PushCFunction(MetaFunctions.ToStringFunction); luaState.SetTable(-3); luaState.PushString("__index"); luaState.PushCFunction(MetaFunctions.BaseIndexFunction); luaState.SetTable(-3); luaState.PushString("__newindex"); luaState.PushCFunction(MetaFunctions.NewIndexFunction); luaState.SetTable(-3); luaState.SetTop(-2); } /* * Creates the metatable for type references */ private void CreateClassMetatable(LuaState luaState) { luaState.NewMetaTable("luaNet_class"); luaState.PushString("__gc"); luaState.PushCFunction(MetaFunctions.GcFunction); luaState.SetTable(-3); luaState.PushString("__tostring"); luaState.PushCFunction(MetaFunctions.ToStringFunction); luaState.SetTable(-3); luaState.PushString("__index"); luaState.PushCFunction(MetaFunctions.ClassIndexFunction); luaState.SetTable(-3); luaState.PushString("__newindex"); luaState.PushCFunction(MetaFunctions.ClassNewIndexFunction); luaState.SetTable(-3); luaState.PushString("__call"); luaState.PushCFunction(MetaFunctions.CallConstructorFunction); luaState.SetTable(-3); luaState.SetTop(-2); } /* * Registers the global functions used by NLua */ private void SetGlobalFunctions(LuaState luaState) { luaState.PushCFunction(MetaFunctions.IndexFunction); luaState.SetGlobal("get_object_member"); luaState.PushCFunction(_importTypeFunction); luaState.SetGlobal("import_type"); luaState.PushCFunction(_loadAssemblyFunction); luaState.SetGlobal("load_assembly"); luaState.PushCFunction(_registerTableFunction); luaState.SetGlobal("make_object"); luaState.PushCFunction(_unregisterTableFunction); luaState.SetGlobal("free_object"); luaState.PushCFunction(_getMethodSigFunction); luaState.SetGlobal("get_method_bysig"); luaState.PushCFunction(_getConstructorSigFunction); luaState.SetGlobal("get_constructor_bysig"); luaState.PushCFunction(_ctypeFunction); luaState.SetGlobal("ctype"); luaState.PushCFunction(_enumFromIntFunction); luaState.SetGlobal("enum"); } /* * Creates the metatable for delegates */ private void CreateFunctionMetatable(LuaState luaState) { luaState.NewMetaTable("luaNet_function"); luaState.PushString("__gc"); luaState.PushCFunction(MetaFunctions.GcFunction); luaState.SetTable(-3); luaState.PushString("__call"); luaState.PushCFunction(MetaFunctions.ExecuteDelegateFunction); luaState.SetTable(-3); luaState.SetTop(-2); } /* * Passes errors (argument e) to the Lua interpreter */ internal void ThrowError(LuaState luaState, object e) { // We use this to remove anything pushed by luaL_where int oldTop = luaState.GetTop(); // Stack frame #1 is our C# wrapper, so not very interesting to the user // Stack frame #2 must be the lua code that called us, so that's what we want to use luaState.Where(1); var curlev = PopValues(luaState, oldTop); // Determine the position in the script where the exception was triggered string errLocation = string.Empty; if (curlev.Length > 0) errLocation = curlev[0].ToString(); string message = e as string; if (message != null) { // Wrap Lua error (just a string) and store the error location if (interpreter.UseTraceback) message += Environment.NewLine + interpreter.GetDebugTraceback(); e = new LuaScriptException(message, errLocation); } else { var ex = e as Exception; if (ex != null) { // Wrap generic .NET exception as an InnerException and store the error location if (interpreter.UseTraceback) ex.Data["Traceback"] = interpreter.GetDebugTraceback(); e = new LuaScriptException(ex, errLocation); } } Push(luaState, e); } /* * Implementation of load_assembly. Throws an error * if the assembly is not found. */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int LoadAssembly(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); int result = translator.LoadAssemblyInternal(state); var exception = translator.GetObject(state, -1) as LuaScriptException; if (exception != null) return state.Error(); return result; } private int LoadAssemblyInternal(LuaState luaState) { try { string assemblyName = luaState.ToString(1, false); Assembly assembly = null; Exception exception = null; try { assembly = Assembly.Load(assemblyName); } catch (BadImageFormatException) { // The assemblyName was invalid. It is most likely a path. } catch (FileNotFoundException e) { exception = e; } if (assembly == null) { try { assembly = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName)); } catch (FileNotFoundException e) { exception = e; } if (assembly == null) { AssemblyName mscor = assemblies[0].GetName(); AssemblyName name = new AssemblyName(); name.Name = assemblyName; name.CultureInfo = mscor.CultureInfo; name.Version = mscor.Version; name.SetPublicKeyToken(mscor.GetPublicKeyToken()); name.SetPublicKey(mscor.GetPublicKey()); assembly = Assembly.Load(name); if (assembly != null) exception = null; } if (exception != null) { ThrowError(luaState, exception); return 1; } } if (assembly != null && !assemblies.Contains(assembly)) assemblies.Add(assembly); } catch (Exception e) { ThrowError(luaState, e); return 1; } return 0; } internal Type FindType(string className) { foreach (var assembly in assemblies) { var klass = assembly.GetType(className); if (klass != null) return klass; } return null; } public bool TryGetExtensionMethod(Type type, string name, out MethodInfo method) { method = GetExtensionMethod(type, name); return method != null; } public MethodInfo GetExtensionMethod(Type type, string name) { return type.GetExtensionMethod(name, assemblies); } /* * Implementation of import_type. Returns nil if the * type is not found. */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int ImportType(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); return translator.ImportTypeInternal(state); } private int ImportTypeInternal(LuaState luaState) { string className = luaState.ToString(1, false); var klass = FindType(className); if (klass != null) PushType(luaState, klass); else luaState.PushNil(); return 1; } /* * Implementation of make_object. Registers a table (first * argument in the stack) as an object subclassing the * type passed as second argument in the stack. */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int RegisterTable(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); int result = translator.RegisterTableInternal(state); var exception = translator.GetObject(state, -1) as LuaScriptException; if (exception != null) return state.Error(); return result; } private int RegisterTableInternal(LuaState luaState) { if (luaState.Type(1) != LuaType.Table) { ThrowError(luaState, "register_table: first arg is not a table"); return 1; } LuaTable luaTable = GetTable(luaState, 1); string superclassName = luaState.ToString(2, false); if (string.IsNullOrEmpty(superclassName)) { ThrowError(luaState, "register_table: superclass name can not be null"); return 1; } var klass = FindType(superclassName); if (klass == null) { ThrowError(luaState, "register_table: can not find superclass '" + superclassName + "'"); return 1; } // Creates and pushes the object in the stack, setting // it as the metatable of the first argument object obj = CodeGeneration.Instance.GetClassInstance(klass, luaTable); PushObject(luaState, obj, "luaNet_metatable"); luaState.NewTable(); luaState.PushString("__index"); luaState.PushCopy(-3); luaState.SetTable(-3); luaState.PushString("__newindex"); luaState.PushCopy(-3); luaState.SetTable(-3); luaState.SetMetaTable(1); // Pushes the object again, this time as the base field // of the table and with the luaNet_searchbase metatable luaState.PushString("base"); int index = AddObject(obj); PushNewObject(luaState, obj, index, "luaNet_searchbase"); luaState.RawSet(1); return 0; } /* * Implementation of free_object. Clears the metatable and the * base field, freeing the created object for garbage-collection */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int UnregisterTable(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); int result = translator.UnregisterTableInternal(state); var exception = translator.GetObject(state, -1) as LuaScriptException; if (exception != null) return state.Error(); return result; } private int UnregisterTableInternal(LuaState luaState) { if (!luaState.GetMetaTable(1)) { ThrowError(luaState, "unregister_table: arg is not valid table"); return 1; } luaState.PushString("__index"); luaState.GetTable(-2); object obj = GetRawNetObject(luaState, -1); if (obj == null) { ThrowError(luaState, "unregister_table: arg is not valid table"); return 1; } var luaTableField = obj.GetType().GetField("__luaInterface_luaTable"); if (luaTableField == null) { ThrowError(luaState, "unregister_table: arg is not valid table"); return 1; } // ReSharper disable once PossibleNullReferenceException luaTableField.SetValue(obj, null); luaState.PushNil(); luaState.SetMetaTable(1); luaState.PushString("base"); luaState.PushNil(); luaState.SetTable(1); return 0; } /* * Implementation of get_method_bysig. Returns nil * if no matching method is not found. */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int GetMethodSignature(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); int result = translator.GetMethodSignatureInternal(state); var exception = translator.GetObject(state, -1) as LuaScriptException; if (exception != null) return state.Error(); return result; } private int GetMethodSignatureInternal(LuaState luaState) { ProxyType klass; object target; int udata = luaState.CheckUObject(1, "luaNet_class"); if (udata != -1) { klass = (ProxyType)_objects[udata]; target = null; } else { target = GetRawNetObject(luaState, 1); if (target == null) { ThrowError(luaState, "get_method_bysig: first arg is not type or object reference"); return 1; } klass = new ProxyType(target.GetType()); } string methodName = luaState.ToString(2, false); var signature = new Type[luaState.GetTop() - 2]; for (int i = 0; i < signature.Length; i++) signature[i] = FindType(luaState.ToString(i + 3, false)); try { var method = klass.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance, signature); var wrapper = new LuaMethodWrapper(this, target, klass, method); LuaNativeFunction invokeDelegate = wrapper.InvokeFunction; PushFunction(luaState, invokeDelegate); } catch (Exception e) { ThrowError(luaState, e); } return 1; } /* * Implementation of get_constructor_bysig. Returns nil * if no matching constructor is found. */ #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int GetConstructorSignature(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); int result = translator.GetConstructorSignatureInternal(state); var exception = translator.GetObject(state, -1) as LuaScriptException; if (exception != null) return state.Error(); return result; } private int GetConstructorSignatureInternal(LuaState luaState) { ProxyType klass = null; int udata = luaState.CheckUObject(1, "luaNet_class"); if (udata != -1) klass = (ProxyType)_objects[udata]; if (klass == null) { ThrowError(luaState, "get_constructor_bysig: first arg is invalid type reference"); return 1; } var signature = new Type[luaState.GetTop() - 1]; for (int i = 0; i < signature.Length; i++) signature[i] = FindType(luaState.ToString(i + 2, false)); try { ConstructorInfo constructor = klass.UnderlyingSystemType.GetConstructor(signature); var wrapper = new LuaMethodWrapper(this, null, klass, constructor); var invokeDelegate = wrapper.InvokeFunction; PushFunction(luaState, invokeDelegate); } catch (Exception e) { ThrowError(luaState, e); } return 1; } /* * Pushes a type reference into the stack */ internal void PushType(LuaState luaState, Type t) { PushObject(luaState, new ProxyType(t), "luaNet_class"); } /* * Pushes a delegate into the stack */ internal void PushFunction(LuaState luaState, LuaNativeFunction func) { PushObject(luaState, func, "luaNet_function"); } /* * Pushes a CLR object into the Lua stack as an userdata * with the provided metatable */ internal void PushObject(LuaState luaState, object o, string metatable) { int index = -1; // Pushes nil if (o == null) { luaState.PushNil(); return; } // Object already in the list of Lua objects? Push the stored reference. bool found = (!o.GetType().IsValueType || o.GetType().IsEnum) && _objectsBackMap.TryGetValue(o, out index); if (found) { luaState.GetMetaTable("luaNet_objects"); luaState.RawGetInteger(-1, index); // Note: starting with lua5.1 the garbage collector may remove weak reference items (such as our luaNet_objects values) when the initial GC sweep // occurs, but the actual call of the __gc finalizer for that object may not happen until a little while later. During that window we might call // this routine and find the element missing from luaNet_objects, but collectObject() has not yet been called. In that case, we go ahead and call collect // object here // did we find a non nil object in our table? if not, we need to call collect object var type = luaState.Type(-1); if (type != LuaType.Nil) { luaState.Remove(-2); // drop the metatable - we're going to leave our object on the stack return; } // MetaFunctions.dumpStack(this, luaState); luaState.Remove(-1); // remove the nil object value luaState.Remove(-1); // remove the metatable CollectObject(o, index); // Remove from both our tables and fall out to get a new ID } index = AddObject(o); PushNewObject(luaState, o, index, metatable); } /* * Pushes a new object into the Lua stack with the provided * metatable */ private void PushNewObject(LuaState luaState, object o, int index, string metatable) { if (metatable == "luaNet_metatable") { // Gets or creates the metatable for the object's type luaState.GetMetaTable(o.GetType().AssemblyQualifiedName); if (luaState.IsNil(-1)) { luaState.SetTop(-2); luaState.NewMetaTable(o.GetType().AssemblyQualifiedName); luaState.PushString("cache"); luaState.NewTable(); luaState.RawSet(-3); luaState.PushLightUserData(_tagPtr); luaState.PushNumber(1); luaState.RawSet(-3); luaState.PushString("__index"); luaState.PushString("luaNet_indexfunction"); luaState.RawGet(LuaRegistry.Index); luaState.RawSet(-3); luaState.PushString("__gc"); luaState.PushCFunction(MetaFunctions.GcFunction); luaState.RawSet(-3); luaState.PushString("__tostring"); luaState.PushCFunction(MetaFunctions.ToStringFunction); luaState.RawSet(-3); luaState.PushString("__newindex"); luaState.PushCFunction(MetaFunctions.NewIndexFunction); luaState.RawSet(-3); // Bind C# operator with Lua metamethods (__add, __sub, __mul) RegisterOperatorsFunctions(luaState, o.GetType()); RegisterCallMethodForDelegate(luaState, o); } } else luaState.GetMetaTable(metatable); // Stores the object index in the Lua list and pushes the // index into the Lua stack luaState.GetMetaTable("luaNet_objects"); luaState.NewUData(index); luaState.PushCopy(-3); luaState.Remove(-4); luaState.SetMetaTable(-2); luaState.PushCopy(-1); luaState.RawSetInteger(-3, index); luaState.Remove(-2); } void RegisterCallMethodForDelegate(LuaState luaState, object o) { if (!(o is Delegate)) return; luaState.PushString("__call"); luaState.PushCFunction(MetaFunctions.CallDelegateFunction); luaState.RawSet(-3); } void RegisterOperatorsFunctions(LuaState luaState, Type type) { if (type.HasAdditionOperator()) { luaState.PushString("__add"); luaState.PushCFunction(MetaFunctions.AddFunction); luaState.RawSet(-3); } if (type.HasSubtractionOperator()) { luaState.PushString("__sub"); luaState.PushCFunction(MetaFunctions.SubtractFunction); luaState.RawSet(-3); } if (type.HasMultiplyOperator()) { luaState.PushString("__mul"); luaState.PushCFunction(MetaFunctions.MultiplyFunction); luaState.RawSet(-3); } if (type.HasDivisionOperator()) { luaState.PushString("__div"); luaState.PushCFunction(MetaFunctions.DivisionFunction); luaState.RawSet(-3); } if (type.HasModulusOperator()) { luaState.PushString("__mod"); luaState.PushCFunction(MetaFunctions.ModulosFunction); luaState.RawSet(-3); } if (type.HasUnaryNegationOperator()) { luaState.PushString("__unm"); luaState.PushCFunction(MetaFunctions.UnaryNegationFunction); luaState.RawSet(-3); } if (type.HasEqualityOperator()) { luaState.PushString("__eq"); luaState.PushCFunction(MetaFunctions.EqualFunction); luaState.RawSet(-3); } if (type.HasLessThanOperator()) { luaState.PushString("__lt"); luaState.PushCFunction(MetaFunctions.LessThanFunction); luaState.RawSet(-3); } if (type.HasLessThanOrEqualOperator()) { luaState.PushString("__le"); luaState.PushCFunction(MetaFunctions.LessThanOrEqualFunction); luaState.RawSet(-3); } } /* * Gets an object from the Lua stack with the desired type, if it matches, otherwise * returns null. */ internal object GetAsType(LuaState luaState, int stackPos, Type paramType) { var extractor = typeChecker.CheckLuaType(luaState, stackPos, paramType); return extractor != null ? extractor(luaState, stackPos) : null; } /// /// Given the Lua int ID for an object remove it from our maps /// /// internal void CollectObject(int udata) { object o; bool found = _objects.TryGetValue(udata, out o); // The other variant of collectObject might have gotten here first, in that case we will silently ignore the missing entry if (found) CollectObject(o, udata); } /// /// Given an object reference, remove it from our maps /// /// /// private void CollectObject(object o, int udata) { _objects.Remove(udata); if (!o.GetType().IsValueType || o.GetType().IsEnum) _objectsBackMap.Remove(o); } private int AddObject(object obj) { // New object: inserts it in the list int index = _nextObj++; _objects[index] = obj; if (!obj.GetType().IsValueType || obj.GetType().IsEnum) _objectsBackMap[obj] = index; return index; } /* * Gets an object from the Lua stack according to its Lua type. */ internal object GetObject(LuaState luaState, int index) { LuaType type = luaState.Type(index); switch (type) { case LuaType.Number: { if (luaState.IsInteger(index)) return luaState.ToInteger(index); return luaState.ToNumber(index); } case LuaType.String: return luaState.ToString(index, false); case LuaType.Boolean: return luaState.ToBoolean(index); case LuaType.Table: return GetTable(luaState, index); case LuaType.Function: return GetFunction(luaState, index); case LuaType.UserData: { int udata = luaState.ToNetObject(index, Tag); return udata != -1 ? _objects[udata] : GetUserData(luaState, index); } default: return null; } } /* * Gets the table in the index positon of the Lua stack. */ internal LuaTable GetTable(LuaState luaState, int index) { // Before create new tables, check if there is any finalized object to clean. CleanFinalizedReferences(luaState); luaState.PushCopy(index); int reference = luaState.Ref(LuaRegistry.Index); if (reference == -1) return null; return new LuaTable(reference, interpreter); } /* * Gets the userdata in the index positon of the Lua stack. */ internal LuaUserData GetUserData(LuaState luaState, int index) { // Before create new tables, check if there is any finalized object to clean. CleanFinalizedReferences(luaState); luaState.PushCopy(index); int reference = luaState.Ref(LuaRegistry.Index); if (reference == -1) return null; return new LuaUserData(reference, interpreter); } /* * Gets the function in the index positon of the Lua stack. */ internal LuaFunction GetFunction(LuaState luaState, int index) { // Before create new tables, check if there is any finalized object to clean. CleanFinalizedReferences(luaState); luaState.PushCopy(index); int reference = luaState.Ref(LuaRegistry.Index); if (reference == -1) return null; return new LuaFunction(reference, interpreter); } /* * Gets the CLR object in the index positon of the Lua stack. Returns * delegates as Lua functions. */ internal object GetNetObject(LuaState luaState, int index) { int idx = luaState.ToNetObject(index, Tag); return idx != -1 ? _objects[idx] : null; } /* * Gets the CLR object in the index position of the Lua stack. Returns * delegates as is. */ internal object GetRawNetObject(LuaState luaState, int index) { int udata = luaState.RawNetObj(index); return udata != -1 ? _objects[udata] : null; } /* * Gets the values from the provided index to * the top of the stack and returns them in an array. */ internal object[] PopValues(LuaState luaState, int oldTop) { int newTop = luaState.GetTop(); if (oldTop == newTop) return null; var returnValues = new List(); for (int i = oldTop + 1; i <= newTop; i++) returnValues.Add(GetObject(luaState, i)); luaState.SetTop(oldTop); return returnValues.ToArray(); } /* * Gets the values from the provided index to * the top of the stack and returns them in an array, casting * them to the provided types. */ internal object[] PopValues(LuaState luaState, int oldTop, Type[] popTypes) { int newTop = luaState.GetTop(); if (oldTop == newTop) return null; int iTypes; var returnValues = new List(); if (popTypes[0] == typeof(void)) iTypes = 1; else iTypes = 0; for (int i = oldTop + 1; i <= newTop; i++) { returnValues.Add(GetAsType(luaState, i, popTypes[iTypes])); iTypes++; } luaState.SetTop(oldTop); return returnValues.ToArray(); } // The following line doesn't work for remoting proxies - they always return a match for 'is' // else if (o is ILuaGeneratedType) private static bool IsILua(object o) { if (o is ILuaGeneratedType) { // Make sure we are _really_ ILuaGenerated var typ = o.GetType(); return typ.GetInterface("ILuaGeneratedType", true) != null; } return false; } /* * Pushes the object into the Lua stack according to its type. */ internal void Push(LuaState luaState, object o) { if (o == null) luaState.PushNil(); else if (o is sbyte sb) luaState.PushInteger(sb); else if(o is byte bt) luaState.PushInteger(bt); else if(o is short s) luaState.PushInteger(s); else if (o is ushort us) luaState.PushInteger(us); else if (o is int i) luaState.PushInteger(i); else if (o is uint ui) luaState.PushInteger(ui); else if (o is long l) luaState.PushInteger(l); else if (o is ulong ul) luaState.PushInteger((long)ul); else if (o is char ch) luaState.PushInteger(ch); else if (o is float fl) luaState.PushNumber(fl); else if(o is decimal dc) luaState.PushNumber((double)dc); else if(o is double db) luaState.PushNumber(db); else if (o is string str) luaState.PushString(str); else if (o is bool b) luaState.PushBoolean(b); else if (IsILua(o)) ((ILuaGeneratedType)o).LuaInterfaceGetLuaTable().Push(luaState); else if (o is LuaTable table) table.Push(luaState); else if (o is LuaNativeFunction nativeFunction) PushFunction(luaState, nativeFunction); else if (o is LuaFunction luaFunction) luaFunction.Push(luaState); else PushObject(luaState, o, "luaNet_metatable"); } /* * Checks if the method matches the arguments in the Lua stack, getting * the arguments if it does. */ internal bool MatchParameters(LuaState luaState, MethodBase method, MethodCache methodCache, int skipParam) { return metaFunctions.MatchParameters(luaState, method, methodCache, skipParam); } internal Array TableToArray(LuaState luaState, ExtractValue extractValue, Type paramArrayType, int startIndex, int count) { return metaFunctions.TableToArray(luaState, extractValue, paramArrayType, ref startIndex, count); } private Type TypeOf(LuaState luaState, int idx) { int udata = luaState.CheckUObject(idx, "luaNet_class"); if (udata == -1) return null; var pt = (ProxyType)_objects[udata]; return pt.UnderlyingSystemType; } static int PushError(LuaState luaState, string msg) { luaState.PushNil(); luaState.PushString(msg); return 2; } #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int CType(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); return translator.CTypeInternal(state); } int CTypeInternal(LuaState luaState) { Type t = TypeOf(luaState,1); if (t == null) return PushError(luaState, "Not a CLR Class"); PushObject(luaState, t, "luaNet_metatable"); return 1; } #if __IOS__ || __TVOS__ || __WATCHOS__ [MonoPInvokeCallback(typeof(LuaNativeFunction))] #endif private static int EnumFromInt(IntPtr luaState) { var state = LuaState.FromIntPtr(luaState); var translator = ObjectTranslatorPool.Instance.Find(state); return translator.EnumFromIntInternal(state); } int EnumFromIntInternal(LuaState luaState) { Type t = TypeOf(luaState, 1); if (t == null || !t.IsEnum) return PushError(luaState, "Not an Enum."); object res = null; LuaType lt = luaState.Type(2); if (lt == LuaType.Number) { int ival = (int)luaState.ToNumber(2); res = Enum.ToObject(t, ival); } else if (lt == LuaType.String) { string sflags = luaState.ToString(2, false); string err = null; try { res = Enum.Parse(t, sflags, true); } catch (ArgumentException e) { err = e.Message; } if (err != null) return PushError(luaState, err); } else { return PushError(luaState, "Second argument must be a integer or a string."); } PushObject(luaState, res, "luaNet_metatable"); return 1; } internal void AddFinalizedReference(int reference) { finalizedReferences.Enqueue(reference); } void CleanFinalizedReferences(LuaState state) { if (finalizedReferences.Count == 0) return; int reference; while (finalizedReferences.TryDequeue(out reference)) state.Unref(LuaRegistry.Index, reference); } } }