262 lines
11 KiB
C#
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);
|
|
|
|
}
|
|
} |