OpenSim.Region.ScriptEngine.../src/LUA/ObjectTranslator.cs

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);
}
}
}