1172 lines
41 KiB
C#
1172 lines
41 KiB
C#
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<object>
|
|
{
|
|
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<object, int> _objectsBackMap = new Dictionary<object, int>(new ReferenceComparer());
|
|
// object # to object (FIXME - it should be possible to get object address as an object #)
|
|
readonly Dictionary<int, object> _objects = new Dictionary<int, object>();
|
|
|
|
readonly ConcurrentQueue<int> finalizedReferences = new ConcurrentQueue<int>();
|
|
|
|
internal EventHandlerContainer PendingEvents = new EventHandlerContainer();
|
|
MetaFunctions metaFunctions;
|
|
List<Assembly> assemblies;
|
|
internal CheckType typeChecker;
|
|
internal Lua interpreter;
|
|
/// <summary>
|
|
/// We want to ensure that objects always have a unique ID
|
|
/// </summary>
|
|
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<Assembly>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given the Lua int ID for an object remove it from our maps
|
|
/// </summary>
|
|
/// <param name = "udata"></param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given an object reference, remove it from our maps
|
|
/// </summary>
|
|
/// <param name = "o"></param>
|
|
/// <param name = "udata"></param>
|
|
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<object>();
|
|
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<object>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|