From 78a6904ab09a087c8250162d3adb94b12375cdef Mon Sep 17 00:00:00 2001 From: Christopher Date: Fri, 7 Aug 2020 01:19:35 +0200 Subject: [PATCH] add lua engine --- src/LUA/CheckType.cs | 406 ++++ src/LUA/Event/DebugHookEventArgs.cs | 21 + src/LUA/Event/HookExceptionEventArgs.cs | 14 + src/LUA/Exceptions/LuaException.cs | 20 + src/LUA/Exceptions/LuaScriptException.cs | 52 + src/LUA/Extensions/LuaExtensions.cs | 122 ++ src/LUA/Extensions/StringExtensions.cs | 31 + src/LUA/Extensions/TypeExtensions.cs | 137 ++ .../GenerateEventAssembly/ClassGenerator.cs | 22 + .../GenerateEventAssembly/CodeGeneration.cs | 752 ++++++++ .../DelegateGenerator.cs | 22 + .../ILuaGeneratedType.cs | 11 + src/LUA/GenerateEventAssembly/LuaClassType.cs | 15 + src/LUA/Lua.cs | 1262 +++++++++++++ src/LUA/LuaBase.cs | 86 + src/LUA/LuaFunction.cs | 91 + src/LUA/LuaGlobalAttribute.cs | 23 + src/LUA/LuaHideAttribute.cs | 12 + src/LUA/LuaRegistrationHelper.cs | 98 + src/LUA/LuaTable.cs | 120 ++ src/LUA/LuaUserData.cs | 74 + src/LUA/Metatables.cs | 1636 +++++++++++++++++ src/LUA/Method/EventHandlerContainer.cs | 36 + src/LUA/Method/LuaClassHelper.cs | 55 + src/LUA/Method/LuaDelegate.cs | 47 + src/LUA/Method/LuaEventHandler.cs | 12 + src/LUA/Method/LuaMethodWrapper.cs | 355 ++++ src/LUA/Method/MethodArgs.cs | 18 + src/LUA/Method/MethodCache.cs | 42 + src/LUA/Method/RegisterEventHandler.cs | 53 + src/LUA/ObjectTranslator.cs | 1171 ++++++++++++ src/LUA/ObjectTranslatorPool.cs | 44 + src/LUA/Properties/AssemblyInfo.cs | 39 + src/LUA/ProxyType.cs | 53 + src/LUAEngine.cs | 213 +++ 35 files changed, 7165 insertions(+) create mode 100644 src/LUA/CheckType.cs create mode 100644 src/LUA/Event/DebugHookEventArgs.cs create mode 100644 src/LUA/Event/HookExceptionEventArgs.cs create mode 100644 src/LUA/Exceptions/LuaException.cs create mode 100644 src/LUA/Exceptions/LuaScriptException.cs create mode 100644 src/LUA/Extensions/LuaExtensions.cs create mode 100644 src/LUA/Extensions/StringExtensions.cs create mode 100644 src/LUA/Extensions/TypeExtensions.cs create mode 100644 src/LUA/GenerateEventAssembly/ClassGenerator.cs create mode 100644 src/LUA/GenerateEventAssembly/CodeGeneration.cs create mode 100644 src/LUA/GenerateEventAssembly/DelegateGenerator.cs create mode 100644 src/LUA/GenerateEventAssembly/ILuaGeneratedType.cs create mode 100644 src/LUA/GenerateEventAssembly/LuaClassType.cs create mode 100644 src/LUA/Lua.cs create mode 100644 src/LUA/LuaBase.cs create mode 100644 src/LUA/LuaFunction.cs create mode 100644 src/LUA/LuaGlobalAttribute.cs create mode 100644 src/LUA/LuaHideAttribute.cs create mode 100644 src/LUA/LuaRegistrationHelper.cs create mode 100644 src/LUA/LuaTable.cs create mode 100644 src/LUA/LuaUserData.cs create mode 100644 src/LUA/Metatables.cs create mode 100644 src/LUA/Method/EventHandlerContainer.cs create mode 100644 src/LUA/Method/LuaClassHelper.cs create mode 100644 src/LUA/Method/LuaDelegate.cs create mode 100644 src/LUA/Method/LuaEventHandler.cs create mode 100644 src/LUA/Method/LuaMethodWrapper.cs create mode 100644 src/LUA/Method/MethodArgs.cs create mode 100644 src/LUA/Method/MethodCache.cs create mode 100644 src/LUA/Method/RegisterEventHandler.cs create mode 100644 src/LUA/ObjectTranslator.cs create mode 100644 src/LUA/ObjectTranslatorPool.cs create mode 100644 src/LUA/Properties/AssemblyInfo.cs create mode 100644 src/LUA/ProxyType.cs create mode 100644 src/LUAEngine.cs diff --git a/src/LUA/CheckType.cs b/src/LUA/CheckType.cs new file mode 100644 index 0000000..67d3234 --- /dev/null +++ b/src/LUA/CheckType.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using KeraLua; +using NLua.Method; +using NLua.Extensions; + +namespace NLua +{ + using LuaState = KeraLua.Lua; + sealed class CheckType + { + readonly Dictionary _extractValues = new Dictionary(); + readonly ExtractValue _extractNetObject; + readonly ObjectTranslator _translator; + + public CheckType(ObjectTranslator translator) + { + _translator = translator; + _extractValues.Add(typeof(object), GetAsObject); + _extractValues.Add(typeof(sbyte), GetAsSbyte); + _extractValues.Add(typeof(byte), GetAsByte); + _extractValues.Add(typeof(short), GetAsShort); + _extractValues.Add(typeof(ushort), GetAsUshort); + _extractValues.Add(typeof(int), GetAsInt); + _extractValues.Add(typeof(uint), GetAsUint); + _extractValues.Add(typeof(long), GetAsLong); + _extractValues.Add(typeof(ulong), GetAsUlong); + _extractValues.Add(typeof(double), GetAsDouble); + _extractValues.Add(typeof(char), GetAsChar); + _extractValues.Add(typeof(float), GetAsFloat); + _extractValues.Add(typeof(decimal), GetAsDecimal); + _extractValues.Add(typeof(bool), GetAsBoolean); + _extractValues.Add(typeof(string), GetAsString); + _extractValues.Add(typeof(char[]), GetAsCharArray); + _extractValues.Add(typeof(byte[]), GetAsByteArray); + _extractValues.Add(typeof(LuaFunction), GetAsFunction); + _extractValues.Add(typeof(LuaTable), GetAsTable); + _extractValues.Add(typeof(LuaUserData), GetAsUserdata); + _extractNetObject = GetAsNetObject; + } + + /* + * Checks if the value at Lua stack index stackPos matches paramType, + * returning a conversion function if it does and null otherwise. + */ + internal ExtractValue GetExtractor(ProxyType paramType) + { + return GetExtractor(paramType.UnderlyingSystemType); + } + + internal ExtractValue GetExtractor(Type paramType) + { + if (paramType.IsByRef) + paramType = paramType.GetElementType(); + + return _extractValues.ContainsKey(paramType) ? _extractValues[paramType] : _extractNetObject; + } + + internal ExtractValue CheckLuaType(LuaState luaState, int stackPos, Type paramType) + { + LuaType luatype = luaState.Type(stackPos); + + if (paramType.IsByRef) + paramType = paramType.GetElementType(); + + var underlyingType = Nullable.GetUnderlyingType(paramType); + + if (underlyingType != null) + { + paramType = underlyingType; // Silently convert nullable types to their non null requics + } + + + bool netParamIsNumeric = paramType == typeof(int) || + paramType == typeof(uint) || + paramType == typeof(long) || + paramType == typeof(ulong) || + paramType == typeof(short) || + paramType == typeof(ushort) || + paramType == typeof(float) || + paramType == typeof(double) || + paramType == typeof(decimal) || + paramType == typeof(byte); + + // If it is a nullable + if (underlyingType != null) + { + // null can always be assigned to nullable + if (luatype == LuaType.Nil) + { + // Return the correct extractor anyways + if (netParamIsNumeric || paramType == typeof(bool)) + return _extractValues[paramType]; + return _extractNetObject; + } + } + + if (paramType == typeof(object)) + return _extractValues[paramType]; + + //CP: Added support for generic parameters + if (paramType.IsGenericParameter) + { + if (luatype == LuaType.Boolean) + return _extractValues[typeof(bool)]; + if (luatype == LuaType.String) + return _extractValues[typeof(string)]; + if (luatype == LuaType.Table) + return _extractValues[typeof(LuaTable)]; + if (luatype == LuaType.UserData) + return _extractValues[typeof(object)]; + if (luatype == LuaType.Function) + return _extractValues[typeof(LuaFunction)]; + if (luatype == LuaType.Number) + return _extractValues[typeof(double)]; + } + bool netParamIsString = paramType == typeof(string) || paramType == typeof(char[]) || paramType == typeof(byte[]); + + if (netParamIsNumeric) + { + if (luaState.IsNumber(stackPos) && !netParamIsString) + return _extractValues[paramType]; + } + else if (paramType == typeof(bool)) + { + if (luaState.IsBoolean(stackPos)) + return _extractValues[paramType]; + } + else if (netParamIsString) + { + if (luaState.IsString(stackPos)) + return _extractValues[paramType]; + if (luatype == LuaType.Nil) + return _extractNetObject; // kevinh - silently convert nil to a null string pointer + } + else if (paramType == typeof(LuaTable)) + { + if (luatype == LuaType.Table || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaUserData)) + { + if (luatype == LuaType.UserData || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (paramType == typeof(LuaFunction)) + { + if (luatype == LuaType.Function || luatype == LuaType.Nil) + return _extractValues[paramType]; + } + else if (typeof(Delegate).IsAssignableFrom(paramType) && luatype == LuaType.Function && paramType.GetMethod("Invoke") != null) + return new DelegateGenerator(_translator, paramType).ExtractGenerated; + else if (paramType.IsInterface && luatype == LuaType.Table) + return new ClassGenerator(_translator, paramType).ExtractGenerated; + else if ((paramType.IsInterface || paramType.IsClass) && luatype == LuaType.Nil) + { + // kevinh - allow nil to be silently converted to null - extractNetObject will return null when the item ain't found + return _extractNetObject; + } + else if (luaState.Type(stackPos) == LuaType.Table) + { + if (luaState.GetMetaField(stackPos, "__index") != LuaType.Nil) + { + object obj = _translator.GetNetObject(luaState, -1); + luaState.SetTop(-2); + if (obj != null && paramType.IsInstanceOfType(obj)) + return _extractNetObject; + } + else + return null; + } + else + { + object obj = _translator.GetNetObject(luaState, stackPos); + if (obj != null && paramType.IsInstanceOfType(obj)) + return _extractNetObject; + } + + return null; + } + + + /* + * The following functions return the value in the Lua stack + * index stackPos as the desired type if it can, or null + * otherwise. + */ + private object GetAsSbyte(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (sbyte)luaState.ToInteger(stackPos); + + return (sbyte)luaState.ToNumber(stackPos); + } + + private object GetAsByte(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (byte)luaState.ToInteger(stackPos); + + return (byte)luaState.ToNumber(stackPos); + } + + private object GetAsShort(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (short)luaState.ToInteger(stackPos); + + return (short)luaState.ToNumber(stackPos); + } + + private object GetAsUshort(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (ushort)luaState.ToInteger(stackPos); + + return (ushort)luaState.ToNumber(stackPos); + } + + private object GetAsInt(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (int)luaState.ToInteger(stackPos); + + return (int)luaState.ToNumber(stackPos); + } + + private object GetAsUint(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (uint)luaState.ToInteger(stackPos); + + return (uint)luaState.ToNumber(stackPos); + } + + private object GetAsLong(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return luaState.ToInteger(stackPos); + + return (long)luaState.ToNumber(stackPos); + } + + private object GetAsUlong(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (ulong)luaState.ToInteger(stackPos); + + return (ulong)luaState.ToNumber(stackPos); + } + + private object GetAsDouble(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (double)luaState.ToInteger(stackPos); + + return luaState.ToNumber(stackPos); + } + + private object GetAsChar(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (char)luaState.ToInteger(stackPos); + + return (char)luaState.ToNumber(stackPos); + } + + private object GetAsFloat(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (float)luaState.ToInteger(stackPos); + + return (float)luaState.ToNumber(stackPos); + } + + private object GetAsDecimal(LuaState luaState, int stackPos) + { + if (!luaState.IsNumericType(stackPos)) + return null; + + if (luaState.IsInteger(stackPos)) + return (decimal)luaState.ToInteger(stackPos); + + return (decimal)luaState.ToNumber(stackPos); + } + + private object GetAsBoolean(LuaState luaState, int stackPos) + { + return luaState.ToBoolean(stackPos); + } + + private object GetAsCharArray(LuaState luaState, int stackPos) + { + if (!luaState.IsString(stackPos)) + return null; + string retVal = luaState.ToString(stackPos, false); + return retVal.ToCharArray(); + } + + private object GetAsByteArray(LuaState luaState, int stackPos) + { + if (!luaState.IsString(stackPos)) + return null; + + byte [] retVal = luaState.ToBuffer(stackPos, false); + return retVal; + } + + private object GetAsString(LuaState luaState, int stackPos) + { + if (!luaState.IsString(stackPos)) + return null; + return luaState.ToString(stackPos, false); + } + + private object GetAsTable(LuaState luaState, int stackPos) + { + return _translator.GetTable(luaState, stackPos); + } + + private object GetAsFunction(LuaState luaState, int stackPos) + { + return _translator.GetFunction(luaState, stackPos); + } + + private object GetAsUserdata(LuaState luaState, int stackPos) + { + return _translator.GetUserData(luaState, stackPos); + } + + public object GetAsObject(LuaState luaState, int stackPos) + { + if (luaState.Type(stackPos) == LuaType.Table) + { + if (luaState.GetMetaField(stackPos, "__index") != LuaType.Nil) + { + if (luaState.CheckMetaTable(-1, _translator.Tag)) + { + luaState.Insert(stackPos); + luaState.Remove(stackPos + 1); + } + else + luaState.SetTop(-2); + } + } + + object obj = _translator.GetObject(luaState, stackPos); + return obj; + } + + public object GetAsNetObject(LuaState luaState, int stackPos) + { + object obj = _translator.GetNetObject(luaState, stackPos); + + if (obj != null || luaState.Type(stackPos) != LuaType.Table) + return obj; + + if (luaState.GetMetaField(stackPos, "__index") == LuaType.Nil) + return null; + + if (luaState.CheckMetaTable(-1, _translator.Tag)) + { + luaState.Insert(stackPos); + luaState.Remove(stackPos + 1); + obj = _translator.GetNetObject(luaState, stackPos); + } + else + luaState.SetTop(-2); + + return obj; + } + } +} diff --git a/src/LUA/Event/DebugHookEventArgs.cs b/src/LUA/Event/DebugHookEventArgs.cs new file mode 100644 index 0000000..6c75583 --- /dev/null +++ b/src/LUA/Event/DebugHookEventArgs.cs @@ -0,0 +1,21 @@ +using System; +using KeraLua; + +namespace NLua.Event +{ + /// + /// Event args for hook callback event + /// + public class DebugHookEventArgs : EventArgs + { + public DebugHookEventArgs(LuaDebug luaDebug) + { + LuaDebug = luaDebug; + } + + /// + /// Lua Debug Information + /// + public LuaDebug LuaDebug { get; } + } +} \ No newline at end of file diff --git a/src/LUA/Event/HookExceptionEventArgs.cs b/src/LUA/Event/HookExceptionEventArgs.cs new file mode 100644 index 0000000..a38b11a --- /dev/null +++ b/src/LUA/Event/HookExceptionEventArgs.cs @@ -0,0 +1,14 @@ +using System; + +namespace NLua.Event +{ + public class HookExceptionEventArgs : EventArgs + { + public Exception Exception { get; } + + public HookExceptionEventArgs(Exception ex) + { + Exception = ex; + } + } +} \ No newline at end of file diff --git a/src/LUA/Exceptions/LuaException.cs b/src/LUA/Exceptions/LuaException.cs new file mode 100644 index 0000000..a157f68 --- /dev/null +++ b/src/LUA/Exceptions/LuaException.cs @@ -0,0 +1,20 @@ +using System; + +namespace NLua.Exceptions +{ + /// + /// Exceptions thrown by the Lua runtime + /// + [Serializable] + public class LuaException : Exception + { + public LuaException (string message) : base(message) + { + } + + public LuaException (string message, Exception innerException) : base(message, innerException) + { + } + + } +} \ No newline at end of file diff --git a/src/LUA/Exceptions/LuaScriptException.cs b/src/LUA/Exceptions/LuaScriptException.cs new file mode 100644 index 0000000..1045ca6 --- /dev/null +++ b/src/LUA/Exceptions/LuaScriptException.cs @@ -0,0 +1,52 @@ +using System; + +namespace NLua.Exceptions +{ + /// + /// Exceptions thrown by the Lua runtime because of errors in the script + /// + /// + [Serializable] + public class LuaScriptException : LuaException + { + /// + /// Returns true if the exception has occured as the result of a .NET exception in user code + /// + public bool IsNetException { get; } + + private readonly string _source; + + /// + /// The position in the script where the exception was triggered. + /// + public override string Source => _source; + + /// + /// Creates a new Lua-only exception. + /// + /// The message that describes the error. + /// The position in the script where the exception was triggered. + public LuaScriptException(string message, string source) : base(message) + { + _source = source; + } + + /// + /// Creates a new .NET wrapping exception. + /// + /// The .NET exception triggered by user-code. + /// The position in the script where the exception was triggered. + public LuaScriptException(Exception innerException, string source) + : base("A .NET exception occured in user-code", innerException) + { + _source = source; + IsNetException = true; + } + + public override string ToString() + { + // Prepend the error source + return GetType().FullName + ": " + _source + Message; + } + } +} \ No newline at end of file diff --git a/src/LUA/Extensions/LuaExtensions.cs b/src/LUA/Extensions/LuaExtensions.cs new file mode 100644 index 0000000..92ae177 --- /dev/null +++ b/src/LUA/Extensions/LuaExtensions.cs @@ -0,0 +1,122 @@ + +using System; +using System.Runtime.InteropServices; +using KeraLua; +using LuaState=KeraLua.Lua; + +namespace NLua.Extensions +{ + static class LuaExtensions + { + public static bool CheckMetaTable(this LuaState state, int index, IntPtr tag) + { + if (!state.GetMetaTable(index)) + return false; + + state.PushLightUserData(tag); + state.RawGet(-2); + bool isNotNil = !state.IsNil(-1); + state.SetTop(-3); + return isNotNil; + } + + public static void PopGlobalTable(this LuaState luaState) + { + luaState.RawSetInteger(LuaRegistry.Index, (long) LuaRegistryIndex.Globals); + } + + public static void GetRef (this LuaState luaState, int reference) + { + luaState.RawGetInteger(LuaRegistry.Index, reference); + } + + // ReSharper disable once IdentifierTypo + public static void Unref (this LuaState luaState, int reference) + { + luaState.Unref(LuaRegistry.Index, reference); + } + + public static bool AreEqual(this LuaState luaState, int ref1, int ref2) + { + return luaState.Compare(ref1, ref2, LuaCompare.Equal); + } + + public static IntPtr CheckUData(this LuaState state, int ud, string name) + { + IntPtr p = state.ToUserData(ud); + if (p == IntPtr.Zero) + return IntPtr.Zero; + if (!state.GetMetaTable(ud)) + return IntPtr.Zero; + + state.GetField(LuaRegistry.Index, name); + + bool isEqual = state.RawEqual(-1, -2); + + state.Pop(2); + + if (isEqual) + return p; + + return IntPtr.Zero; + } + + public static int ToNetObject(this LuaState state, int index, IntPtr tag) + { + if (state.Type(index) != LuaType.UserData) + return -1; + + IntPtr userData; + + if (state.CheckMetaTable(index, tag)) + { + userData = state.ToUserData(index); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + } + + userData = state.CheckUData(index, "luaNet_class"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + userData = state.CheckUData(index, "luaNet_searchbase"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + userData = state.CheckUData(index, "luaNet_function"); + if (userData != IntPtr.Zero) + return Marshal.ReadInt32(userData); + + return -1; + } + + public static void NewUData(this LuaState state, int val) + { + IntPtr pointer = state.NewUserData(Marshal.SizeOf(typeof(int))); + Marshal.WriteInt32(pointer, val); + } + + public static int RawNetObj(this LuaState state, int index) + { + IntPtr pointer = state.ToUserData(index); + if (pointer == IntPtr.Zero) + return -1; + + return Marshal.ReadInt32(pointer); + } + + public static int CheckUObject(this LuaState state, int index, string name) + { + IntPtr udata = state.CheckUData(index, name); + if (udata == IntPtr.Zero) + return -1; + + return Marshal.ReadInt32(udata); + } + + public static bool IsNumericType(this LuaState state, int index) + { + return state.Type(index) == LuaType.Number; + } + } +} diff --git a/src/LUA/Extensions/StringExtensions.cs b/src/LUA/Extensions/StringExtensions.cs new file mode 100644 index 0000000..0f74189 --- /dev/null +++ b/src/LUA/Extensions/StringExtensions.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; + +namespace NLua.Extensions +{ + static class StringExtensions + { + public static IEnumerable SplitWithEscape(this string input, char separator, char escapeCharacter) + { + int start = 0; + int index = 0; + while (index < input.Length) + { + index = input.IndexOf(separator, index); + if (index == -1) + break; + + if (input[index - 1] == escapeCharacter) + { + input = input.Remove(index - 1, 1); + continue; + } + + + yield return input.Substring(start, index - start); + index++; + start = index; + } + yield return input.Substring(start); + } + } +} \ No newline at end of file diff --git a/src/LUA/Extensions/TypeExtensions.cs b/src/LUA/Extensions/TypeExtensions.cs new file mode 100644 index 0000000..b88e5bf --- /dev/null +++ b/src/LUA/Extensions/TypeExtensions.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace NLua.Extensions +{ + static class TypeExtensions + { + public static bool HasMethod(this Type t, string name) + { + var op = t.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + return op.Any(m => m.Name == name); + } + + public static bool HasAdditionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Addition"); + } + + public static bool HasSubtractionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Subtraction"); + } + + public static bool HasMultiplyOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Multiply"); + } + + public static bool HasDivisionOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Division"); + } + + public static bool HasModulusOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_Modulus"); + } + + public static bool HasUnaryNegationOperator(this Type t) + { + if (t.IsPrimitive) + return true; + // Unary - will always have only one version. + var op = t.GetMethod("op_UnaryNegation", BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + return op != null; + } + + public static bool HasEqualityOperator(this Type t) + { + if (t.IsPrimitive) + return true; + return t.HasMethod("op_Equality"); + } + + public static bool HasLessThanOperator(this Type t) + { + if (t.IsPrimitive) + return true; + + return t.HasMethod("op_LessThan"); + } + + public static bool HasLessThanOrEqualOperator(this Type t) + { + if (t.IsPrimitive) + return true; + return t.HasMethod("op_LessThanOrEqual"); + } + + public static MethodInfo[] GetMethods(this Type t, string name, BindingFlags flags) + { + return t.GetMethods(flags).Where(m => m.Name == name).ToArray(); + } + + public static MethodInfo[] GetExtensionMethods(this Type type, string name, IEnumerable assemblies = null) + { + var types = new List(); + + types.AddRange(type.Assembly.GetTypes().Where(t => t.IsPublic)); + + if (assemblies != null) + { + foreach (Assembly item in assemblies) + { + if (item == type.Assembly) + continue; + types.AddRange(item.GetTypes().Where(t => t.IsPublic && t.IsClass && t.IsSealed && t.IsAbstract && !t.IsNested)); + } + } + + var query = types + .SelectMany(extensionType => extensionType.GetMethods(name, BindingFlags.Static | BindingFlags.Public), + (extensionType, method) => new {extensionType, method}) + .Where(t => t.method.IsDefined(typeof(ExtensionAttribute), false)) + .Where(t => + t.method.GetParameters()[0].ParameterType == type || + t.method.GetParameters()[0].ParameterType.IsAssignableFrom(type) || + type.GetInterfaces().Contains(t.method.GetParameters()[0].ParameterType)) + .Select(t => t.method); + + return query.ToArray(); + } + + /// + /// Extends the System.Type-type to search for a given extended MethodeName. + /// + /// + /// + /// + /// + public static MethodInfo GetExtensionMethod(this Type t, string name, IEnumerable assemblies = null) + { + var mi = t.GetExtensionMethods(name, assemblies).ToArray(); + if (mi.Length == 0) + return null; + return mi[0]; + } + } +} \ No newline at end of file diff --git a/src/LUA/GenerateEventAssembly/ClassGenerator.cs b/src/LUA/GenerateEventAssembly/ClassGenerator.cs new file mode 100644 index 0000000..4e78279 --- /dev/null +++ b/src/LUA/GenerateEventAssembly/ClassGenerator.cs @@ -0,0 +1,22 @@ +using System; +using LuaState = KeraLua.Lua; + +namespace NLua +{ + class ClassGenerator + { + private readonly ObjectTranslator _translator; + private readonly Type _klass; + + public ClassGenerator(ObjectTranslator objTranslator, Type typeClass) + { + _translator = objTranslator; + _klass = typeClass; + } + + public object ExtractGenerated(LuaState luaState, int stackPos) + { + return CodeGeneration.Instance.GetClassInstance(_klass, _translator.GetTable(luaState, stackPos)); + } + } +} \ No newline at end of file diff --git a/src/LUA/GenerateEventAssembly/CodeGeneration.cs b/src/LUA/GenerateEventAssembly/CodeGeneration.cs new file mode 100644 index 0000000..8cac7ac --- /dev/null +++ b/src/LUA/GenerateEventAssembly/CodeGeneration.cs @@ -0,0 +1,752 @@ +using System; +using System.Threading; +using System.Reflection; + +using System.Reflection.Emit; +using System.Collections.Generic; +using NLua.Method; + +namespace NLua +{ + class CodeGeneration + { + private readonly Dictionary _classCollection = new Dictionary(); + private readonly Dictionary _delegateCollection = new Dictionary(); + +#if !NETSTANDARD && !WINDOWS_UWP + private Dictionary eventHandlerCollection = new Dictionary(); + private Type eventHandlerParent = typeof(LuaEventHandler); + private Type delegateParent = typeof(LuaDelegate); + private Type classHelper = typeof(LuaClassHelper); + private AssemblyBuilder newAssembly; + private ModuleBuilder newModule; + private int luaClassNumber = 1; +#endif + + static CodeGeneration() + { + } + + private CodeGeneration() + { + // Create an assembly name + var assemblyName = new AssemblyName(); + assemblyName.Name = "NLua_generatedcode"; + // Create a new assembly with one module. +#if NETCOREAPP + newAssembly = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + newModule = newAssembly.DefineDynamicModule("NLua_generatedcode"); +#elif !NETSTANDARD && !WINDOWS_UWP + newAssembly = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + newModule = newAssembly.DefineDynamicModule("NLua_generatedcode"); +#endif + } + + /* + * Singleton instance of the class + */ + public static CodeGeneration Instance { get; } = new CodeGeneration(); + + /* + * Generates an event handler that calls a Lua function + */ + private Type GenerateEvent(Type eventHandlerType) + { +#if NETSTANDARD || WINDOWS_UWP + throw new NotImplementedException(" Emit not available on .NET Standard "); +#else + string typeName; + lock (this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + + // Define a public class in the assembly, called typeName + var myType = newModule.DefineType(typeName, TypeAttributes.Public, eventHandlerParent); + + // Defines the handler method. Its signature is void(object, ) + var paramTypes = new Type[2]; + paramTypes[0] = typeof(object); + paramTypes[1] = eventHandlerType; + var returnType = typeof(void); + var handleMethod = myType.DefineMethod("HandleEvent", MethodAttributes.Public | MethodAttributes.HideBySig, returnType, paramTypes); + + // Emits the IL for the method. It loads the arguments + // and calls the handleEvent method of the base class + ILGenerator generator = handleMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Ldarg_2); + var miGenericEventHandler = eventHandlerParent.GetMethod("HandleEvent"); + generator.Emit(OpCodes.Call, miGenericEventHandler); + // returns + generator.Emit(OpCodes.Ret); + // creates the new type + return myType.CreateType(); +#endif + } + + /* + * Generates a type that can be used for instantiating a delegate + * of the provided type, given a Lua function. + */ + private Type GenerateDelegate(Type delegateType) + { +#if NETSTANDARD || WINDOWS_UWP + throw new NotImplementedException("GenerateDelegate is not available on Windows Store, please register your LuaDelegate type with Lua.RegisterLuaDelegateType( yourDelegate, theLuaDelegateHandler) "); +#else + string typeName; + lock (this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + + // Define a public class in the assembly, called typeName + var myType = newModule.DefineType(typeName, TypeAttributes.Public, delegateParent); + + // Defines the delegate method with the same signature as the + // Invoke method of delegateType + var invokeMethod = delegateType.GetMethod("Invoke"); + var paramInfo = invokeMethod.GetParameters(); + var paramTypes = new Type[paramInfo.Length]; + var returnType = invokeMethod.ReturnType; + + // Counts out and ref params, for use later + int nOutParams = 0; + int nOutAndRefParams = 0; + + for (int i = 0; i < paramTypes.Length; i++) + { + paramTypes[i] = paramInfo[i].ParameterType; + + if ((!paramInfo[i].IsIn) && paramInfo[i].IsOut) + nOutParams++; + + if (paramTypes[i].IsByRef) + nOutAndRefParams++; + } + + int[] refArgs = new int[nOutAndRefParams]; + var delegateMethod = myType.DefineMethod("CallFunction", invokeMethod.Attributes, returnType, paramTypes); + + // Generates the IL for the method + ILGenerator generator = delegateMethod.GetILGenerator(); + generator.DeclareLocal(typeof(object[])); // original arguments + generator.DeclareLocal(typeof(object[])); // with out-only arguments removed + generator.DeclareLocal(typeof(int[])); // indexes of out and ref arguments + + if (!(returnType == typeof(void))) // return value + generator.DeclareLocal(returnType); + else + generator.DeclareLocal(typeof(object)); + + // Initializes local variables + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); + generator.Emit(OpCodes.Newarr, typeof(object)); + generator.Emit(OpCodes.Stloc_0); + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length - nOutParams); + generator.Emit(OpCodes.Newarr, typeof(object)); + generator.Emit(OpCodes.Stloc_1); + generator.Emit(OpCodes.Ldc_I4, nOutAndRefParams); + generator.Emit(OpCodes.Newarr, typeof(int)); + generator.Emit(OpCodes.Stloc_2); + + // Stores the arguments in the local variables + for (int iArgs = 0, iInArgs = 0, iOutArgs = 0; iArgs < paramTypes.Length; iArgs++) + { + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldc_I4, iArgs); + generator.Emit(OpCodes.Ldarg, iArgs + 1); + + if (paramTypes[iArgs].IsByRef) + { + if (paramTypes[iArgs].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); + generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); + } + else + generator.Emit(OpCodes.Ldind_Ref); + } + else + { + if (paramTypes[iArgs].IsValueType) + generator.Emit(OpCodes.Box, paramTypes[iArgs]); + } + + generator.Emit(OpCodes.Stelem_Ref); + + if (paramTypes[iArgs].IsByRef) + { + generator.Emit(OpCodes.Ldloc_2); + generator.Emit(OpCodes.Ldc_I4, iOutArgs); + generator.Emit(OpCodes.Ldc_I4, iArgs); + generator.Emit(OpCodes.Stelem_I4); + refArgs[iOutArgs] = iArgs; + iOutArgs++; + } + + if (paramInfo[iArgs].IsIn || (!paramInfo[iArgs].IsOut)) + { + generator.Emit(OpCodes.Ldloc_1); + generator.Emit(OpCodes.Ldc_I4, iInArgs); + generator.Emit(OpCodes.Ldarg, iArgs + 1); + + if (paramTypes[iArgs].IsByRef) + { + if (paramTypes[iArgs].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); + generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); + } + else + generator.Emit(OpCodes.Ldind_Ref); + } + else + { + if (paramTypes[iArgs].IsValueType) + generator.Emit(OpCodes.Box, paramTypes[iArgs]); + } + + generator.Emit(OpCodes.Stelem_Ref); + iInArgs++; + } + } + + // Calls the callFunction method of the base class + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldloc_1); + generator.Emit(OpCodes.Ldloc_2); + var miGenericEventHandler = delegateParent.GetMethod("CallFunction"); + generator.Emit(OpCodes.Call, miGenericEventHandler); + + // Stores return value + if (returnType == typeof(void)) + { + generator.Emit(OpCodes.Pop); + generator.Emit(OpCodes.Ldnull); + } + else if (returnType.IsValueType) + { + generator.Emit(OpCodes.Unbox, returnType); + generator.Emit(OpCodes.Ldobj, returnType); + } + else + generator.Emit(OpCodes.Castclass, returnType); + + generator.Emit(OpCodes.Stloc_3); + + // Stores new value of out and ref params + for (int i = 0; i < refArgs.Length; i++) + { + generator.Emit(OpCodes.Ldarg, refArgs[i] + 1); + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldc_I4, refArgs[i]); + generator.Emit(OpCodes.Ldelem_Ref); + + if (paramTypes[refArgs[i]].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Unbox, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Ldobj, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Stobj, paramTypes[refArgs[i]].GetElementType()); + } + else + { + generator.Emit(OpCodes.Castclass, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Stind_Ref); + } + } + + // Returns + if (!(returnType == typeof(void))) + generator.Emit(OpCodes.Ldloc_3); + + generator.Emit(OpCodes.Ret); + return myType.CreateType(); // creates the new type +#endif + } + + void GetReturnTypesFromClass(Type klass, out Type[][] returnTypes) + { + var classMethods = klass.GetMethods(); + returnTypes = new Type[classMethods.Length][]; + + int i = 0; + + foreach (var method in classMethods) + { + if (klass.IsInterface) + { + GetReturnTypesFromMethod(method, out returnTypes[i]); + i++; + } + else if (!method.IsPrivate && !method.IsFinal && method.IsVirtual) + { + GetReturnTypesFromMethod(method, out returnTypes[i]); + i++; + } + } + } + + /* + * Generates an implementation of klass, if it is an interface, or + * a subclass of klass that delegates its virtual methods to a Lua table. + */ + public void GenerateClass(Type klass, out Type newType, out Type[][] returnTypes) + { + +#if NETSTANDARD || WINDOWS_UWP + throw new NotImplementedException (" Emit not available on .NET Standard "); +#else + string typeName; + lock (this) + { + typeName = "LuaGeneratedClass" + luaClassNumber; + luaClassNumber++; + } + + TypeBuilder myType; + // Define a public class in the assembly, called typeName + if (klass.IsInterface) + myType = newModule.DefineType(typeName, TypeAttributes.Public, typeof(object), new Type[] { + klass, + typeof(ILuaGeneratedType) + }); + else + myType = newModule.DefineType(typeName, TypeAttributes.Public, klass, new Type[] { typeof(ILuaGeneratedType) }); + + // Field that stores the Lua table + var luaTableField = myType.DefineField("__luaInterface_luaTable", typeof(LuaTable), FieldAttributes.Public); + // Field that stores the return types array + var returnTypesField = myType.DefineField("__luaInterface_returnTypes", typeof(Type[][]), FieldAttributes.Public); + // Generates the constructor for the new type, it takes a Lua table and an array + // of return types and stores them in the respective fields + var constructor = myType.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { + typeof(LuaTable), + typeof(Type[][]) + }); + ILGenerator generator = constructor.GetILGenerator(); + generator.Emit(OpCodes.Ldarg_0); + + if (klass.IsInterface) + generator.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes)); + else + generator.Emit(OpCodes.Call, klass.GetConstructor(Type.EmptyTypes)); + + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_1); + generator.Emit(OpCodes.Stfld, luaTableField); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldarg_2); + generator.Emit(OpCodes.Stfld, returnTypesField); + generator.Emit(OpCodes.Ret); + // Generates overriden versions of the klass' public virtual methods + var classMethods = klass.GetMethods(); + returnTypes = new Type[classMethods.Length][]; + int i = 0; + + foreach (var method in classMethods) + { + if (klass.IsInterface) + { + GenerateMethod(myType, method, MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.NewSlot, + i, luaTableField, returnTypesField, false, out returnTypes[i]); + i++; + } + else + { + if (!method.IsPrivate && !method.IsFinal && method.IsVirtual) + { + GenerateMethod(myType, method, (method.Attributes | MethodAttributes.NewSlot) ^ MethodAttributes.NewSlot, i, + luaTableField, returnTypesField, true, out returnTypes[i]); + i++; + } + } + } + + // Generates an implementation of the luaInterfaceGetLuaTable method + var returnTableMethod = myType.DefineMethod("LuaInterfaceGetLuaTable", + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(LuaTable), new Type[0]); + myType.DefineMethodOverride(returnTableMethod, typeof(ILuaGeneratedType).GetMethod("LuaInterfaceGetLuaTable")); + generator = returnTableMethod.GetILGenerator(); + generator.Emit(OpCodes.Ldfld, luaTableField); + generator.Emit(OpCodes.Ret); + newType = myType.CreateType(); // Creates the type +#endif + } + + void GetReturnTypesFromMethod(MethodInfo method, out Type[] returnTypes) + { + var paramInfo = method.GetParameters(); + var paramTypes = new Type[paramInfo.Length]; + var returnTypesList = new List(); + + // Counts out and ref parameters, for later use, + // and creates the list of return types + int nOutParams = 0; + int nOutAndRefParams = 0; + var returnType = method.ReturnType; + returnTypesList.Add(returnType); + + for (int i = 0; i < paramTypes.Length; i++) + { + paramTypes[i] = paramInfo[i].ParameterType; + + if (!paramInfo[i].IsIn && paramInfo[i].IsOut) + { + nOutParams++; + } + + if (paramTypes[i].IsByRef) + { + returnTypesList.Add(paramTypes[i].GetElementType()); + nOutAndRefParams++; + } + } + + returnTypes = returnTypesList.ToArray(); + } + +#if !NETSTANDARD && !WINDOWS_UWP + + /* + * Generates an overriden implementation of method inside myType that delegates + * to a function in a Lua table with the same name, if the function exists. If it + * doesn't the method calls the base method (or does nothing, in case of interface + * implementations). + */ + private void GenerateMethod(TypeBuilder myType, MethodInfo method, MethodAttributes attributes, int methodIndex, + FieldInfo luaTableField, FieldInfo returnTypesField, bool generateBase, out Type[] returnTypes) + { + var paramInfo = method.GetParameters(); + var paramTypes = new Type[paramInfo.Length]; + var returnTypesList = new List(); + + // Counts out and ref parameters, for later use, + // and creates the list of return types + int nOutParams = 0; + int nOutAndRefParams = 0; + var returnType = method.ReturnType; + returnTypesList.Add(returnType); + + for (int i = 0; i < paramTypes.Length; i++) + { + paramTypes[i] = paramInfo[i].ParameterType; + if (!paramInfo[i].IsIn && paramInfo[i].IsOut) + nOutParams++; + + if (paramTypes[i].IsByRef) + { + returnTypesList.Add(paramTypes[i].GetElementType()); + nOutAndRefParams++; + } + } + + int[] refArgs = new int[nOutAndRefParams]; + returnTypes = returnTypesList.ToArray(); + + // Generates a version of the method that calls the base implementation + // directly, for use by the base field of the table + if (generateBase) + { + var baseMethod = myType.DefineMethod("__luaInterface_base_" + method.Name, + MethodAttributes.Private | MethodAttributes.NewSlot | MethodAttributes.HideBySig, + returnType, paramTypes); + ILGenerator generatorBase = baseMethod.GetILGenerator(); + generatorBase.Emit(OpCodes.Ldarg_0); + + for (int i = 0; i < paramTypes.Length; i++) + generatorBase.Emit(OpCodes.Ldarg, i + 1); + + generatorBase.Emit(OpCodes.Call, method); + + if (returnType == typeof(void)) + generatorBase.Emit(OpCodes.Pop); + + generatorBase.Emit(OpCodes.Ret); + } + + // Defines the method + var methodImpl = myType.DefineMethod(method.Name, attributes, returnType, paramTypes); + + // If it's an implementation of an interface tells what method it + // is overriding + if (myType.BaseType.Equals(typeof(object))) + myType.DefineMethodOverride(methodImpl, method); + + ILGenerator generator = methodImpl.GetILGenerator(); + generator.DeclareLocal(typeof(object[])); // original arguments + generator.DeclareLocal(typeof(object[])); // with out-only arguments removed + generator.DeclareLocal(typeof(int[])); // indexes of out and ref arguments + + if (!(returnType == typeof(void))) // return value + generator.DeclareLocal(returnType); + else + generator.DeclareLocal(typeof(object)); + + // Initializes local variables + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length); + generator.Emit(OpCodes.Newarr, typeof(object)); + generator.Emit(OpCodes.Stloc_0); + generator.Emit(OpCodes.Ldc_I4, paramTypes.Length - nOutParams + 1); + generator.Emit(OpCodes.Newarr, typeof(object)); + generator.Emit(OpCodes.Stloc_1); + generator.Emit(OpCodes.Ldc_I4, nOutAndRefParams); + generator.Emit(OpCodes.Newarr, typeof(int)); + generator.Emit(OpCodes.Stloc_2); + generator.Emit(OpCodes.Ldloc_1); + generator.Emit(OpCodes.Ldc_I4_0); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldfld, luaTableField); + generator.Emit(OpCodes.Stelem_Ref); + + // Stores the arguments into the local variables, as needed + for (int iArgs = 0, iInArgs = 1, iOutArgs = 0; iArgs < paramTypes.Length; iArgs++) + { + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldc_I4, iArgs); + generator.Emit(OpCodes.Ldarg, iArgs + 1); + + if (paramTypes[iArgs].IsByRef) + { + if (paramTypes[iArgs].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); + generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); + } + else + generator.Emit(OpCodes.Ldind_Ref); + } + else + { + if (paramTypes[iArgs].IsValueType) + generator.Emit(OpCodes.Box, paramTypes[iArgs]); + } + + generator.Emit(OpCodes.Stelem_Ref); + + if (paramTypes[iArgs].IsByRef) + { + generator.Emit(OpCodes.Ldloc_2); + generator.Emit(OpCodes.Ldc_I4, iOutArgs); + generator.Emit(OpCodes.Ldc_I4, iArgs); + generator.Emit(OpCodes.Stelem_I4); + refArgs[iOutArgs] = iArgs; + iOutArgs++; + } + + if (paramInfo[iArgs].IsIn || (!paramInfo[iArgs].IsOut)) + { + generator.Emit(OpCodes.Ldloc_1); + generator.Emit(OpCodes.Ldc_I4, iInArgs); + generator.Emit(OpCodes.Ldarg, iArgs + 1); + + if (paramTypes[iArgs].IsByRef) + { + if (paramTypes[iArgs].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Ldobj, paramTypes[iArgs].GetElementType()); + generator.Emit(OpCodes.Box, paramTypes[iArgs].GetElementType()); + } + else + generator.Emit(OpCodes.Ldind_Ref); + } + else + { + if (paramTypes[iArgs].IsValueType) + generator.Emit(OpCodes.Box, paramTypes[iArgs]); + } + + generator.Emit(OpCodes.Stelem_Ref); + iInArgs++; + } + } + + // Gets the function the method will delegate to by calling + // the getTableFunction method of class LuaClassHelper + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldfld, luaTableField); + generator.Emit(OpCodes.Ldstr, method.Name); + generator.Emit(OpCodes.Call, classHelper.GetMethod("GetTableFunction")); + var lab1 = generator.DefineLabel(); + generator.Emit(OpCodes.Dup); + generator.Emit(OpCodes.Brtrue_S, lab1); + // Function does not exist, call base method + generator.Emit(OpCodes.Pop); + + if (!method.IsAbstract) + { + generator.Emit(OpCodes.Ldarg_0); + + for (int i = 0; i < paramTypes.Length; i++) + generator.Emit(OpCodes.Ldarg, i + 1); + + generator.Emit(OpCodes.Call, method); + + if (returnType == typeof(void)) + generator.Emit(OpCodes.Pop); + + generator.Emit(OpCodes.Ret); + generator.Emit(OpCodes.Ldnull); + } + else + generator.Emit(OpCodes.Ldnull); + + var lab2 = generator.DefineLabel(); + generator.Emit(OpCodes.Br_S, lab2); + generator.MarkLabel(lab1); + // Function exists, call using method callFunction of LuaClassHelper + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldarg_0); + generator.Emit(OpCodes.Ldfld, returnTypesField); + generator.Emit(OpCodes.Ldc_I4, methodIndex); + generator.Emit(OpCodes.Ldelem_Ref); + generator.Emit(OpCodes.Ldloc_1); + generator.Emit(OpCodes.Ldloc_2); + generator.Emit(OpCodes.Call, classHelper.GetMethod("CallFunction")); + generator.MarkLabel(lab2); + + // Stores the function return value + if (returnType == typeof(void)) + { + generator.Emit(OpCodes.Pop); + generator.Emit(OpCodes.Ldnull); + } + else if (returnType.IsValueType) + { + generator.Emit(OpCodes.Unbox, returnType); + generator.Emit(OpCodes.Ldobj, returnType); + } + else + generator.Emit(OpCodes.Castclass, returnType); + + generator.Emit(OpCodes.Stloc_3); + + // Sets return values of out and ref parameters + for (int i = 0; i < refArgs.Length; i++) + { + generator.Emit(OpCodes.Ldarg, refArgs[i] + 1); + generator.Emit(OpCodes.Ldloc_0); + generator.Emit(OpCodes.Ldc_I4, refArgs[i]); + generator.Emit(OpCodes.Ldelem_Ref); + + if (paramTypes[refArgs[i]].GetElementType().IsValueType) + { + generator.Emit(OpCodes.Unbox, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Ldobj, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Stobj, paramTypes[refArgs[i]].GetElementType()); + } + else + { + generator.Emit(OpCodes.Castclass, paramTypes[refArgs[i]].GetElementType()); + generator.Emit(OpCodes.Stind_Ref); + } + } + + // Returns + if (!(returnType == typeof(void))) + generator.Emit(OpCodes.Ldloc_3); + + generator.Emit(OpCodes.Ret); + } +#endif + /* + * Gets an event handler for the event type that delegates to the eventHandler Lua function. + * Caches the generated type. + */ + public LuaEventHandler GetEvent(Type eventHandlerType, LuaFunction eventHandler) + { +#if NETSTANDARD || WINDOWS_UWP + throw new NotImplementedException (" Emit not available on .NET Standard "); +#else + Type eventConsumerType; + + if (eventHandlerCollection.ContainsKey(eventHandlerType)) + eventConsumerType = eventHandlerCollection[eventHandlerType]; + else + { + eventConsumerType = GenerateEvent(eventHandlerType); + eventHandlerCollection[eventHandlerType] = eventConsumerType; + } + + var luaEventHandler = (LuaEventHandler)Activator.CreateInstance(eventConsumerType); + luaEventHandler.Handler = eventHandler; + return luaEventHandler; +#endif + } + + public void RegisterLuaDelegateType(Type delegateType, Type luaDelegateType) + { + _delegateCollection[delegateType] = luaDelegateType; + } + + public void RegisterLuaClassType(Type klass, Type luaClass) + { + var luaClassType = new LuaClassType(); + luaClassType.klass = luaClass; + GetReturnTypesFromClass(klass, out luaClassType.returnTypes); + _classCollection[klass] = luaClassType; + } + /* + * Gets a delegate with delegateType that calls the luaFunc Lua function + * Caches the generated type. + */ + public Delegate GetDelegate(Type delegateType, LuaFunction luaFunc) + { + var returnTypes = new List(); + Type luaDelegateType; + + if (_delegateCollection.ContainsKey(delegateType)) + { + luaDelegateType = _delegateCollection[delegateType]; + } + else + { + luaDelegateType = GenerateDelegate(delegateType); + _delegateCollection[delegateType] = luaDelegateType; + } + + var methodInfo = delegateType.GetMethod("Invoke"); + returnTypes.Add(methodInfo.ReturnType); + + foreach (ParameterInfo paramInfo in methodInfo.GetParameters()) + { + if (paramInfo.ParameterType.IsByRef) + returnTypes.Add(paramInfo.ParameterType); + } + + var luaDelegate = (LuaDelegate)Activator.CreateInstance(luaDelegateType); + luaDelegate.Function = luaFunc; + luaDelegate.ReturnTypes = returnTypes.ToArray(); + return Delegate.CreateDelegate(delegateType, luaDelegate, "CallFunction"); + } + + /* + * Gets an instance of an implementation of the klass interface or + * subclass of klass that delegates public virtual methods to the + * luaTable table. + * Caches the generated type. + */ + public object GetClassInstance(Type klass, LuaTable luaTable) + { + LuaClassType luaClassType; + + if (_classCollection.ContainsKey(klass)) + luaClassType = _classCollection[klass]; + else + { + luaClassType = new LuaClassType(); + GenerateClass(klass, out luaClassType.klass, out luaClassType.returnTypes); + _classCollection[klass] = luaClassType; + } + + return Activator.CreateInstance(luaClassType.klass, new object[] { + luaTable, + luaClassType.returnTypes + }); + } + } +} diff --git a/src/LUA/GenerateEventAssembly/DelegateGenerator.cs b/src/LUA/GenerateEventAssembly/DelegateGenerator.cs new file mode 100644 index 0000000..62ad271 --- /dev/null +++ b/src/LUA/GenerateEventAssembly/DelegateGenerator.cs @@ -0,0 +1,22 @@ +using System; +using LuaState = KeraLua.Lua; + +namespace NLua +{ + class DelegateGenerator + { + private readonly ObjectTranslator _translator; + private readonly Type _delegateType; + + public DelegateGenerator(ObjectTranslator objectTranslator, Type type) + { + _translator = objectTranslator; + _delegateType = type; + } + + public object ExtractGenerated(LuaState luaState, int stackPos) + { + return CodeGeneration.Instance.GetDelegate(_delegateType, _translator.GetFunction(luaState, stackPos)); + } + } +} \ No newline at end of file diff --git a/src/LUA/GenerateEventAssembly/ILuaGeneratedType.cs b/src/LUA/GenerateEventAssembly/ILuaGeneratedType.cs new file mode 100644 index 0000000..37499e1 --- /dev/null +++ b/src/LUA/GenerateEventAssembly/ILuaGeneratedType.cs @@ -0,0 +1,11 @@ +namespace NLua +{ + /* + * Common interface for types generated from tables. The method + * returns the table that overrides some or all of the type's methods. + */ + public interface ILuaGeneratedType + { + LuaTable LuaInterfaceGetLuaTable(); + } +} \ No newline at end of file diff --git a/src/LUA/GenerateEventAssembly/LuaClassType.cs b/src/LUA/GenerateEventAssembly/LuaClassType.cs new file mode 100644 index 0000000..8b861d7 --- /dev/null +++ b/src/LUA/GenerateEventAssembly/LuaClassType.cs @@ -0,0 +1,15 @@ +using System; + +namespace NLua +{ + /* + * Structure to store a type and the return types of + * its methods (the type of the returned value and out/ref + * parameters). + */ + struct LuaClassType + { + public Type klass; + public Type[][] returnTypes; + } +} \ No newline at end of file diff --git a/src/LUA/Lua.cs b/src/LUA/Lua.cs new file mode 100644 index 0000000..00c288d --- /dev/null +++ b/src/LUA/Lua.cs @@ -0,0 +1,1262 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using KeraLua; + +using NLua.Event; +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 Lua : IDisposable + { + #region lua debug functions + /// + /// Event that is raised when an exception occures during a hook call. + /// + public event EventHandler HookException; + /// + /// Event when lua hook callback is called + /// + /// + /// Is only raised if SetDebugHook is called before. + /// + public event EventHandler DebugHook; + /// + /// lua hook calback delegate + /// + private LuaHookFunction _hookCallback; + #endregion + #region Globals auto-complete + private readonly List _globals = new List(); + private bool _globalsSorted; + #endregion + private LuaState _luaState; + /// + /// True while a script is being executed + /// + public bool IsExecuting => _executing; + + public LuaState State => _luaState; + + private ObjectTranslator _translator; + + /// + /// Used to ensure multiple .net threads all get serialized by this single lock for access to the lua stack/objects + /// + //private object luaLock = new object(); + private bool _StatePassed; + private bool _executing; + + // The commented code bellow is the initLua, the code assigned here is minified for size/performance reasons. + private const string InitLuanet = @"local a={}local rawget=rawget;local b=luanet.import_type;local c=luanet.load_assembly;luanet.error,luanet.type=error,type;function a:__index(d)local e=rawget(self,'.fqn')e=(e and e..'.'or'')..d;local f=rawget(luanet,d)or b(e)if f==nil then pcall(c,e)f={['.fqn']=e}setmetatable(f,a)end;rawset(self,d,f)return f end;function a:__call(...)error('No such type: '..rawget(self,'.fqn'),2)end;luanet['.fqn']=false;setmetatable(luanet,a)luanet.load_assembly('mscorlib')"; + //@"local metatable = {} + // local rawget = rawget + // local import_type = luanet.import_type + // local load_assembly = luanet.load_assembly + // luanet.error, luanet.type = error, type + // -- Lookup a .NET identifier component. + // function metatable:__index(key) -- key is e.g. 'Form' + // -- Get the fully-qualified name, e.g. 'System.Windows.Forms.Form' + // local fqn = rawget(self,'.fqn') + // fqn = ((fqn and fqn .. '.') or '') .. key + + // -- Try to find either a luanet function or a CLR type + // local obj = rawget(luanet,key) or import_type(fqn) + + // -- If key is neither a luanet function or a CLR type, then it is simply + // -- an identifier component. + // if obj == nil then + // -- It might be an assembly, so we load it too. + // pcall(load_assembly,fqn) + // obj = { ['.fqn'] = fqn } + // setmetatable(obj, metatable) + // end + + // -- Cache this lookup + // rawset(self, key, obj) + // return obj + // end + + // -- A non-type has been called; e.g. foo = System.Foo() + // function metatable:__call(...) + // error('No such type: ' .. rawget(self,'.fqn'), 2) + // end + + // -- This is the root of the .NET namespace + // luanet['.fqn'] = false + // setmetatable(luanet, metatable) + + // -- Preload the mscorlib assembly + // luanet.load_assembly('mscorlib')"; + + private const string ClrPackage = @"if not luanet then require'luanet'end;local a,b=luanet.import_type,luanet.load_assembly;local c={__index=function(d,e)local f=rawget(d,e)if f==nil then f=a(d.packageName.."".""..e)if f==nil then f=a(e)end;d[e]=f end;return f end}function luanet.namespace(g)if type(g)=='table'then local h={}for i=1,#g do h[i]=luanet.namespace(g[i])end;return unpack(h)end;local j={packageName=g}setmetatable(j,c)return j end;local k,l;local function m()l={}k={__index=function(n,e)for i,d in ipairs(l)do local f=d[e]if f then _G[e]=f;return f end end end}setmetatable(_G,k)end;function CLRPackage(o,p)p=p or o;local q=pcall(b,o)return luanet.namespace(p)end;function import(o,p)if not k then m()end;if not p then local i=o:find('%.dll$')if i then p=o:sub(1,i-1)else p=o end end;local j=CLRPackage(o,p)table.insert(l,j)return j end;function luanet.make_array(r,s)local t=r[#s]for i,u in ipairs(s)do t:SetValue(u,i-1)end;return t end;function luanet.each(v)local w=v:GetEnumerator()return function()if w:MoveNext()then return w.Current end end end"; +//@"--- +//--- This lua module provides auto importing of .net classes into a named package. +//--- Makes for super easy use of LuaInterface glue +//--- +//--- example: +//--- Threading = CLRPackage(""System"", ""System.Threading"") +//--- Threading.Thread.Sleep(100) +//--- +//--- Extensions: +//--- import() is a version of CLRPackage() which puts the package into a list which is used by a global __index lookup, +//--- and thus works rather like C#'s using statement. It also recognizes the case where one is importing a local +//--- assembly, which must end with an explicit .dll extension. + +//--- Alternatively, luanet.namespace can be used for convenience without polluting the global namespace: +//--- local sys,sysi = luanet.namespace {'System','System.IO'} +//-- sys.Console.WriteLine(""we are at {0}"",sysi.Directory.GetCurrentDirectory()) + + +//-- LuaInterface hosted with stock Lua interpreter will need to explicitly require this... +//if not luanet then require 'luanet' end + +//local import_type, load_assembly = luanet.import_type, luanet.load_assembly + +//local mt = { +// --- Lookup a previously unfound class and add it to our table +// __index = function(package, classname) +// local class = rawget(package, classname) +// if class == nil then +// class = import_type(package.packageName .. ""."" .. classname) +// if class == nil then class = import_type(classname) end +// package[classname] = class -- keep what we found around, so it will be shared +// end +// return class +// end +//} + +//function luanet.namespace(ns) +// if type(ns) == 'table' then +// local res = {} +// for i = 1,#ns do +// res[i] = luanet.namespace(ns[i]) +// end +// return unpack(res) +// end +// -- FIXME - table.packageName could instead be a private index (see Lua 13.4.4) +// local t = { packageName = ns } +// setmetatable(t,mt) +// return t +//end + +//local globalMT, packages + +//local function set_global_mt() +// packages = {} +// globalMT = { +// __index = function(T,classname) +// for i,package in ipairs(packages) do +// local class = package[classname] +// if class then +// _G[classname] = class +// return class +// end +// end +// end +// } +// setmetatable(_G, globalMT) +//end + +//--- Create a new Package class +//function CLRPackage(assemblyName, packageName) +// -- a sensible default... +// packageName = packageName or assemblyName +// local ok = pcall(load_assembly,assemblyName) -- Make sure our assembly is loaded +// return luanet.namespace(packageName) +//end + +//function import (assemblyName, packageName) +// if not globalMT then +// set_global_mt() +// end +// if not packageName then +// local i = assemblyName:find('%.dll$') +// if i then packageName = assemblyName:sub(1,i-1) +// else packageName = assemblyName end +// end +// local t = CLRPackage(assemblyName,packageName) +// table.insert(packages,t) +// return t +//end + + +//function luanet.make_array (tp,tbl) +// local arr = tp[#tbl] +// for i,v in ipairs(tbl) do +// arr:SetValue(v,i-1) +// end +// return arr +//end + +//function luanet.each(o) +// local e = o:GetEnumerator() +// return function() +// if e:MoveNext() then +// return e.Current +// end +// end +//end +//"; + public bool UseTraceback { get; set; } = false; + + /// + /// The maximum number of recursive steps to take when adding global reference variables. Defaults to 2. + /// + public int MaximumRecursion { get; set; } = 2; + + #region Globals auto-complete + /// + /// An alphabetically sorted list of all globals (objects, methods, etc.) externally added to this Lua instance + /// + /// Members of globals are also listed. The formatting is optimized for text input auto-completion. + public IEnumerable Globals { + get + { + // Only sort list when necessary + if (!_globalsSorted) + { + _globals.Sort(); + _globalsSorted = true; + } + + return _globals; + } + } + #endregion + + public Lua() + { + _luaState = new LuaState(); + Init(); + // We need to keep this in a managed reference so the delegate doesn't get garbage collected + _luaState.AtPanic(PanicCallback); + } + + /* + * CAUTION: NLua.Lua instances can't share the same lua state! + */ + public Lua(LuaState luaState) + { + luaState.PushString("NLua_Loaded"); + luaState.GetTable((int)LuaRegistry.Index); + + if (luaState.ToBoolean(-1)) + { + luaState.SetTop(-2); + throw new LuaException("There is already a NLua.Lua instance associated with this Lua state"); + } + + _luaState = luaState; + _StatePassed = true; + luaState.SetTop(-2); + Init(); + } + + void Init() + { + _luaState.PushString("NLua_Loaded"); + _luaState.PushBoolean(true); + _luaState.SetTable((int)LuaRegistry.Index); + if (_StatePassed == false) + { + _luaState.NewTable(); + _luaState.SetGlobal("luanet"); + } + _luaState.PushGlobalTable(); + _luaState.GetGlobal("luanet"); + _luaState.PushString("getmetatable"); + _luaState.GetGlobal("getmetatable"); + _luaState.SetTable(-3); + _luaState.PopGlobalTable(); + _translator = new ObjectTranslator(this, _luaState); + + ObjectTranslatorPool.Instance.Add(_luaState, _translator); + + _luaState.PopGlobalTable(); + _luaState.DoString(InitLuanet); + } + + public void Close() + { + if (_StatePassed || _luaState == null) + return; + + _luaState.Close(); + ObjectTranslatorPool.Instance.Remove(_luaState); + _luaState = null; + } + +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int PanicCallback(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + string reason = string.Format("Unprotected error in call to Lua API ({0})", luaState.ToString(-1, false)); + throw new LuaException(reason); + } + + /// + /// Assuming we have a Lua error string sitting on the stack, throw a C# exception out to the user's app + /// + /// Thrown if the script caused an exception + private void ThrowExceptionFromError(int oldTop) + { + object err = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + + // A pre-wrapped exception - just rethrow it (stack trace of InnerException will be preserved) + var luaEx = err as LuaScriptException; + + if (luaEx != null) + throw luaEx; + + // A non-wrapped Lua error (best interpreted as a string) - wrap it and throw it + if (err == null) + err = "Unknown Lua Error"; + + throw new LuaScriptException(err.ToString(), string.Empty); + } + + /// + /// Push a debug.traceback reference onto the stack, for a pcall function to use as error handler. (Remember to increment any top-of-stack markers!) + /// + private static int PushDebugTraceback(LuaState luaState, int argCount) + { + luaState.GetGlobal("debug"); + luaState.GetField(-1, "traceback"); + luaState.Remove(-2); + int errIndex = -argCount - 2; + luaState.Insert(errIndex); + return errIndex; + } + + /// + /// Return a debug.traceback() call result (a multi-line string, containing a full stack trace, including C calls. + /// Note: it won't return anything unless the interpreter is in the middle of execution - that is, it only makes sense to call it from a method called from Lua, or during a coroutine yield. + /// + public string GetDebugTraceback() + { + int oldTop = _luaState.GetTop(); + _luaState.GetGlobal("debug"); // stack: debug + _luaState.GetField(-1, "traceback"); // stack: debug,traceback + _luaState.Remove(-2); // stack: traceback + _luaState.PCall(0, -1, 0); + return _translator.PopValues(_luaState, oldTop)[0] as string; + } + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + internal int SetPendingException(Exception e) + { + var caughtExcept = e; + + if (caughtExcept == null) + return 0; + + _translator.ThrowError(_luaState, caughtExcept); + return 1; + } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(string chunk, string name) + { + int oldTop = _luaState.GetTop(); + _executing = true; + + try + { + if (_luaState.LoadString(chunk, name) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// + /// + /// + /// + /// + public LuaFunction LoadString(byte[] chunk, string name) + { + int oldTop = _luaState.GetTop(); + _executing = true; + + try + { + if (_luaState.LoadBuffer(chunk, name) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// Load a File on, and return a LuaFunction to execute the file loaded (useful to see if the syntax of a file is ok) + /// + /// + /// + public LuaFunction LoadFile(string fileName) + { + int oldTop = _luaState.GetTop(); + + if (_luaState.LoadFile(fileName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + var result = _translator.GetFunction(_luaState, -1); + _translator.PopValues(_luaState, oldTop); + return result; + } + + /// + /// Executes a Lua chunk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk. Defaults to "chunk". + /// + public object[] DoString(byte[] chunk, string chunkName = "chunk") + { + int oldTop = _luaState.GetTop(); + _executing = true; + + if (_luaState.LoadBuffer(chunk, chunkName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + int errorFunctionIndex = 0; + + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + /// + /// Executes a Lua chunk and returns all the chunk's return values in an array. + /// + /// Chunk to execute + /// Name to associate with the chunk. Defaults to "chunk". + /// + public object[] DoString(string chunk, string chunkName = "chunk") + { + int oldTop = _luaState.GetTop(); + _executing = true; + + if (_luaState.LoadString(chunk, chunkName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + int errorFunctionIndex = 0; + + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + /* + * Executes a Lua file and returns all the chunk's return + * values in an array + */ + public object[] DoFile(string fileName) + { + int oldTop = _luaState.GetTop(); + + if (_luaState.LoadFile(fileName) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + _executing = true; + + int errorFunctionIndex = 0; + if (UseTraceback) + { + errorFunctionIndex = PushDebugTraceback(_luaState, 0); + oldTop++; + } + + try + { + if (_luaState.PCall(0, -1, errorFunctionIndex) != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + + return _translator.PopValues(_luaState, oldTop); + } + finally + { + _executing = false; + } + } + + public object GetObjectFromPath(string fullPath) + { + int oldTop = _luaState.GetTop(); + string[] path = FullPathToArray(fullPath); + _luaState.GetGlobal(path[0]); + object returnValue = _translator.GetObject(_luaState, -1); + + if (path.Length > 1) + { + var dispose = returnValue as LuaBase; + string[] remainingPath = new string[path.Length - 1]; + Array.Copy(path, 1, remainingPath, 0, path.Length - 1); + returnValue = GetObject(remainingPath); + dispose?.Dispose(); + } + + _luaState.SetTop(oldTop); + return returnValue; + } + + public void SetObjectToPath(string fullPath, object value) + { + int oldTop = _luaState.GetTop(); + string[] path = FullPathToArray(fullPath); + + if (path.Length == 1) + { + _translator.Push(_luaState, value); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + string[] remainingPath = new string[path.Length - 1]; + Array.Copy(path, 1, remainingPath, 0, path.Length - 1); + SetObject(remainingPath, value); + } + + _luaState.SetTop(oldTop); + + // Globals auto-complete + if (value == null) + { + // Remove now obsolete entries + _globals.Remove(fullPath); + } + else + { + // Add new entries + if (!_globals.Contains(fullPath)) + RegisterGlobal(fullPath, value.GetType(), 0); + } + } + /* + * Indexer for global variables from the LuaInterpreter + * Supports navigation of tables by using . operator + */ + public object this[string fullPath] { + get + { + // Silently convert Lua integer to double for backward compatibility with index[] operator + object obj = GetObjectFromPath(fullPath); + if (obj is long l) + return (double)l; + return obj; + } + set + { + SetObjectToPath(fullPath, value); + } + } + + #region Globals auto-complete + /// + /// Adds an entry to (recursivley handles 2 levels of members) + /// + /// The index accessor path ot the entry + /// The type of the entry + /// How deep have we gone with recursion? + private void RegisterGlobal(string path, Type type, int recursionCounter) + { + // If the type is a global method, list it directly + if (type == typeof(LuaFunction)) + { + // Format for easy method invocation + _globals.Add(path + "("); + } + // If the type is a class or an interface and recursion hasn't been running too long, list the members + else if ((type.IsClass || type.IsInterface) && type != typeof(string) && recursionCounter < MaximumRecursion) + { + #region Methods + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + string name = method.Name; + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!method.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!method.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) && + // Exclude some generic .NET methods that wouldn't be very usefull in Lua + name != "GetType" && name != "GetHashCode" && name != "Equals" && + name != "ToString" && name != "Clone" && name != "Dispose" && + name != "GetEnumerator" && name != "CopyTo" && + !name.StartsWith("get_", StringComparison.Ordinal) && + !name.StartsWith("set_", StringComparison.Ordinal) && + !name.StartsWith("add_", StringComparison.Ordinal) && + !name.StartsWith("remove_", StringComparison.Ordinal)) + { + // Format for easy method invocation + string command = path + ":" + name + "("; + + if (method.GetParameters().Length == 0) + command += ")"; + _globals.Add(command); + } + } + #endregion + + #region Fields + foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!field.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!field.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any())) + { + // Go into recursion for members + RegisterGlobal(path + "." + field.Name, field.FieldType, recursionCounter + 1); + } + } + #endregion + + #region Properties + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) + { + if ( + // Check that the LuaHideAttribute and LuaGlobalAttribute were not applied + (!property.GetCustomAttributes(typeof(LuaHideAttribute), false).Any()) && + (!property.GetCustomAttributes(typeof(LuaGlobalAttribute), false).Any()) + // Exclude some generic .NET properties that wouldn't be very useful in Lua + && property.Name != "Item") + { + // Go into recursion for members + RegisterGlobal(path + "." + property.Name, property.PropertyType, recursionCounter + 1); + } + } + #endregion + } + else + _globals.Add(path); // Otherwise simply add the element to the list + + // List will need to be sorted on next access + _globalsSorted = false; + } + #endregion + + /* + * Navigates a table in the top of the stack, returning + * the value of the specified field + */ + object GetObject(string[] remainingPath) + { + object returnValue = null; + + for (int i = 0; i < remainingPath.Length; i++) + { + _luaState.PushString(remainingPath[i]); + _luaState.GetTable(-2); + returnValue = _translator.GetObject(_luaState, -1); + + if (returnValue == null) + break; + } + + return returnValue; + } + + /* + * Gets a numeric global variable + */ + public double GetNumber(string fullPath) + { + // Silently convert Lua integer to double for backward compatibility with GetNumber method + object obj = GetObjectFromPath(fullPath); + if (obj is long l) + return l; + return (double)obj; + } + + public int GetInteger(string fullPath) + { + return (int)(long)GetObjectFromPath(fullPath); + } + + public long GetLong(string fullPath) + { + return (long)GetObjectFromPath(fullPath); + } + + /* + * Gets a string global variable + */ + public string GetString(string fullPath) + { + object obj = GetObjectFromPath(fullPath); + return obj.ToString(); + } + + /* + * Gets a table global variable + */ + public LuaTable GetTable(string fullPath) + { + return (LuaTable)GetObjectFromPath(fullPath); + } + + /* + * Gets a table global variable as an object implementing + * the interfaceType interface + */ + public object GetTable(Type interfaceType, string fullPath) + { + return CodeGeneration.Instance.GetClassInstance(interfaceType, GetTable(fullPath)); + } + + /* + * Gets a function global variable + */ + public LuaFunction GetFunction(string fullPath) + { + object obj = GetObjectFromPath(fullPath); + var luaFunction = obj as LuaFunction; + if (luaFunction != null) + return luaFunction; + + luaFunction = new LuaFunction((LuaNativeFunction) obj, this); + return luaFunction; + } + + /* + * Register a delegate type to be used to convert Lua functions to C# delegates (useful for iOS where there is no dynamic code generation) + * type delegateType + */ + public void RegisterLuaDelegateType(Type delegateType, Type luaDelegateType) + { + CodeGeneration.Instance.RegisterLuaDelegateType(delegateType, luaDelegateType); + } + + public void RegisterLuaClassType(Type klass, Type luaClass) + { + CodeGeneration.Instance.RegisterLuaClassType(klass, luaClass); + } + + // ReSharper disable once InconsistentNaming + public void LoadCLRPackage() + { + _luaState.DoString(ClrPackage); + } + /* + * Gets a function global variable as a delegate of + * type delegateType + */ + public Delegate GetFunction(Type delegateType, string fullPath) + { + return CodeGeneration.Instance.GetDelegate(delegateType, GetFunction(fullPath)); + } + + /* + * Calls the object as a function with the provided arguments, + * returning the function's returned values inside an array + */ + internal object[] CallFunction(object function, object[] args) + { + return CallFunction(function, args, null); + } + + /* + * Calls the object as a function with the provided arguments and + * casting returned values to the types in returnTypes before returning + * them in an array + */ + internal object[] CallFunction(object function, object[] args, Type[] returnTypes) + { + int nArgs = 0; + int oldTop = _luaState.GetTop(); + + if (!_luaState.CheckStack(args.Length + 6)) + throw new LuaException("Lua stack overflow"); + + _translator.Push(_luaState, function); + + if (args.Length > 0) + { + nArgs = args.Length; + + for (int i = 0; i < args.Length; i++) + _translator.Push(_luaState, args[i]); + } + + _executing = true; + + try + { + int errfunction = 0; + if (UseTraceback) + { + errfunction = PushDebugTraceback(_luaState, nArgs); + oldTop++; + } + + LuaStatus error = _luaState.PCall(nArgs, -1, errfunction); + if (error != LuaStatus.OK) + ThrowExceptionFromError(oldTop); + } + finally + { + _executing = false; + } + + if (returnTypes != null) + return _translator.PopValues(_luaState, oldTop, returnTypes); + + return _translator.PopValues(_luaState, oldTop); + } + + /* + * Navigates a table to set the value of one of its fields + */ + void SetObject(string[] remainingPath, object val) + { + for (int i = 0; i < remainingPath.Length - 1; i++) + { + _luaState.PushString(remainingPath[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(remainingPath[remainingPath.Length - 1]); + _translator.Push(_luaState, val); + _luaState.SetTable(-3); + } + + string[] FullPathToArray(string fullPath) + { + return fullPath.SplitWithEscape('.', '\\').ToArray(); + } + /* + * Creates a new table as a global variable or as a field + * inside an existing table + */ + public void NewTable(string fullPath) + { + string[] path = FullPathToArray(fullPath); + int oldTop = _luaState.GetTop(); + + if (path.Length == 1) + { + _luaState.NewTable(); + _luaState.SetGlobal(fullPath); + } + else + { + _luaState.GetGlobal(path[0]); + + for (int i = 1; i < path.Length - 1; i++) + { + _luaState.PushString(path[i]); + _luaState.GetTable(-2); + } + + _luaState.PushString(path[path.Length - 1]); + _luaState.NewTable(); + _luaState.SetTable(-3); + } + + _luaState.SetTop( oldTop); + } + + public Dictionary GetTableDict(LuaTable table) + { + if (table == null) + throw new ArgumentNullException(nameof(table)); + + var dict = new Dictionary(); + int oldTop = _luaState.GetTop(); + _translator.Push(_luaState, table); + _luaState.PushNil(); + + while (_luaState.Next(-2)) + { + dict[_translator.GetObject(_luaState, -2)] = _translator.GetObject(_luaState, -1); + _luaState.SetTop(-2); + } + + _luaState.SetTop(oldTop); + return dict; + } + + /* + * Lets go of a previously allocated reference to a table, function + * or userdata + */ + #region lua debug functions + /// + /// Activates the debug hook + /// + /// Mask + /// Count + /// see lua docs. -1 if hook is already set + public int SetDebugHook(LuaHookMask mask, int count) + { + if (_hookCallback == null) + { + _hookCallback = DebugHookCallback; + _luaState.SetHook(_hookCallback, mask, count); + } + + return -1; + } + + /// + /// Removes the debug hook + /// + /// see lua docs + public void RemoveDebugHook() + { + _hookCallback = null; + _luaState.SetHook(null, LuaHookMask.Disabled, 0); + } + + /// + /// Gets the hook mask. + /// + /// hook mask + public LuaHookMask GetHookMask() + { + return _luaState.HookMask; + } + + /// + /// Gets the hook count + /// + /// see lua docs + public int GetHookCount() + { + return _luaState.HookCount; + } + + + /// + /// Gets local (see lua docs) + /// + /// lua debug structure + /// see lua docs + /// see lua docs + public string GetLocal(LuaDebug luaDebug, int n) + { + return _luaState.GetLocal(luaDebug, n); + } + + /// + /// Sets local (see lua docs) + /// + /// lua debug structure + /// see lua docs + /// see lua docs + public string SetLocal(LuaDebug luaDebug, int n) + { + return _luaState.SetLocal(luaDebug, n); + } + + public int GetStack(int level, ref LuaDebug ar) + { + return _luaState.GetStack(level, ref ar); + } + + public bool GetInfo(string what, ref LuaDebug ar) + { + return _luaState.GetInfo(what, ref ar); + } + + /// + /// Gets up value (see lua docs) + /// + /// see lua docs + /// see lua docs + /// see lua docs + public string GetUpValue(int funcindex, int n) + { + return _luaState.GetUpValue(funcindex, n); + } + + /// + /// Sets up value (see lua docs) + /// + /// see lua docs + /// see lua docs + /// see lua docs + public string SetUpValue(int funcindex, int n) + { + return _luaState.SetUpValue(funcindex, n); + } + + /// + /// Delegate that is called on lua hook callback + /// + /// lua state + /// Pointer to LuaDebug (lua_debug) structure + /// +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaHookFunction))] +#endif + static void DebugHookCallback(IntPtr luaState, IntPtr luaDebug) + { + var state = LuaState.FromIntPtr(luaState); + + state.GetStack(0, luaDebug); + + if (!state.GetInfo("Snlu", luaDebug)) + return; + + var debug = LuaDebug.FromIntPtr(luaDebug); + + ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(state); + Lua lua = translator.Interpreter; + lua.DebugHookCallbackInternal(debug); + } + + private void DebugHookCallbackInternal(LuaDebug luaDebug) + { + try + { + var temp = DebugHook; + + if (temp != null) + temp(this, new DebugHookEventArgs(luaDebug)); + } + catch (Exception ex) + { + OnHookException(new HookExceptionEventArgs(ex)); + } + } + + private void OnHookException(HookExceptionEventArgs e) + { + var temp = HookException; + if (temp != null) + temp(this, e); + } + + /// + /// Pops a value from the lua stack. + /// + /// Returns the top value from the lua stack. + public object Pop() + { + int top = _luaState.GetTop(); + return _translator.PopValues(_luaState, top - 1)[0]; + } + + /// + /// Pushes a value onto the lua stack. + /// + /// Value to push. + public void Push(object value) + { + _translator.Push(_luaState, value); + } + #endregion + + internal void DisposeInternal(int reference, bool finalized) + { + if (finalized && _translator != null) + { + _translator.AddFinalizedReference(reference); + return; + } + + if (_luaState != null && !finalized) + _luaState.Unref(reference); + } + + /* + * Gets a field of the table corresponding to the provided reference + * using rawget (do not use metatables) + */ + internal object RawGetObject(int reference, string field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _luaState.PushString(field); + _luaState.RawGet(-2); + object obj = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + return obj; + } + + /* + * Gets a field of the table or userdata corresponding to the provided reference + */ + internal object GetObject(int reference, string field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + object returnValue = GetObject(FullPathToArray(field)); + _luaState.SetTop(oldTop); + return returnValue; + } + + /* + * Gets a numeric field of the table or userdata corresponding the the provided reference + */ + internal object GetObject(int reference, object field) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _translator.Push(_luaState, field); + _luaState.GetTable(-2); + object returnValue = _translator.GetObject(_luaState, -1); + _luaState.SetTop(oldTop); + return returnValue; + } + + /* + * Sets a field of the table or userdata corresponding the the provided reference + * to the provided value + */ + internal void SetObject(int reference, string field, object val) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + SetObject(FullPathToArray(field), val); + _luaState.SetTop(oldTop); + } + + /* + * Sets a numeric field of the table or userdata corresponding the the provided reference + * to the provided value + */ + internal void SetObject(int reference, object field, object val) + { + int oldTop = _luaState.GetTop(); + _luaState.GetRef(reference); + _translator.Push(_luaState, field); + _translator.Push(_luaState, val); + _luaState.SetTable(-3); + _luaState.SetTop(oldTop); + } + + public LuaFunction RegisterFunction(string path, MethodBase function) + { + return RegisterFunction(path, null, function); + } + + /* + * Registers an object's method as a Lua function (global or table field) + * The method may have any signature + */ + public LuaFunction RegisterFunction(string path, object target, MethodBase function) + { + // We leave nothing on the stack when we are done + int oldTop = _luaState.GetTop(); + var wrapper = new LuaMethodWrapper(_translator, target, new ProxyType(function.DeclaringType), function); + + _translator.Push(_luaState, new LuaNativeFunction(wrapper.InvokeFunction)); + + object value = _translator.GetObject(_luaState, -1); + SetObjectToPath(path, value); + + LuaFunction f = GetFunction(path); + _luaState.SetTop(oldTop); + return f; + } + + /* + * Compares the two values referenced by ref1 and ref2 for equality + */ + internal bool CompareRef(int ref1, int ref2) + { + int top = _luaState.GetTop(); + _luaState.GetRef(ref1); + _luaState.GetRef(ref2); + bool equal = _luaState.AreEqual(-1, -2); + _luaState.SetTop(top); + return equal; + } + + // ReSharper disable once InconsistentNaming + internal void PushCSFunction(LuaNativeFunction function) + { + _translator.PushFunction(_luaState, function); + } + + #region IDisposable Members + + ~Lua() + { + Dispose(); + } + public virtual void Dispose() + { + if (_translator != null) + { + _translator.PendingEvents.Dispose(); + if (_translator.Tag != IntPtr.Zero) + Marshal.FreeHGlobal(_translator.Tag); + _translator = null; + } + + Close(); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/src/LUA/LuaBase.cs b/src/LUA/LuaBase.cs new file mode 100644 index 0000000..75a5441 --- /dev/null +++ b/src/LUA/LuaBase.cs @@ -0,0 +1,86 @@ +using System; + +namespace NLua +{ + /// + /// Base class to provide consistent disposal flow across lua objects. Uses code provided by Yves Duhoux and suggestions by Hans Schmeidenbacher and Qingrui Li + /// + public abstract class LuaBase : IDisposable + { + private bool _disposed; + protected readonly int _Reference; + Lua _lua; + + protected bool TryGet(out Lua lua) + { + if (_lua.State == null) + { + lua = null; + return false; + } + + lua = _lua; + return true; + } + protected LuaBase(int reference, Lua lua) + { + _lua = lua; + _Reference = reference; + } + + ~LuaBase() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + void DisposeLuaReference(bool finalized) + { + if (_lua == null) + return; + Lua lua; + if (!TryGet(out lua)) + return; + + lua.DisposeInternal(_Reference, finalized); + } + public virtual void Dispose(bool disposeManagedResources) + { + if (_disposed) + return; + + bool finalized = !disposeManagedResources; + + if (_Reference != 0) + { + DisposeLuaReference(finalized); + } + + _lua = null; + _disposed = true; + } + + public override bool Equals(object o) + { + var reference = o as LuaBase; + if (reference == null) + return false; + + Lua lua; + if (!TryGet(out lua)) + return false; + + return lua.CompareRef(reference._Reference, _Reference); + } + + public override int GetHashCode() + { + return _Reference; + } + } +} \ No newline at end of file diff --git a/src/LUA/LuaFunction.cs b/src/LUA/LuaFunction.cs new file mode 100644 index 0000000..9e2674f --- /dev/null +++ b/src/LUA/LuaFunction.cs @@ -0,0 +1,91 @@ +using System; +using KeraLua; + +using LuaState = KeraLua.Lua; +using LuaNativeFunction = KeraLua.LuaFunction; + +namespace NLua +{ + public class LuaFunction : LuaBase + { + internal readonly LuaNativeFunction function; + + public LuaFunction(int reference, Lua interpreter):base(reference, interpreter) + { + function = null; + } + + public LuaFunction(LuaNativeFunction nativeFunction, Lua interpreter):base (0, interpreter) + { + function = nativeFunction; + } + + /* + * Calls the function casting return values to the types + * in returnTypes + */ + internal object[] Call(object[] args, Type[] returnTypes) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args, returnTypes); + } + + /* + * Calls the function and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args); + } + + /* + * Pushes the function into the Lua stack + */ + internal void Push(LuaState luaState) + { + Lua lua; + if (!TryGet(out lua)) + return; + + if (_Reference != 0) + luaState.RawGetInteger(LuaRegistry.Index, _Reference); + else + lua.PushCSFunction(function); + } + + public override string ToString() + { + return "function"; + } + + public override bool Equals(object o) + { + var l = o as LuaFunction; + + if (l == null) + return false; + + Lua lua; + if (!TryGet(out lua)) + return false; + + if (_Reference != 0 && l._Reference != 0) + return lua.CompareRef(l._Reference, _Reference); + + return function == l.function; + } + + public override int GetHashCode() + { + return _Reference != 0 ? _Reference : function.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/src/LUA/LuaGlobalAttribute.cs b/src/LUA/LuaGlobalAttribute.cs new file mode 100644 index 0000000..27e6393 --- /dev/null +++ b/src/LUA/LuaGlobalAttribute.cs @@ -0,0 +1,23 @@ +using System; + +namespace NLua +{ + /// + /// Marks a method for global usage in Lua scripts + /// + /// + /// + [AttributeUsage(AttributeTargets.Method)] + public sealed class LuaGlobalAttribute : Attribute + { + /// + /// An alternative name to use for calling the function in Lua - leave empty for CLR name + /// + public string Name { get; set; } + + /// + /// A description of the function + /// + public string Description { get; set; } + } +} \ No newline at end of file diff --git a/src/LUA/LuaHideAttribute.cs b/src/LUA/LuaHideAttribute.cs new file mode 100644 index 0000000..c5840fd --- /dev/null +++ b/src/LUA/LuaHideAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace NLua +{ + /// + /// Marks a method, field or property to be hidden from Lua auto-completion + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Field | AttributeTargets.Property)] + public sealed class LuaHideAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/LUA/LuaRegistrationHelper.cs b/src/LUA/LuaRegistrationHelper.cs new file mode 100644 index 0000000..eca12a9 --- /dev/null +++ b/src/LUA/LuaRegistrationHelper.cs @@ -0,0 +1,98 @@ +using System; +using System.Reflection; +using System.Diagnostics.CodeAnalysis; + +namespace NLua +{ + public static class LuaRegistrationHelper + { + #region Tagged instance methods + /// + /// Registers all public instance methods in an object tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The object to get the methods from + public static void TaggedInstanceMethods(Lua lua, object o) + { + #region Sanity checks + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + if (o == null) + throw new ArgumentNullException(nameof(o)); + #endregion + + foreach (var method in o.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), true)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, o, method); // CLR name + else + lua.RegisterFunction(attribute.Name, o, method); // Custom name + } + } + } + #endregion + + #region Tagged static methods + /// + /// Registers all public static methods in a class tagged with as Lua global functions + /// + /// The Lua VM to add the methods to + /// The class type to get the methods from + public static void TaggedStaticMethods(Lua lua, Type type) + { + #region Sanity checks + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + if (type == null) + throw new ArgumentNullException(nameof(type)); + + if (!type.IsClass) + throw new ArgumentException("The type must be a class!", nameof(type)); + #endregion + + foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) + { + foreach (LuaGlobalAttribute attribute in method.GetCustomAttributes(typeof(LuaGlobalAttribute), false)) + { + if (string.IsNullOrEmpty(attribute.Name)) + lua.RegisterFunction(method.Name, null, method); // CLR name + else + lua.RegisterFunction(attribute.Name, null, method); // Custom name + } + } + } + #endregion + + /// + /// Registers an enumeration's values for usage as a Lua variable table + /// + /// The enum type to register + /// The Lua VM to add the enum to + [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "The type parameter is used to select an enum type")] + public static void Enumeration(Lua lua) + { + if (lua == null) + throw new ArgumentNullException(nameof(lua)); + + Type type = typeof(T); + + if (!type.IsEnum) + throw new ArgumentException("The type must be an enumeration!"); + + + string[] names = Enum.GetNames(type); + var values = (T[])Enum.GetValues(type); + lua.NewTable(type.Name); + + for (int i = 0; i < names.Length; i++) + { + string path = type.Name + "." + names[i]; + lua.SetObjectToPath(path, values[i]); + } + } + } +} diff --git a/src/LUA/LuaTable.cs b/src/LUA/LuaTable.cs new file mode 100644 index 0000000..8fff2e9 --- /dev/null +++ b/src/LUA/LuaTable.cs @@ -0,0 +1,120 @@ + +using System; +using System.Collections; + +using NLua.Extensions; + +using LuaState = KeraLua.Lua; + +namespace NLua +{ + public class LuaTable : LuaBase + { + public LuaTable(int reference, Lua interpreter): base(reference, interpreter) + { + } + + /* + * Indexer for string fields of the table + */ + public object this[string field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + lua.SetObject(_Reference, field, value); + } + } + + /* + * Indexer for numeric fields of the table + */ + public object this[object field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + public IDictionaryEnumerator GetEnumerator() + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetTableDict(this).GetEnumerator(); + } + + public ICollection Keys + { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetTableDict(this).Keys; + } + } + + + public ICollection Values + { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetTableDict(this).Values; + } + } + + + /* + * Gets an string fields of a table ignoring its metatable, + * if it exists + */ + internal object RawGet(string field) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.RawGetObject(_Reference, field); + } + + /* + * Pushes this table into the Lua stack + */ + internal void Push(LuaState luaState) + { + luaState.GetRef(_Reference); + } + + public override string ToString() + { + return "table"; + } + } +} \ No newline at end of file diff --git a/src/LUA/LuaUserData.cs b/src/LUA/LuaUserData.cs new file mode 100644 index 0000000..147d474 --- /dev/null +++ b/src/LUA/LuaUserData.cs @@ -0,0 +1,74 @@ + +using System; + +namespace NLua +{ + public class LuaUserData : LuaBase + { + public LuaUserData(int reference, Lua interpreter):base(reference, interpreter) + { + } + + /* + * Indexer for string fields of the userdata + */ + public object this[string field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + /* + * Indexer for numeric fields of the userdata + */ + public object this[object field] { + get + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.GetObject(_Reference, field); + } + set + { + Lua lua; + if (!TryGet(out lua)) + return; + + lua.SetObject(_Reference, field, value); + } + } + + /* + * Calls the userdata and returns its return values inside + * an array + */ + public object[] Call(params object[] args) + { + Lua lua; + if (!TryGet(out lua)) + return null; + + return lua.CallFunction(this, args); + } + + public override string ToString() + { + return "userdata"; + } + } +} \ No newline at end of file diff --git a/src/LUA/Metatables.cs b/src/LUA/Metatables.cs new file mode 100644 index 0000000..a4b9b19 --- /dev/null +++ b/src/LUA/Metatables.cs @@ -0,0 +1,1636 @@ +using System; +using System.Linq; +using System.Collections; +using System.Reflection; +using System.Diagnostics; +using System.Collections.Generic; +using System.Runtime.InteropServices; + +using KeraLua; + +using NLua.Method; +using NLua.Extensions; + +#if __IOS__ || __TVOS__ || __WATCHOS__ + using ObjCRuntime; +#endif + +using LuaState = KeraLua.Lua; +using LuaNativeFunction = KeraLua.LuaFunction; +using NLua.Exceptions; + +namespace NLua +{ + public class MetaFunctions + { + public static readonly LuaNativeFunction GcFunction = CollectObject; + public static readonly LuaNativeFunction IndexFunction = GetMethod; + public static readonly LuaNativeFunction NewIndexFunction = SetFieldOrProperty; + public static readonly LuaNativeFunction BaseIndexFunction = GetBaseMethod; + public static readonly LuaNativeFunction ClassIndexFunction = GetClassMethod; + public static readonly LuaNativeFunction ClassNewIndexFunction = SetClassFieldOrProperty; + public static readonly LuaNativeFunction ExecuteDelegateFunction = RunFunctionDelegate; + public static readonly LuaNativeFunction CallConstructorFunction = CallConstructor; + public static readonly LuaNativeFunction ToStringFunction = ToStringLua; + public static readonly LuaNativeFunction CallDelegateFunction = CallDelegate; + + public static readonly LuaNativeFunction AddFunction = AddLua; + public static readonly LuaNativeFunction SubtractFunction = SubtractLua; + public static readonly LuaNativeFunction MultiplyFunction = MultiplyLua; + public static readonly LuaNativeFunction DivisionFunction = DivideLua; + public static readonly LuaNativeFunction ModulosFunction = ModLua; + public static readonly LuaNativeFunction UnaryNegationFunction = UnaryNegationLua; + public static readonly LuaNativeFunction EqualFunction = EqualLua; + public static readonly LuaNativeFunction LessThanFunction = LessThanLua; + public static readonly LuaNativeFunction LessThanOrEqualFunction = LessThanOrEqualLua; + + readonly Dictionary> _memberCache = new Dictionary>(); + readonly ObjectTranslator _translator; + + /* + * __index metafunction for CLR objects. Implemented in Lua. + */ + public const string LuaIndexFunction = @"local function a(b,c)local d=getmetatable(b)local e=d.cache[c]if e~=nil then return e else local f,g=get_object_member(b,c)if g then d.cache[c]=f end;return f end end;return a"; + //@"local function index(obj,name) + // local meta = getmetatable(obj) + // local cached = meta.cache[name] + // if cached ~= nil then + // return cached + // else + // local value,isFunc = get_object_member(obj,name) + + // if isFunc then + // meta.cache[name]=value + // end + // return value + // end + //end + //return index"; + + public MetaFunctions(ObjectTranslator translator) + { + _translator = translator; + } + + /* + * __call metafunction of CLR delegates, retrieves and calls the delegate. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int RunFunctionDelegate(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + var func = (LuaNativeFunction)translator.GetRawNetObject(state, 1); + state.Remove(1); + int result = func(luaState); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __gc metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int CollectObject(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + return CollectObject(luaState, translator); + } + + private static int CollectObject(LuaState luaState, ObjectTranslator translator) + { + int udata = luaState.RawNetObj(1); + + if (udata != -1) + translator.CollectObject(udata); + + return 0; + } + + /* + * __tostring metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int ToStringLua(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + return ToStringLua(luaState, translator); + } + + private static int ToStringLua(LuaState luaState, ObjectTranslator translator) + { + object obj = translator.GetRawNetObject(luaState, 1); + + if (obj != null) + translator.Push(luaState, obj + ": " + obj.GetHashCode()); + else + luaState.PushNil(); + + return 1; + } + + + /* + * __add metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int AddLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Addition", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __sub metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int SubtractLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Subtraction", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __mul metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int MultiplyLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Multiply", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __div metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int DivideLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Division", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __mod metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int ModLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Modulus", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __unm metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int UnaryNegationLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = UnaryNegationLua(state, translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + static int UnaryNegationLua(LuaState luaState, ObjectTranslator translator) + { + object obj1 = translator.GetRawNetObject(luaState, 1); + + if (obj1 == null) + { + translator.ThrowError(luaState, "Cannot negate a nil object"); + return 1; + } + + Type type = obj1.GetType(); + MethodInfo opUnaryNegation = type.GetMethod("op_UnaryNegation"); + + if (opUnaryNegation == null) + { + translator.ThrowError(luaState, "Cannot negate object (" + type.Name + " does not overload the operator -)"); + return 1; + } + obj1 = opUnaryNegation.Invoke(obj1, new [] { obj1 }); + translator.Push(luaState, obj1); + return 1; + } + + + /* + * __eq metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int EqualLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_Equality", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __lt metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int LessThanLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_LessThan", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /* + * __le metafunction of CLR objects. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int LessThanOrEqualLua(IntPtr luaState) + { + var state = LuaState.FromIntPtr(luaState); + var translator = ObjectTranslatorPool.Instance.Find(state); + int result = MatchOperator(state, "op_LessThanOrEqual", translator); + var exception = translator.GetObject(state, -1) as LuaScriptException; + + if (exception != null) + return state.Error(); + return result; + } + + /// + /// Debug tool to dump the lua stack + /// + /// FIXME, move somewhere else + public static void DumpStack(ObjectTranslator translator, LuaState luaState) + { + int depth = luaState.GetTop(); + + Debug.WriteLine("lua stack depth: {0}", depth); + + for (int i = 1; i <= depth; i++) + { + var type = luaState.Type(i); + // we dump stacks when deep in calls, calling typename while the stack is in flux can fail sometimes, so manually check for key types + string typestr = (type == LuaType.Table) ? "table" : luaState.TypeName(type); + string strrep = luaState.ToString(i, false); + + if (type == LuaType.UserData) + { + object obj = translator.GetRawNetObject(luaState, i); + strrep = obj.ToString(); + } + + Debug.WriteLine("{0}: ({1}) {2}", i, typestr, strrep); + } + } + + /* + * Called by the __index metafunction of CLR objects in case the + * method is not cached or it is a field/property/event. + * Receives the object and the member name as arguments and returns + * either the value of the member or a delegate to call it. + * If the member does not exist returns nil. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int GetMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetMethodInternal(LuaState luaState) + { + object obj = _translator.GetRawNetObject(luaState, 1); + + if (obj == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid object reference"); + return 1; + } + + object index = _translator.GetObject(luaState, 2); + string methodName = index as string; // will be null if not a string arg + var objType = obj.GetType(); + var proxyType = new ProxyType(objType); + + // Handle the most common case, looking up the method by name. + // CP: This will fail when using indexers and attempting to get a value with the same name as a property of the object, + // ie: xmlelement['item'] <- item is a property of xmlelement + + if (!string.IsNullOrEmpty(methodName) && IsMemberPresent(proxyType, methodName)) + return GetMember(luaState, proxyType, obj, methodName, BindingFlags.Instance); + + // Try to access by array if the type is right and index is an int (lua numbers always come across as double) + if (TryAccessByArray(luaState, objType, obj, index)) + return 1; + + int fallback = GetMethodFallback(luaState, objType, obj, index, methodName); + if (fallback != 0) + return fallback; + + if (!string.IsNullOrEmpty(methodName) || index != null) + { + if (string.IsNullOrEmpty(methodName)) + methodName = index.ToString(); + + return PushInvalidMethodCall(luaState, objType, methodName); + } + + luaState.PushBoolean(false); + return 2; + } + + private int PushInvalidMethodCall(LuaState luaState, Type type, string name) + { + SetMemberCache(type, name, null); + + _translator.Push(luaState, null); + _translator.Push(luaState, false); + return 2; + } + + private bool TryAccessByArray(LuaState luaState, + Type objType, + object obj, + object index) + { + if (!objType.IsArray) + return false; + + int intIndex = -1; + if (index is long l) + intIndex = (int)l; + else if (index is double d) + intIndex = (int)d; + + if (intIndex == -1) + return false; + + Type type = objType.UnderlyingSystemType; + + if (type == typeof(long[])) + { + long[] arr = (long[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(float[])) + { + float[] arr = (float[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(double[])) + { + double[] arr = (double[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(int[])) + { + int[] arr = (int[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(byte[])) + { + byte[] arr = (byte[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(short[])) + { + short[] arr = (short[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(ushort[])) + { + ushort[] arr = (ushort[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(ulong[])) + { + ulong[] arr = (ulong[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(uint[])) + { + uint[] arr = (uint[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + if (type == typeof(sbyte[])) + { + sbyte[] arr = (sbyte[])obj; + _translator.Push(luaState, arr[intIndex]); + return true; + } + + var array = (Array)obj; + object element = array.GetValue(intIndex); + _translator.Push(luaState, element); + return true; + } + + private int GetMethodFallback + (LuaState luaState, + Type objType, + object obj, + object index, + string methodName) + { + object method; + if (!string.IsNullOrEmpty(methodName) && TryGetExtensionMethod(objType, methodName, out method)) + { + return PushExtensionMethod(luaState, objType, obj, methodName, method); + } + // Try to use get_Item to index into this .net object + MethodInfo[] methods = objType.GetMethods(); + + int res = TryIndexMethods(luaState, methods, obj, index); + if (res != 0) + return res; + + // Fallback to GetRuntimeMethods + methods = objType.GetRuntimeMethods().ToArray(); + + res = TryIndexMethods(luaState, methods, obj, index); + if (res != 0) + return res; + + res = TryGetValueForKeyMethods(luaState, methods, obj, index); + if (res != 0) + return res; + + // Try find explicity interface implementation + MethodInfo explicitInterfaceMethod = objType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance). + FirstOrDefault(m => m.Name == methodName && m.IsPrivate && m.IsVirtual && m.IsFinal); + + if (explicitInterfaceMethod != null) + { + var proxyType = new ProxyType(objType); + var methodWrapper = new LuaMethodWrapper(_translator, obj, proxyType, explicitInterfaceMethod); + var invokeDelegate = new LuaNativeFunction(methodWrapper.InvokeFunction); + + SetMemberCache(proxyType, methodName, invokeDelegate); + + _translator.PushFunction(luaState, invokeDelegate); + _translator.Push(luaState, true); + return 2; + } + + return 0; + } + + private int TryGetValueForKeyMethods(LuaState luaState, MethodInfo[] methods, object obj, object index) + { + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name != "TryGetValueForKey") + continue; + + // Check if the signature matches the input + if (methodInfo.GetParameters().Length != 2) + continue; + + ParameterInfo[] actualParams = methodInfo.GetParameters(); + + // Get the index in a form acceptable to the getter + index = _translator.GetAsType(luaState, 2, actualParams[0].ParameterType); + + // If the index type and the parameter doesn't match, just skip it + if (index == null) + break; + + object[] args = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + args[0] = index; + + try + { + bool found = (bool)methodInfo.Invoke(obj, args); + + if (!found) + { + _translator.ThrowError(luaState, "key not found: " + index); + return 1; + } + + _translator.Push(luaState, args[1]); + return 1; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + _translator.ThrowError(luaState, "key '" + index + "' not found "); + else + _translator.ThrowError(luaState, "exception indexing '" + index + "' " + e.Message); + + return 1; + } + } + return 0; + } + + + private int TryIndexMethods(LuaState luaState, MethodInfo [] methods, object obj, object index) + { + foreach (MethodInfo methodInfo in methods) + { + if (methodInfo.Name != "get_Item") + continue; + + // Check if the signature matches the input + if (methodInfo.GetParameters().Length != 1) + continue; + + ParameterInfo[] actualParams = methodInfo.GetParameters(); + + // Get the index in a form acceptable to the getter + index = _translator.GetAsType(luaState, 2, actualParams[0].ParameterType); + + // If the index type and the parameter doesn't match, just skip it + if (index == null) + continue; + + object[] args = new object[1]; + + // Just call the indexer - if out of bounds an exception will happen + args[0] = index; + + try + { + object result = methodInfo.Invoke(obj, args); + _translator.Push(luaState, result); + return 1; + } + catch (TargetInvocationException e) + { + // Provide a more readable description for the common case of key not found + if (e.InnerException is KeyNotFoundException) + _translator.ThrowError(luaState, "key '" + index + "' not found "); + else + _translator.ThrowError(luaState, "exception indexing '" + index + "' " + e.Message); + + return 1; + } + } + return 0; + } + + /* + * __index metafunction of base classes (the base field of Lua tables). + * Adds a prefix to the method name to call the base version of the method. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int GetBaseMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetBaseMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetBaseMethodInternal(LuaState luaState) + { + object obj = _translator.GetRawNetObject(luaState, 1); + + if (obj == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid object reference"); + return 1; + } + + string methodName = luaState.ToString(2, false); + + if (string.IsNullOrEmpty(methodName)) + { + luaState.PushNil(); + luaState.PushBoolean(false); + return 2; + } + + GetMember(luaState, new ProxyType(obj.GetType()), obj, "__luaInterface_base_" + methodName, BindingFlags.Instance); + luaState.SetTop(-2); + + if (luaState.Type(-1) == LuaType.Nil) + { + luaState.SetTop(-2); + return GetMember(luaState, new ProxyType(obj.GetType()), obj, methodName, BindingFlags.Instance); + } + + luaState.PushBoolean(false); + return 2; + } + + /// + /// Does this method exist as either an instance or static? + /// + /// + /// + /// + bool IsMemberPresent(ProxyType objType, string methodName) + { + object cachedMember = CheckMemberCache(objType, methodName); + + if (cachedMember != null) + return true; + + var members = objType.GetMember(methodName, BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public); + return members.Length > 0; + } + + bool TryGetExtensionMethod(Type type, string name, out object method) + { + object cachedMember = CheckMemberCache(type, name); + + if (cachedMember != null) + { + method = cachedMember; + return true; + } + + MethodInfo methodInfo; + bool found = _translator.TryGetExtensionMethod(type, name, out methodInfo); + method = methodInfo; + return found; + } + + int PushExtensionMethod(LuaState luaState, Type type, object obj, string name, object method) + { + var cachedMember = method as LuaNativeFunction; + + if (cachedMember != null) + { + _translator.PushFunction(luaState, cachedMember); + _translator.Push(luaState, true); + return 2; + } + + var methodInfo = (MethodInfo)method; + var methodWrapper = new LuaMethodWrapper(_translator, obj, new ProxyType(type), methodInfo); + var invokeDelegate = new LuaNativeFunction(methodWrapper.InvokeFunction); + + SetMemberCache(type, name, invokeDelegate); + + _translator.PushFunction(luaState, invokeDelegate); + _translator.Push(luaState, true); + return 2; + } + + /* + * Pushes the value of a member or a delegate to call it, depending on the type of + * the member. Works with static or instance members. + * Uses reflection to find members, and stores the reflected MemberInfo object in + * a cache (indexed by the type of the object and the name of the member). + */ + int GetMember(LuaState luaState, ProxyType objType, object obj, string methodName, BindingFlags bindingType) + { + bool implicitStatic = false; + MemberInfo member = null; + object cachedMember = CheckMemberCache(objType, methodName); + + if (cachedMember is LuaNativeFunction) + { + _translator.PushFunction(luaState, (LuaNativeFunction)cachedMember); + _translator.Push(luaState, true); + return 2; + } + if (cachedMember != null) + member = (MemberInfo)cachedMember; + else + { + var members = objType.GetMember(methodName, bindingType | BindingFlags.Public); + + if (members.Length > 0) + member = members[0]; + else + { + // If we can't find any suitable instance members, try to find them as statics - but we only want to allow implicit static + members = objType.GetMember(methodName, bindingType | BindingFlags.Static | BindingFlags.Public); + + if (members.Length > 0) + { + member = members[0]; + implicitStatic = true; + } + } + } + + if (member != null) + { + if (member.MemberType == MemberTypes.Field) + { + var field = (FieldInfo)member; + + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + try + { + var value = field.GetValue(obj); + _translator.Push(luaState, value); + } + catch + { + Debug.WriteLine("[Exception] Fail to get field value"); + luaState.PushNil(); + } + } + else if (member.MemberType == MemberTypes.Property) + { + var property = (PropertyInfo)member; + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + try + { + object value = property.GetValue(obj, null); + _translator.Push(luaState, value); + } + catch (ArgumentException) + { + // If we can't find the getter in our class, recurse up to the base class and see + // if they can help. + if (objType.UnderlyingSystemType != typeof(object)) + return GetMember(luaState, new ProxyType(objType.UnderlyingSystemType.BaseType), obj, methodName, bindingType); + luaState.PushNil(); + } + catch (TargetInvocationException e) + { // Convert this exception into a Lua error + ThrowError(luaState, e); + luaState.PushNil(); + } + } + else if (member.MemberType == MemberTypes.Event) + { + var eventInfo = (EventInfo)member; + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + _translator.Push(luaState, new RegisterEventHandler(_translator.PendingEvents, obj, eventInfo)); + } + else if (!implicitStatic) + { + if (member.MemberType == MemberTypes.NestedType && member.DeclaringType != null) + { + if (cachedMember == null) + SetMemberCache(objType, methodName, member); + + // Find the name of our class + string name = member.Name; + Type decType = member.DeclaringType; + + // Build a new long name and try to find the type by name + string longName = decType.FullName + "+" + name; + var nestedType = _translator.FindType(longName); + _translator.PushType(luaState, nestedType); + } + else + { + // Member type must be 'method' + var methodWrapper = new LuaMethodWrapper(_translator, objType, methodName, bindingType); + var wrapper = methodWrapper.InvokeFunction; + + if (cachedMember == null) + SetMemberCache(objType, methodName, wrapper); + + _translator.PushFunction(luaState, wrapper); + _translator.Push(luaState, true); + return 2; + } + } + else + { + // If we reach this point we found a static method, but can't use it in this context because the user passed in an instance + _translator.ThrowError(luaState, "Can't pass instance to static method " + methodName); + return 1; + } + } + else + { + if (objType.UnderlyingSystemType != typeof(object)) + return GetMember(luaState, new ProxyType(objType.UnderlyingSystemType.BaseType), obj, methodName, bindingType); + + // We want to throw an exception because merely returning 'nil' in this case + // is not sufficient. valid data members may return nil and therefore there must be some + // way to know the member just doesn't exist. + _translator.ThrowError(luaState, "Unknown member name " + methodName); + return 1; + } + + // Push false because we are NOT returning a function (see luaIndexFunction) + _translator.Push(luaState, false); + return 2; + } + + /* + * Checks if a MemberInfo object is cached, returning it or null. + */ + object CheckMemberCache(Type objType, string memberName) + { + return CheckMemberCache(new ProxyType(objType), memberName); + } + + object CheckMemberCache(ProxyType objType, string memberName) + { + Dictionary members; + + if (!_memberCache.TryGetValue(objType, out members)) + return null; + + object memberValue; + + if (members == null || !members.TryGetValue(memberName, out memberValue)) + return null; + + return memberValue; + } + + /* + * Stores a MemberInfo object in the member cache. + */ + void SetMemberCache(Type objType, string memberName, object member) + { + SetMemberCache(new ProxyType(objType), memberName, member); + } + + void SetMemberCache(ProxyType objType, string memberName, object member) + { + Dictionary members; + Dictionary memberCacheValue; + + if (_memberCache.TryGetValue(objType, out memberCacheValue)) + { + members = memberCacheValue; + } + else + { + members = new Dictionary(); + _memberCache[objType] = members; + } + + members[memberName] = member; + } + + /* + * __newindex metafunction of CLR objects. Receives the object, + * the member name and the value to be stored as arguments. Throws + * and error if the assignment is invalid. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int SetFieldOrProperty(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.SetFieldOrPropertyInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int SetFieldOrPropertyInternal(LuaState luaState) + { + object target = _translator.GetRawNetObject(luaState, 1); + + if (target == null) + { + _translator.ThrowError(luaState, "trying to index and invalid object reference"); + return 1; + } + + var type = target.GetType(); + + // First try to look up the parameter as a property name + string detailMessage; + bool didMember = TrySetMember(luaState, new ProxyType(type), target, BindingFlags.Instance, out detailMessage); + + if (didMember) + return 0; // Must have found the property name + + // We didn't find a property name, now see if we can use a [] style this accessor to set array contents + try + { + if (type.IsArray && luaState.IsNumber(2)) + { + int index = (int)luaState.ToNumber(2); + var arr = (Array)target; + object val = _translator.GetAsType(luaState, 3, arr.GetType().GetElementType()); + arr.SetValue(val, index); + } + else + { + // Try to see if we have a this[] accessor + var setter = type.GetMethod("set_Item"); + if (setter != null) + { + var args = setter.GetParameters(); + var valueType = args[1].ParameterType; + + // The new value the user specified + object val = _translator.GetAsType(luaState, 3, valueType); + var indexType = args[0].ParameterType; + object index = _translator.GetAsType(luaState, 2, indexType); + + object[] methodArgs = new object[2]; + + // Just call the indexer - if out of bounds an exception will happen + methodArgs[0] = index; + methodArgs[1] = val; + setter.Invoke(target, methodArgs); + } + else + { + _translator.ThrowError(luaState, detailMessage); // Pass the original message from trySetMember because it is probably best + return 1; + } + } + } + catch (Exception e) + { + ThrowError(luaState, e); + return 1; + } + + return 0; + } + + /// + /// Tries to set a named property or field + /// + /// + /// + /// + /// + /// false if unable to find the named member, true for success + bool TrySetMember(LuaState luaState, ProxyType targetType, object target, BindingFlags bindingType, out string detailMessage) + { + detailMessage = null; // No error yet + + // If not already a string just return - we don't want to call tostring - which has the side effect of + // changing the lua typecode to string + // Note: We don't use isstring because the standard lua C isstring considers either strings or numbers to + // be true for isstring. + if (luaState.Type(2) != LuaType.String) + { + detailMessage = "property names must be strings"; + return false; + } + + // We only look up property names by string + string fieldName = luaState.ToString(2, false); + if (string.IsNullOrEmpty(fieldName) || !(char.IsLetter(fieldName[0]) || fieldName[0] == '_')) + { + detailMessage = "Invalid property name"; + return false; + } + + // Find our member via reflection or the cache + var member = (MemberInfo)CheckMemberCache(targetType, fieldName); + if (member == null) + { + var members = targetType.GetMember(fieldName, bindingType | BindingFlags.Public); + + if (members.Length <= 0) + { + detailMessage = "field or property '" + fieldName + "' does not exist"; + return false; + } + + member = members[0]; + SetMemberCache(targetType, fieldName, member); + } + + if (member.MemberType == MemberTypes.Field) + { + var field = (FieldInfo)member; + object val = _translator.GetAsType(luaState, 3, field.FieldType); + + try + { + field.SetValue(target, val); + } + catch (Exception e) + { + detailMessage = "Error setting field: " + e.Message; + return false; + } + + return true; + } + if (member.MemberType == MemberTypes.Property) + { + var property = (PropertyInfo)member; + object val = _translator.GetAsType(luaState, 3, property.PropertyType); + + try + { + property.SetValue(target, val, null); + } + catch (Exception e) + { + detailMessage = "Error setting property: " + e.Message; + return false; + } + + return true; + } + + detailMessage = "'" + fieldName + "' is not a .net field or property"; + return false; + } + + /* + * Writes to fields or properties, either static or instance. Throws an error + * if the operation is invalid. + */ + private int SetMember(LuaState luaState, ProxyType targetType, object target, BindingFlags bindingType) + { + string detail; + bool success = TrySetMember(luaState, targetType, target, bindingType, out detail); + + if (!success) + { + _translator.ThrowError(luaState, detail); + return 1; + } + + return 0; + } + + /// + /// Convert a C# exception into a Lua error + /// + /// + /// + /// We try to look into the exception to give the most meaningful description + void ThrowError(LuaState luaState, Exception e) + { + // If we got inside a reflection show what really happened + var te = e as TargetInvocationException; + + if (te != null) + e = te.InnerException; + + _translator.ThrowError(luaState, e); + } + + /* + * __index metafunction of type references, works on static members. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int GetClassMethod(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.GetClassMethodInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int GetClassMethodInternal(LuaState luaState) + { + var klass = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (klass == null) + { + _translator.ThrowError(luaState, "Trying to index an invalid type reference"); + return 1; + } + + if (luaState.IsNumber(2)) + { + int size = (int)luaState.ToNumber(2); + _translator.Push(luaState, Array.CreateInstance(klass.UnderlyingSystemType, size)); + return 1; + } + + string methodName = luaState.ToString(2, false); + + if (string.IsNullOrEmpty(methodName)) + { + luaState.PushNil(); + return 1; + } + return GetMember(luaState, klass, null, methodName, BindingFlags.Static); + } + + /* + * __newindex function of type references, works on static members. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int SetClassFieldOrProperty(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.SetClassFieldOrPropertyInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private int SetClassFieldOrPropertyInternal(LuaState luaState) + { + var target = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (target == null) + { + _translator.ThrowError(luaState, "trying to index an invalid type reference"); + return 1; + } + + return SetMember(luaState, target, null, BindingFlags.Static); + } + + /* + * __call metafunction of Delegates. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + static int CallDelegate(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.CallDelegateInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + + return result; + } + + int CallDelegateInternal(LuaState luaState) + { + var del = _translator.GetRawNetObject(luaState, 1) as Delegate; + + if (del == null) + { + _translator.ThrowError(luaState, "Trying to invoke a not delegate or callable value"); + return 1; + } + + luaState.Remove(1); + + var validDelegate = new MethodCache(); + MethodBase methodDelegate = del.Method; + bool isOk = MatchParameters(luaState, methodDelegate, validDelegate, 0); + + if (isOk) + { + object result; + + if (methodDelegate.IsStatic) + result = methodDelegate.Invoke(null, validDelegate.args); + else + result = methodDelegate.Invoke(del.Target, validDelegate.args); + + _translator.Push(luaState, result); + return 1; + } + + _translator.ThrowError(luaState, "Cannot invoke delegate (invalid arguments for " + methodDelegate.Name + ")"); + return 1; + } + + /* + * __call metafunction of type references. Searches for and calls + * a constructor for the type. Returns nil if the constructor is not + * found or if the arguments are invalid. Throws an error if the constructor + * generates an exception. + */ +#if __IOS__ || __TVOS__ || __WATCHOS__ + [MonoPInvokeCallback(typeof(LuaNativeFunction))] +#endif + private static int CallConstructor(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + var translator = ObjectTranslatorPool.Instance.Find(luaState); + var instance = translator.MetaFunctionsInstance; + int result = instance.CallConstructorInternal(luaState); + var exception = translator.GetObject(luaState, -1) as LuaScriptException; + + if (exception != null) + return luaState.Error(); + return result; + } + + private static ConstructorInfo[] ReorderConstructors(ConstructorInfo[] constructors) + { + int len = constructors.Length; + + if (len < 2) + return constructors; + + return constructors. + GroupBy(c => c.GetParameters().Length). + SelectMany(g => g.OrderByDescending(ci => ci.ToString())). + ToArray(); + } + + private int CallConstructorInternal(LuaState luaState) + { + var klass = _translator.GetRawNetObject(luaState, 1) as ProxyType; + + if (klass == null) + { + _translator.ThrowError(luaState, "Trying to call constructor on an invalid type reference"); + return 1; + } + + var validConstructor = new MethodCache(); + + luaState.Remove(1); + ConstructorInfo[] constructors = klass.UnderlyingSystemType.GetConstructors(); + constructors = ReorderConstructors(constructors); + foreach (var constructor in constructors) + { + bool isConstructor = MatchParameters(luaState, constructor, validConstructor, 0); + + if (!isConstructor) + continue; + + try + { + _translator.Push(luaState, constructor.Invoke(validConstructor.args)); + } + catch (TargetInvocationException e) + { + ThrowError(luaState, e); + return 1; + } + catch + { + luaState.PushNil(); + } + return 1; + } + + if (klass.UnderlyingSystemType.IsValueType) + { + int numLuaParams = luaState.GetTop(); + if (numLuaParams == 0) + { + _translator.Push(luaState, Activator.CreateInstance(klass.UnderlyingSystemType)); + return 1; + } + } + + string constructorName = constructors.Length == 0 ? "unknown" : constructors[0].Name; + _translator.ThrowError(luaState, string.Format("{0} does not contain constructor({1}) argument match", + klass.UnderlyingSystemType, constructorName)); + return 1; + } + + static bool IsInteger(double x) + { + return Math.Ceiling(x) == x; + } + + static object GetTargetObject(LuaState luaState, string operation, ObjectTranslator translator) + { + Type t; + object target = translator.GetRawNetObject(luaState, 1); + if (target != null) + { + t = target.GetType(); + if (t.HasMethod(operation)) + return target; + } + target = translator.GetRawNetObject(luaState, 2); + if (target != null) + { + t = target.GetType(); + if (t.HasMethod(operation)) + return target; + } + return null; + } + + static int MatchOperator(LuaState luaState, string operation, ObjectTranslator translator) + { + var validOperator = new MethodCache(); + + object target = GetTargetObject(luaState, operation, translator); + + if (target == null) + { + translator.ThrowError(luaState, "Cannot call " + operation + " on a nil object"); + return 1; + } + + Type type = target.GetType(); + var operators = type.GetMethods(operation, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + + foreach (var op in operators) + { + bool isOk = translator.MatchParameters(luaState, op, validOperator, 0); + + if (!isOk) + continue; + + object result; + if (op.IsStatic) + result = op.Invoke(null, validOperator.args); + else + result = op.Invoke(target, validOperator.args); + translator.Push(luaState, result); + return 1; + } + + translator.ThrowError(luaState, "Cannot call (" + operation + ") on object type " + type.Name); + return 1; + } + + internal Array TableToArray(LuaState luaState, ExtractValue extractValue, Type paramArrayType, ref int startIndex, int count) + { + Array paramArray; + + if (count == 0) + return Array.CreateInstance(paramArrayType, 0); + + var luaParamValue = extractValue(luaState, startIndex); + startIndex++; + + if (luaParamValue is LuaTable) + { + LuaTable table = (LuaTable)luaParamValue; + IDictionaryEnumerator tableEnumerator = table.GetEnumerator(); + tableEnumerator.Reset(); + paramArray = Array.CreateInstance(paramArrayType, table.Values.Count); + + int paramArrayIndex = 0; + + while (tableEnumerator.MoveNext()) + { + object value = tableEnumerator.Value; + + if (paramArrayType == typeof(object)) + { + if (value != null && value is double && IsInteger((double)value)) + value = Convert.ToInt32((double)value); + } + + paramArray.SetValue(Convert.ChangeType(value, paramArrayType), paramArrayIndex); + paramArrayIndex++; + } + } + else + { + paramArray = Array.CreateInstance(paramArrayType, count); + + paramArray.SetValue(luaParamValue, 0); + + for (int i = 1; i < count; i++) + { + var value = extractValue(luaState, startIndex); + paramArray.SetValue(value, i); + startIndex++; + } + + } + + return paramArray; + + } + + /* + * Matches a method against its arguments in the Lua stack. Returns + * if the match was successful. It it was also returns the information + * necessary to invoke the method. + */ + internal bool MatchParameters(LuaState luaState, MethodBase method, MethodCache methodCache, int skipParam) + { + var paramInfo = method.GetParameters(); + int currentLuaParam = 1; + int nLuaParams = luaState.GetTop() - skipParam; + var paramList = new List(); + var outList = new List(); + var argTypes = new List(); + + foreach (var currentNetParam in paramInfo) + { + if (!currentNetParam.IsIn && currentNetParam.IsOut) // Skips out params + { + paramList.Add(null); + outList.Add(paramList.Count - 1); + continue; + } // Type does not match, ignore if the parameter is optional + + ExtractValue extractValue; + if (IsParamsArray(luaState, nLuaParams, currentLuaParam, currentNetParam, out extractValue)) + { + int count = (nLuaParams - currentLuaParam) + 1; + Type paramArrayType = currentNetParam.ParameterType.GetElementType(); + + Array paramArray = TableToArray(luaState, extractValue, paramArrayType, ref currentLuaParam, count); + paramList.Add(paramArray); + int index = paramList.LastIndexOf(paramArray); + var methodArg = new MethodArgs(); + methodArg.Index = index; + methodArg.ExtractValue = extractValue; + methodArg.IsParamsArray = true; + methodArg.ParameterType = paramArrayType; + argTypes.Add(methodArg); + continue; + } + + if (currentLuaParam > nLuaParams) + { // Adds optional parameters + if (!currentNetParam.IsOptional) + return false; + paramList.Add(currentNetParam.DefaultValue); + continue; + } + + if (IsTypeCorrect(luaState, currentLuaParam, currentNetParam, out extractValue)) + { // Type checking + var value = extractValue(luaState, currentLuaParam); + paramList.Add(value); + int index = paramList.Count - 1; + var methodArg = new MethodArgs(); + methodArg.Index = index; + methodArg.ExtractValue = extractValue; + methodArg.ParameterType = currentNetParam.ParameterType; + argTypes.Add(methodArg); + + if (currentNetParam.ParameterType.IsByRef) + outList.Add(index); + + currentLuaParam++; + continue; + } + + if (currentNetParam.IsOptional) + { + paramList.Add(currentNetParam.DefaultValue); + continue; + } + + return false; + } + + if (currentLuaParam != nLuaParams + 1) // Number of parameters does not match + return false; + + methodCache.args = paramList.ToArray(); + methodCache.cachedMethod = method; + methodCache.outList = outList.ToArray(); + methodCache.argTypes = argTypes.ToArray(); + + return true; + } + + /// + /// Returns true if the type is set and assigns the extract value + /// + /// + /// + /// + /// + /// + private bool IsTypeCorrect(LuaState luaState, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = _translator.typeChecker.CheckLuaType(luaState, currentLuaParam, currentNetParam.ParameterType); + return extractValue != null; + } + + private bool IsParamsArray(LuaState luaState, int nLuaParams, int currentLuaParam, ParameterInfo currentNetParam, out ExtractValue extractValue) + { + extractValue = null; + + if (!currentNetParam.GetCustomAttributes(typeof(ParamArrayAttribute), false).Any()) + return false; + + bool isParamArray = nLuaParams < currentLuaParam; + + LuaType luaType = luaState.Type(currentLuaParam); + + if (luaType == LuaType.Table) + { + extractValue = _translator.typeChecker.GetExtractor(typeof(LuaTable)); + if (extractValue != null) + return true; + } + else + { + Type paramElementType = currentNetParam.ParameterType.GetElementType(); + + extractValue = _translator.typeChecker.CheckLuaType(luaState, currentLuaParam, paramElementType); + + if (extractValue != null) + return true; + } + return isParamArray; + } + } +} diff --git a/src/LUA/Method/EventHandlerContainer.cs b/src/LUA/Method/EventHandlerContainer.cs new file mode 100644 index 0000000..0d1a9f1 --- /dev/null +++ b/src/LUA/Method/EventHandlerContainer.cs @@ -0,0 +1,36 @@ +using System; +using System.Diagnostics; +using System.Collections.Generic; + +namespace NLua.Method +{ + /// + /// We keep track of what delegates we have auto attached to an event - to allow us to cleanly exit a NLua session + /// + class EventHandlerContainer : IDisposable + { + private readonly Dictionary _dict = new Dictionary(); + + public void Add(Delegate handler, RegisterEventHandler eventInfo) + { + _dict.Add(handler, eventInfo); + } + + public void Remove(Delegate handler) + { + bool found = _dict.Remove(handler); + Debug.Assert(found); + } + + /// + /// Remove any still registered handlers + /// + public void Dispose() + { + foreach (KeyValuePair pair in _dict) + pair.Value.RemovePending(pair.Key); + + _dict.Clear(); + } + } +} \ No newline at end of file diff --git a/src/LUA/Method/LuaClassHelper.cs b/src/LUA/Method/LuaClassHelper.cs new file mode 100644 index 0000000..381eb8c --- /dev/null +++ b/src/LUA/Method/LuaClassHelper.cs @@ -0,0 +1,55 @@ +using System; + +namespace NLua.Method +{ + public class LuaClassHelper + { + /* + * Gets the function called name from the provided table, + * returning null if it does not exist + */ + public static LuaFunction GetTableFunction(LuaTable luaTable, string name) + { + if (luaTable == null) + return null; + + var funcObj = luaTable.RawGet(name) as LuaFunction; + + if (funcObj != null) + return funcObj; + return null; + } + + /* + * Calls the provided function with the provided parameters + */ + public static object CallFunction(LuaFunction function, object[] args, Type[] returnTypes, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = function.Call(inArgs, returnTypes); + + if (returnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + + return returnValue; + } + } +} \ No newline at end of file diff --git a/src/LUA/Method/LuaDelegate.cs b/src/LUA/Method/LuaDelegate.cs new file mode 100644 index 0000000..0d988ff --- /dev/null +++ b/src/LUA/Method/LuaDelegate.cs @@ -0,0 +1,47 @@ +using System; + +namespace NLua.Method +{ + public class LuaDelegate + { + public LuaFunction Function; + public Type[] ReturnTypes; + + public LuaDelegate() + { + Function = null; + ReturnTypes = null; + } + + public object CallFunction(object[] args, object[] inArgs, int[] outArgs) + { + // args is the return array of arguments, inArgs is the actual array + // of arguments passed to the function (with in parameters only), outArgs + // has the positions of out parameters + object returnValue; + int iRefArgs; + object[] returnValues = Function.Call(inArgs, ReturnTypes); + + if (ReturnTypes[0] == typeof(void)) + { + returnValue = null; + iRefArgs = 0; + } + else + { + returnValue = returnValues[0]; + iRefArgs = 1; + } + + // Sets the value of out and ref parameters (from + // the values returned by the Lua function). + for (int i = 0; i < outArgs.Length; i++) + { + args[outArgs[i]] = returnValues[iRefArgs]; + iRefArgs++; + } + + return returnValue; + } + } +} \ No newline at end of file diff --git a/src/LUA/Method/LuaEventHandler.cs b/src/LUA/Method/LuaEventHandler.cs new file mode 100644 index 0000000..49538dc --- /dev/null +++ b/src/LUA/Method/LuaEventHandler.cs @@ -0,0 +1,12 @@ +namespace NLua.Method +{ + public class LuaEventHandler + { + public LuaFunction Handler = null; + + public void HandleEvent(object[] args) + { + Handler.Call(args); + } + } +} \ No newline at end of file diff --git a/src/LUA/Method/LuaMethodWrapper.cs b/src/LUA/Method/LuaMethodWrapper.cs new file mode 100644 index 0000000..22b5686 --- /dev/null +++ b/src/LUA/Method/LuaMethodWrapper.cs @@ -0,0 +1,355 @@ +using System; +using System.Reflection; +using System.Collections.Generic; +using System.Linq; + +using NLua.Exceptions; +using NLua.Extensions; + +using LuaState = KeraLua.Lua; +using LuaNativeFunction = KeraLua.LuaFunction; + +namespace NLua.Method +{ + /* + * Argument extraction with type-conversion function + */ + delegate object ExtractValue(LuaState luaState, int stackPos); + + /* + * Wrapper class for methods/constructors accessed from Lua. + * + */ + class LuaMethodWrapper + { + internal LuaNativeFunction InvokeFunction; + + readonly ObjectTranslator _translator; + readonly MethodBase _method; + + readonly ExtractValue _extractTarget; + readonly object _target; + readonly bool _isStatic; + + readonly string _methodName; + readonly MethodInfo[] _members; + + private MethodCache _lastCalledMethod; + + + /* + * Constructs the wrapper for a known MethodBase instance + */ + public LuaMethodWrapper(ObjectTranslator translator, object target, ProxyType targetType, MethodBase method) + { + InvokeFunction = Call; + _translator = translator; + _target = target; + _extractTarget = translator.typeChecker.GetExtractor(targetType); + _lastCalledMethod = new MethodCache(); + + _method = method; + _methodName = method.Name; + _isStatic = method.IsStatic; + + } + + /* + * Constructs the wrapper for a known method name + */ + public LuaMethodWrapper(ObjectTranslator translator, ProxyType targetType, string methodName, BindingFlags bindingType) + { + InvokeFunction = Call; + + _translator = translator; + _methodName = methodName; + _extractTarget = translator.typeChecker.GetExtractor(targetType); + _lastCalledMethod = new MethodCache(); + + _isStatic = (bindingType & BindingFlags.Static) == BindingFlags.Static; + MethodInfo [] methods = GetMethodsRecursively(targetType.UnderlyingSystemType, + methodName, + bindingType | BindingFlags.Public); + _members = ReorderMethods(methods); + } + + private static MethodInfo[] ReorderMethods(MethodInfo[] m) + { + int len = m.Length; + + if (len < 2) + return m; + + return m. + GroupBy(c => c.GetParameters().Length). + SelectMany(g => g.OrderByDescending(ci => ci.ToString())). + ToArray(); + } + + MethodInfo[] GetMethodsRecursively(Type type, string methodName, BindingFlags bindingType) + { + if (type == typeof(object)) + return type.GetMethods(methodName, bindingType); + + var methods = type.GetMethods(methodName, bindingType); + var baseMethods = GetMethodsRecursively(type.BaseType, methodName, bindingType); + + return methods.Concat(baseMethods).ToArray(); + } + + /// + /// Convert C# exceptions into Lua errors + /// + /// num of things on stack + /// null for no pending exception + int SetPendingException(Exception e) + { + return _translator.interpreter.SetPendingException(e); + } + + void FillMethodArguments(LuaState luaState, int numStackToSkip) + { + object[] args = _lastCalledMethod.args; + + + for (int i = 0; i < _lastCalledMethod.argTypes.Length; i++) + { + MethodArgs type = _lastCalledMethod.argTypes[i]; + + int index = i + 1 + numStackToSkip; + + + if (_lastCalledMethod.argTypes[i].IsParamsArray) + { + int count = _lastCalledMethod.argTypes.Length - i; + Array paramArray = _translator.TableToArray(luaState, type.ExtractValue, type.ParameterType, index, count); + args[_lastCalledMethod.argTypes[i].Index] = paramArray; + } + else + { + args[type.Index] = type.ExtractValue(luaState, index); + } + + if (_lastCalledMethod.args[_lastCalledMethod.argTypes[i].Index] == null && + !luaState.IsNil(i + 1 + numStackToSkip)) + throw new LuaException(string.Format("Argument number {0} is invalid", (i + 1))); + } + } + + int PushReturnValue(LuaState luaState) + { + int nReturnValues = 0; + // Pushes out and ref return values + for (int index = 0; index < _lastCalledMethod.outList.Length; index++) + { + nReturnValues++; + _translator.Push(luaState, _lastCalledMethod.args[_lastCalledMethod.outList[index]]); + } + + // If not return void,we need add 1, + // or we will lost the function's return value + // when call dotnet function like "int foo(arg1,out arg2,out arg3)" in Lua code + if (!_lastCalledMethod.IsReturnVoid && nReturnValues > 0) + nReturnValues++; + + return nReturnValues < 1 ? 1 : nReturnValues; + } + + int CallInvoke(LuaState luaState, MethodBase method, object targetObject) + { + if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + try + { + object result; + + if (method.IsConstructor) + result = ((ConstructorInfo)method).Invoke(_lastCalledMethod.args); + else + result = method.Invoke(targetObject, _lastCalledMethod.args); + + _translator.Push(luaState, result); + } + catch (TargetInvocationException e) + { + // Failure of method invocation + if (_translator.interpreter.UseTraceback) + e.GetBaseException().Data["Traceback"] = _translator.interpreter.GetDebugTraceback(); + return SetPendingException(e.GetBaseException()); + } + catch (Exception e) + { + return SetPendingException(e); + } + + return PushReturnValue(luaState); + } + + bool IsMethodCached(LuaState luaState, int numArgsPassed, int skipParams) + { + if (_lastCalledMethod.cachedMethod == null) + return false; + + if (numArgsPassed != _lastCalledMethod.argTypes.Length) + return false; + + // If there is no method overloads, is ok to use the cached method + if (_members.Length == 1) + return true; + + return _translator.MatchParameters(luaState, _lastCalledMethod.cachedMethod, _lastCalledMethod, skipParams); + } + + int CallMethodFromName(LuaState luaState) + { + object targetObject = null; + + if (!_isStatic) + targetObject = _extractTarget(luaState, 1); + + int numStackToSkip = + _isStatic + ? 0 + : 1; // If this is an instance invoe we will have an extra arg on the stack for the targetObject + int numArgsPassed = luaState.GetTop() - numStackToSkip; + + // Cached? + if (IsMethodCached(luaState, numArgsPassed, numStackToSkip)) + { + MethodBase method = _lastCalledMethod.cachedMethod; + + if (!luaState.CheckStack(_lastCalledMethod.outList.Length + 6)) + throw new LuaException("Lua stack overflow"); + + FillMethodArguments(luaState, numStackToSkip); + + return CallInvoke(luaState, method, targetObject); + } + + // If we are running an instance variable, we can now pop the targetObject from the stack + if (!_isStatic) + { + if (targetObject == null) + { + _translator.ThrowError(luaState, + string.Format("instance method '{0}' requires a non null target object", _methodName)); + return 1; + } + + luaState.Remove(1); // Pops the receiver + } + + bool hasMatch = false; + string candidateName = null; + + foreach (var member in _members) + { + if (member.ReflectedType == null) + continue; + + candidateName = member.ReflectedType.Name + "." + member.Name; + bool isMethod = _translator.MatchParameters(luaState, member, _lastCalledMethod, 0); + + if (isMethod) + { + hasMatch = true; + break; + } + } + + if (!hasMatch) + { + string msg = (candidateName == null) + ? "Invalid arguments to method call" + : ("Invalid arguments to method: " + candidateName); + _translator.ThrowError(luaState, msg); + return 1; + } + + if (_lastCalledMethod.cachedMethod.ContainsGenericParameters) + return CallInvokeOnGenericMethod(luaState, (MethodInfo)_lastCalledMethod.cachedMethod, targetObject); + + return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject); + } + + int CallInvokeOnGenericMethod(LuaState luaState, MethodInfo methodToCall, object targetObject) + { + //need to make a concrete type of the generic method definition + var typeArgs = new List(); + + ParameterInfo [] parameters = methodToCall.GetParameters(); + + for (int i = 0; i < parameters.Length; i++) + { + ParameterInfo parameter = parameters[i]; + + if (!parameter.ParameterType.IsGenericParameter) + continue; + + typeArgs.Add(_lastCalledMethod.args[i].GetType()); + } + + MethodInfo concreteMethod = methodToCall.MakeGenericMethod(typeArgs.ToArray()); + + _translator.Push(luaState, concreteMethod.Invoke(targetObject, _lastCalledMethod.args)); + + return PushReturnValue(luaState); + } + + /* + * Calls the method. Receives the arguments from the Lua stack + * and returns values in it. + */ + int Call(IntPtr state) + { + var luaState = LuaState.FromIntPtr(state); + + MethodBase methodToCall = _method; + object targetObject = _target; + + if (!luaState.CheckStack(5)) + throw new LuaException("Lua stack overflow"); + + SetPendingException(null); + + // Method from name + if (methodToCall == null) + return CallMethodFromName(luaState); + + // Method from MethodBase instance + if (!methodToCall.ContainsGenericParameters) + { + if (!methodToCall.IsStatic && !methodToCall.IsConstructor && targetObject == null) + { + targetObject = _extractTarget(luaState, 1); + luaState.Remove(1); // Pops the receiver + } + + if (!_translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0)) + { + _translator.ThrowError(luaState, "Invalid arguments to method call"); + return 1; + } + } + else + { + if (!methodToCall.IsGenericMethodDefinition) + { + _translator.ThrowError(luaState, + "Unable to invoke method on generic class as the current method is an open generic method"); + return 1; + } + + _translator.MatchParameters(luaState, methodToCall, _lastCalledMethod, 0); + + return CallInvokeOnGenericMethod(luaState, (MethodInfo) methodToCall, targetObject); + } + + if (_isStatic) + targetObject = null; + + return CallInvoke(luaState, _lastCalledMethod.cachedMethod, targetObject); + } + } +} diff --git a/src/LUA/Method/MethodArgs.cs b/src/LUA/Method/MethodArgs.cs new file mode 100644 index 0000000..4b7db00 --- /dev/null +++ b/src/LUA/Method/MethodArgs.cs @@ -0,0 +1,18 @@ +using System; + +namespace NLua.Method +{ + /* + * Parameter information + */ + class MethodArgs + { + // Position of parameter + public int Index; + public Type ParameterType; + + // Type-conversion function + public ExtractValue ExtractValue; + public bool IsParamsArray; + } +} \ No newline at end of file diff --git a/src/LUA/Method/MethodCache.cs b/src/LUA/Method/MethodCache.cs new file mode 100644 index 0000000..74bc5e6 --- /dev/null +++ b/src/LUA/Method/MethodCache.cs @@ -0,0 +1,42 @@ +using System; +using System.Reflection; +using NLua.Extensions; + +namespace NLua.Method +{ + class MethodCache + { + public MethodCache() + { + args = new object[0]; + argTypes = new MethodArgs[0]; + outList = new int[0]; + } + private MethodBase _cachedMethod; + + public MethodBase cachedMethod { + get + { + return _cachedMethod; + } + set + { + _cachedMethod = value; + var mi = value as MethodInfo; + + if (mi != null) + { + IsReturnVoid = mi.ReturnType == typeof(void); + } + } + } + + public bool IsReturnVoid; + // List or arguments + public object[] args; + // Positions of out parameters + public int[] outList; + // Types of parameters + public MethodArgs[] argTypes; + } +} \ No newline at end of file diff --git a/src/LUA/Method/RegisterEventHandler.cs b/src/LUA/Method/RegisterEventHandler.cs new file mode 100644 index 0000000..5736062 --- /dev/null +++ b/src/LUA/Method/RegisterEventHandler.cs @@ -0,0 +1,53 @@ +using System; +using System.Reflection; + +namespace NLua.Method +{ + class RegisterEventHandler + { + private readonly EventHandlerContainer _pendingEvents; + private readonly EventInfo _eventInfo; + private readonly object _target; + + public RegisterEventHandler(EventHandlerContainer pendingEvents, object target, EventInfo eventInfo) + { + _target = target; + _eventInfo = eventInfo; + _pendingEvents = pendingEvents; + } + + /* + * Adds a new event handler + */ + public Delegate Add(LuaFunction function) + { + Delegate handlerDelegate = CodeGeneration.Instance.GetDelegate(_eventInfo.EventHandlerType, function); + return Add(handlerDelegate); + } + + public Delegate Add(Delegate handlerDelegate) + { + _eventInfo.AddEventHandler(_target, handlerDelegate); + _pendingEvents.Add(handlerDelegate, this); + + return handlerDelegate; + } + + /* + * Removes an existing event handler + */ + public void Remove(Delegate handlerDelegate) + { + RemovePending(handlerDelegate); + _pendingEvents.Remove(handlerDelegate); + } + + /* + * Removes an existing event handler (without updating the pending handlers list) + */ + internal void RemovePending(Delegate handlerDelegate) + { + _eventInfo.RemoveEventHandler(_target, handlerDelegate); + } + } +} \ No newline at end of file diff --git a/src/LUA/ObjectTranslator.cs b/src/LUA/ObjectTranslator.cs new file mode 100644 index 0000000..5d5dfb0 --- /dev/null +++ b/src/LUA/ObjectTranslator.cs @@ -0,0 +1,1171 @@ +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); + } + } +} diff --git a/src/LUA/ObjectTranslatorPool.cs b/src/LUA/ObjectTranslatorPool.cs new file mode 100644 index 0000000..7f1ac25 --- /dev/null +++ b/src/LUA/ObjectTranslatorPool.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Concurrent; + +using LuaState = KeraLua.Lua; + +namespace NLua +{ + internal class ObjectTranslatorPool + { + private static volatile ObjectTranslatorPool _instance = new ObjectTranslatorPool(); + + private ConcurrentDictionary translators = new ConcurrentDictionary(); + + public static ObjectTranslatorPool Instance => _instance; + + + public void Add(LuaState luaState, ObjectTranslator translator) + { + if(!translators.TryAdd(luaState, translator)) + throw new ArgumentException("An item with the same key has already been added. ", "luaState"); + } + + public ObjectTranslator Find(LuaState luaState) + { + ObjectTranslator translator; + + if(!translators.TryGetValue(luaState, out translator)) + { + LuaState main = luaState.MainThread; + + if (!translators.TryGetValue(main, out translator)) + return null; + } + return translator; + } + + public void Remove(LuaState luaState) + { + ObjectTranslator translator; + translators.TryRemove(luaState, out translator); + } + } +} + diff --git a/src/LUA/Properties/AssemblyInfo.cs b/src/LUA/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..befb0a5 --- /dev/null +++ b/src/LUA/Properties/AssemblyInfo.cs @@ -0,0 +1,39 @@ +using System.Reflection; + +// Information about this assembly is defined by the following attributes. +// Change them to the values specific to your project. + +#if NETFRAMEWORK +[assembly: AssemblyTitle ("NLua (.NET Framework 4.5)")] +#elif WINDOWS_UWP +[assembly: AssemblyTitle ("NLua (Windows Universal)")] +#elif __ANDROID__ +[assembly: AssemblyTitle ("NLua (Xamarin.Android)")] +#elif NETCOREAPP +[assembly: AssemblyTitle ("NLua (.NET Core)")] +#elif NETSTANDARD +[assembly: AssemblyTitle ("NLua (.NET Standard)")] +#elif __TVOS__ +[assembly: AssemblyTitle ("NLua (Xamarin.tvOS)")] +#elif __WATCHOS__ +[assembly: AssemblyTitle ("NLua (Xamarin.watchOS)")] +#elif __IOS__ +[assembly: AssemblyTitle ("NLua (Xamarin.iOS)")] +#elif __MACOS__ +[assembly: AssemblyTitle ("NLua (Xamarin.Mac)")] +#else +[assembly: AssemblyTitle ("NLua (.NET Framework)")] +#endif + +[assembly: AssemblyDescription ("NLua library")] +[assembly: AssemblyCompany ("NLua.org")] +[assembly: AssemblyProduct ("NLua")] +[assembly: AssemblyCopyright ("Copyright © Vinicius Jarina 2020")] +[assembly: AssemblyCulture ("")] + + +[assembly: AssemblyVersion("1.4.1.0")] +[assembly: AssemblyInformationalVersion("1.0.7+Branch.master.Sha.80a328a64f12ed9032a0f14a75e6ecad967514d0")] +[assembly: AssemblyFileVersion("1.4.1.0")] + + diff --git a/src/LUA/ProxyType.cs b/src/LUA/ProxyType.cs new file mode 100644 index 0000000..92c0075 --- /dev/null +++ b/src/LUA/ProxyType.cs @@ -0,0 +1,53 @@ +using System; +using System.Reflection; + +namespace NLua +{ + /// + /// Summary description for ProxyType. + /// + public class ProxyType + { + private readonly Type _proxy; + + public ProxyType(Type proxy) + { + _proxy = proxy; + } + + /// + /// Provide human readable short hand for this proxy object + /// + /// + public override string ToString() + { + return "ProxyType(" + UnderlyingSystemType + ")"; + } + + public Type UnderlyingSystemType => _proxy; + + public override bool Equals(object obj) + { + if (obj is Type) + return _proxy == (Type)obj; + if (obj is ProxyType) + return _proxy == ((ProxyType)obj).UnderlyingSystemType; + return _proxy.Equals(obj); + } + + public override int GetHashCode() + { + return _proxy.GetHashCode(); + } + + public MemberInfo[] GetMember(string name, BindingFlags bindingAttr) + { + return _proxy.GetMember(name, bindingAttr); + } + + public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Type[] signature) + { + return _proxy.GetMethod(name, bindingAttr, null, signature, null); + } + } +} \ No newline at end of file diff --git a/src/LUAEngine.cs b/src/LUAEngine.cs new file mode 100644 index 0000000..91372d9 --- /dev/null +++ b/src/LUAEngine.cs @@ -0,0 +1,213 @@ +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +[assembly: Addin("LUAEngine", "0.1")] +[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)] +namespace OpenSim.Region.ScriptEngine.Lua +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "LUAEngine")] + class LUAEngine : INonSharedRegionModule, IScriptModule, IScriptEngine + { + #region INonSharedRegionModule + public string Name => throw new NotImplementedException(); + + public Type ReplaceableInterface => throw new NotImplementedException(); + + public void AddRegion(Scene scene) + { + throw new NotImplementedException(); + } + + public void Close() + { + throw new NotImplementedException(); + } + + public void Initialise(IConfigSource source) + { + throw new NotImplementedException(); + } + + public void RegionLoaded(Scene scene) + { + throw new NotImplementedException(); + } + + public void RemoveRegion(Scene scene) + { + throw new NotImplementedException(); + } + #endregion + + #region IScriptModule + public string ScriptEngineName => throw new NotImplementedException(); + + public event ScriptRemoved OnScriptRemoved; + public event ObjectRemoved OnObjectRemoved; + + public Dictionary GetObjectScriptsExecutionTimes() + { + throw new NotImplementedException(); + } + + public ArrayList GetScriptErrors(UUID itemID) + { + throw new NotImplementedException(); + } + + public float GetScriptExecutionTime(List itemIDs) + { + throw new NotImplementedException(); + } + + public bool GetScriptState(UUID itemID) + { + throw new NotImplementedException(); + } + + public string GetXMLState(UUID itemID) + { + throw new NotImplementedException(); + } + + public bool HasScript(UUID itemID, out bool running) + { + throw new NotImplementedException(); + } + + + public bool PostObjectEvent(UUID itemID, string name, object[] args) + { + throw new NotImplementedException(); + } + + public bool PostScriptEvent(UUID itemID, string name, object[] args) + { + throw new NotImplementedException(); + } + + public bool ResumeScript(UUID itemID) + { + throw new NotImplementedException(); + } + + public void SaveAllState() + { + throw new NotImplementedException(); + } + + public bool SetXMLState(UUID itemID, string xml) + { + throw new NotImplementedException(); + } + + public void StartProcessing() + { + throw new NotImplementedException(); + } + + public bool SuspendScript(UUID itemID) + { + throw new NotImplementedException(); + } + #endregion + + #region IScriptEngine + public Scene World => throw new NotImplementedException(); + + public IScriptModule ScriptModule => throw new NotImplementedException(); + + public IConfig Config => throw new NotImplementedException(); + + public IConfigSource ConfigSource => throw new NotImplementedException(); + + public string ScriptEnginePath => throw new NotImplementedException(); + + public string ScriptClassName => throw new NotImplementedException(); + + public string ScriptBaseClassName => throw new NotImplementedException(); + + public string[] ScriptReferencedAssemblies => throw new NotImplementedException(); + + public ParameterInfo[] ScriptBaseClassParameters => throw new NotImplementedException(); + + public void ApiResetScript(UUID itemID) + { + throw new NotImplementedException(); + } + + public void CancelScriptEvent(UUID itemID, string eventName) + { + throw new NotImplementedException(); + } + + public IScriptApi GetApi(UUID itemID, string name) + { + throw new NotImplementedException(); + } + + public DetectParams GetDetectParams(UUID item, int number) + { + throw new NotImplementedException(); + } + + public int GetStartParameter(UUID itemID) + { + throw new NotImplementedException(); + } + + public bool PostObjectEvent(uint localID, EventParams parms) + { + throw new NotImplementedException(); + } + + public bool PostScriptEvent(UUID itemID, EventParams parms) + { + throw new NotImplementedException(); + } + + public IScriptWorkItem QueueEventHandler(object parms) + { + throw new NotImplementedException(); + } + + public void ResetScript(UUID itemID) + { + throw new NotImplementedException(); + } + + public void SetMinEventDelay(UUID itemID, double delay) + { + throw new NotImplementedException(); + } + + public void SetScriptState(UUID itemID, bool state, bool self) + { + throw new NotImplementedException(); + } + + public void SetState(UUID itemID, string newState) + { + throw new NotImplementedException(); + } + + public void SleepScript(UUID itemID, int delay) + { + throw new NotImplementedException(); + } + #endregion + + } +}