PorygonSharp/PorygonSharp/UserData/UserData.cs

262 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.EvalValues;
using PorygonSharp.Utilities;
namespace PorygonSharp.UserData
{
internal class UserData
{
public readonly Dictionary<uint, UserDataField> Fields = new Dictionary<uint, UserDataField>();
public uint Id { get; }
public Type Type { get; }
private readonly Dictionary<Type, MethodInfo> _implicitCasts;
private readonly Dictionary<Type, MethodInfo> _explicitCasts;
// ReSharper disable once CollectionNeverQueried.Local
private readonly List<UserDataBinaryOperation> _binaryOperations = new List<UserDataBinaryOperation>();
private delegate bool IsCastableDelegate(IntPtr scriptType, bool isExplicit);
private delegate IntPtr CastDelegate(IntPtr obj, IntPtr scriptType);
// ReSharper disable PrivateFieldCanBeConvertedToLocalVariable
private readonly IsCastableDelegate _isCastableFunc;
private readonly CastDelegate _castFunc;
// ReSharper restore PrivateFieldCanBeConvertedToLocalVariable
private const BindingFlags BindingFlags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance |
System.Reflection.BindingFlags.Static;
public UserData(uint id, Type type)
{
Id = id;
Type = type;
_implicitCasts = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => string.Equals(mi.Name, "op_Implicit"))
.ToDictionary(x => x.ReturnType, x => x);
_explicitCasts = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where(mi => string.Equals(mi.Name, "op_Explicit"))
.ToDictionary(x => x.ReturnType, x => x);
_isCastableFunc = IsCastable;
_castFunc = Cast;
SetIsCastableFunc(id, Marshal.GetFunctionPointerForDelegate(_isCastableFunc));
SetCastFunc(id, Marshal.GetFunctionPointerForDelegate(_castFunc));
}
internal void RegisterFields()
{
var fields = Type.GetFields(BindingFlags);
foreach (var field in fields)
{
RegisterField(field);
}
var properties = Type.GetProperties(BindingFlags);
foreach (var property in properties)
{
RegisterProperty(property);
}
var t = Type;
if (t.IsInterface)
{
// GetProperties doesn't return inherited properties if type is an interface.
var parentTypes = t.GetInterfaces();
foreach (var parentType in parentTypes)
{
properties = parentType.GetProperties(BindingFlags);
foreach (var property in properties)
{
RegisterProperty(property);
}
}
}
var methods = Type.GetMethods(BindingFlags);
foreach (var method in methods)
{
RegisterFunction(method);
}
RegisterOperations();
}
private void RegisterField(FieldInfo field)
{
var scriptType = ScriptType.ScriptTypeHandler.GetScriptType(field.FieldType);
if (!scriptType.HasValue)
return;
var fieldName = field.Name.ScriptHash();
if (Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(field);
var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
RegisterUserDataField(Id, fieldName, userDataField);
Fields.Add(fieldName, fieldData);
}
private void RegisterProperty(PropertyInfo property)
{
var scriptType = ScriptType.ScriptTypeHandler.GetScriptType(property.PropertyType);
if (!scriptType.HasValue)
return;
var fieldName = property.Name.ScriptHash();
if (Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(property);
var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
RegisterUserDataField(Id, fieldName, userDataField);
Fields.Add(fieldName, fieldData);
}
private void RegisterFunction(MethodInfo method)
{
var fieldName = method.Name.ScriptHash();
if (Fields.TryGetValue(fieldName, out var f))
{
if (!(f is UserDataFunctionField func))
return;
var methodPtr = func.MethodPtr;
var ret = ScriptType.ScriptTypeHandler.GetScriptType(method.ReturnType);
if (!ret.HasValue) return;
var parameters = method.GetParameters()
.Select(x => ScriptType.ScriptTypeHandler.GetScriptType(x.ParameterType))
.ToArray();
if (parameters.Any(x => !x.HasValue))
return;
RegisterUserDataFunctionScriptTypeOption(methodPtr, ret.Value,
parameters.Select(x => x.Value).ToArray(), parameters.Length);
func.RegisterOption(method);
}
else
{
var type = ScriptType.ScriptTypeHandler.GetFunctionScriptType(method);
if (type == IntPtr.Zero || !type.HasValue)
return;
var fieldData = new UserDataFunctionField(method, type.Value);
var userDataField = CreateUserDataField(type.Value, fieldData.GetGetterPointer(), IntPtr.Zero);
RegisterUserDataField(Id, fieldName, userDataField);
Fields.Add(fieldName, fieldData);
}
}
private bool IsCastable(IntPtr scriptTypePtr, bool isExplicit)
{
var scriptType = new ScriptType.ScriptType(scriptTypePtr);
var expectedType = scriptType.ResolveType();
if (expectedType.IsAssignableFrom(Type))
return true;
if (Type.IsAssignableFrom(expectedType))
return true;
if (_implicitCasts.ContainsKey(expectedType))
return true;
if (isExplicit && _explicitCasts.ContainsKey(expectedType))
return true;
return false;
}
private IntPtr Cast(IntPtr objPtr, IntPtr typePtr)
{
var scriptType = new ScriptType.ScriptType(typePtr);
var expectedType = scriptType.ResolveType();
var obj = GCHandle.FromIntPtr(objPtr).Target;
var objType = obj.GetType();
if (expectedType.IsAssignableFrom(objType))
return EvalValueCreator.CreateValue(obj).GetPointer();
if (objType.IsAssignableFrom(expectedType))
return EvalValueCreator.CreateValue(obj).GetPointer();
if (_implicitCasts.TryGetValue(expectedType, out var func))
{
var castVal = func.Invoke(null, new[] {obj});
return EvalValueCreator.CreateValue(castVal).GetPointer();
}
if (_explicitCasts.TryGetValue(expectedType, out func))
{
var castVal = func.Invoke(null, new[] {obj});
return EvalValueCreator.CreateValue(castVal).GetPointer();
}
throw new Exception("Invalid cast kind");
}
private void RegisterOperations()
{
var methods = Type.GetMethods(BindingFlags.Public | BindingFlags.Static);
foreach (var method in methods)
{
switch (method.Name)
{
case "op_Addition":
RegisterOperation(BinaryOperationKind.Addition, method);
break;
case "op_Subtraction":
RegisterOperation(BinaryOperationKind.Subtraction, method);
break;
case "op_Multiply":
RegisterOperation(BinaryOperationKind.Multiplication, method);
break;
case "op_Division":
RegisterOperation(BinaryOperationKind.Division, method);
break;
case "op_Equality":
RegisterOperation(BinaryOperationKind.Equality, method);
break;
case "op_Inequality":
RegisterOperation(BinaryOperationKind.Inequality, method);
break;
case "op_LessThan":
RegisterOperation(BinaryOperationKind.Less, method);
break;
case "op_GreaterThan":
RegisterOperation(BinaryOperationKind.Greater, method);
break;
case "op_LessThanOrEqual":
RegisterOperation(BinaryOperationKind.LessOrEquals, method);
break;
case "op_GreaterThanOrEqual":
RegisterOperation(BinaryOperationKind.GreaterOrEquals, method);
break;
default: continue;
}
}
}
private void RegisterOperation(BinaryOperationKind kind, MethodInfo info)
{
var operation = UserDataBinaryOperation.Create(info);
AddUserdataBinaryOperation(Id, (byte)kind, operation.Handle);
_binaryOperations.Add(operation);
}
[DllImport("PorygonLang", EntryPoint = "RegisterUserDataField", CallingConvention = CallingConvention.Cdecl)]
internal static extern void RegisterUserDataField(uint hashId, uint fieldId, IntPtr field);
[DllImport("PorygonLang", EntryPoint = "CreateUserDataField", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr CreateUserDataField(IntPtr type, IntPtr getter, IntPtr setter);
[DllImport("PorygonLang", EntryPoint = "SetIsCastableFunc", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetIsCastableFunc(uint id, IntPtr isCastableFunc);
[DllImport("PorygonLang", EntryPoint = "SetCastFunc", CallingConvention = CallingConvention.Cdecl)]
private static extern void SetCastFunc(uint id, IntPtr castFunc);
[DllImport("PorygonLang", EntryPoint = "AddUserdataBinaryOperation", CallingConvention = CallingConvention.Cdecl)]
private static extern void AddUserdataBinaryOperation(uint id, byte kind, IntPtr operation);
[DllImport("PorygonLang", EntryPoint = "RegisterUserDataFunctionScriptTypeOption", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr RegisterUserDataFunctionScriptTypeOption(IntPtr existing, IntPtr returnType,
IntPtr[] parameters, int parameterCount);
}
}