Support implicit and explicit casting

This commit is contained in:
Deukhoofd 2019-09-01 16:44:38 +02:00
parent c1ed9b1eb6
commit d36d091d28
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
6 changed files with 329 additions and 207 deletions

View File

@ -1,133 +1,52 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.UserData;
namespace PorygonSharp.ScriptType
{
internal static class ScriptType
public class ScriptType
{
internal static IntPtr? GetScriptType(Type t)
{
if (t.IsEnum && !t.IsGenericParameter)
{
if (UserDataHandler.IsTypeRegistered(t))
return UserDataHandler.CreateUserDataType(t);
return CreateNumericScriptType(true, false);
}
var typeCode = Type.GetTypeCode(t);
switch (typeCode)
{
case TypeCode.Boolean:
return CreateScriptType(TypeClass.Bool);
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return CreateNumericScriptType(true, false);
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return CreateNumericScriptType(true, true);
case TypeCode.Char:
case TypeCode.String:
return CreateStringScriptType(false, 0);
case TypeCode.Object:
if (t == typeof(void))
{
return CreateScriptType(TypeClass.Nil);
}
if (UserDataHandler.IsTypeRegistered(t))
{
return UserDataHandler.CreateUserDataType(t);
}
if (typeof(IList).IsAssignableFrom(t))
{
return CreateUserDataListType(t);
}
if (typeof(IDictionary).IsAssignableFrom(t))
{
return CreateUserDataDictionaryType(t);
}
private readonly IntPtr _ptr;
var attr = t.GetCustomAttribute<Attributes.PorygonUserdataAttribute>();
if (attr != null)
{
UserDataHandler.RegisterType(attr.Identifier, t);
return UserDataHandler.CreateUserDataType(t);
}
return null;
public ScriptType(IntPtr ptr)
{
_ptr = ptr;
}
public TypeClass GetTypeClass()
{
return GetTypeClass(_ptr);
}
public uint GetUserDataType()
{
return GetScriptTypeUserData(_ptr);
}
internal Type ResolveType()
{
var c = GetTypeClass();
switch (c)
{
case TypeClass.Number: return typeof(long);
case TypeClass.Bool: return typeof(bool);
case TypeClass.String: return typeof(string);
case TypeClass.UserData:
var id = GetUserDataType();
var t = UserDataHandler.ReverseLookup[id];
return t.Type;
default:
return null;
throw new ArgumentOutOfRangeException();
}
}
internal static IntPtr? GetFunctionScriptType(MethodInfo info)
{
try
{
var returnType = GetScriptType(info.ReturnType);
if (!returnType.HasValue)
return null;
var parameters = info.GetParameters();
var parameterFuncs = new IntPtr[parameters.Length];
for (var index = 0; index < parameters.Length; index++)
{
var parameter = parameters[index];
var parameterType = GetScriptType(parameter.ParameterType);
if (!parameterType.HasValue)
{
return null;
}
parameterFuncs[index] = parameterType.Value;
}
return CreateUserDataFunctionScriptType(returnType.Value, parameterFuncs, parameters.Length);
}
catch
{
return IntPtr.Zero;
}
}
private static IntPtr? CreateUserDataListType(Type t)
{
var keyType = CreateNumericScriptType(true, false);
var valType = t.IsArray ? t.GetElementType() : t.GenericTypeArguments[0];
var valueType = GetScriptType(valType);
if (valueType.HasValue)
return CreateCollectionType(keyType, valueType.Value);
return null;
}
private static IntPtr? CreateUserDataDictionaryType(Type t)
{
var keyType = GetScriptType(t.GenericTypeArguments[0]);
var valueType = GetScriptType(t.GenericTypeArguments[1]);
if (keyType.HasValue && valueType.HasValue)
return CreateCollectionType(keyType.Value, valueType.Value);
return null;
}
[DllImport("PorygonLang", EntryPoint = "CreateScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateScriptType(TypeClass t);
[DllImport("PorygonLang", EntryPoint = "CreateNumericScriptType", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr CreateNumericScriptType(bool isAware, bool isFloat);
[DllImport("PorygonLang", EntryPoint = "CreateStringScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateStringScriptType(bool knownAtBind, uint hash);
[DllImport("PorygonLang", EntryPoint = "CreateUserDataFunctionScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateUserDataFunctionScriptType(IntPtr returnType, IntPtr[] parameters, int parameterCount);
[DllImport("PorygonLang", EntryPoint = "CreateCollectionType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateCollectionType(IntPtr keyType, IntPtr valueType);
[DllImport("PorygonLang", EntryPoint = "GetTypeClass", CallingConvention = CallingConvention.Cdecl)]
private static extern TypeClass GetTypeClass(IntPtr t);
[DllImport("PorygonLang", EntryPoint = "GetScriptTypeUserData", CallingConvention = CallingConvention.Cdecl)]
private static extern uint GetScriptTypeUserData(IntPtr t);
}
}

View File

@ -0,0 +1,132 @@
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.UserData;
namespace PorygonSharp.ScriptType
{
internal static class ScriptTypeHandler
{
internal static IntPtr? GetScriptType(Type t)
{
if (t.IsEnum && !t.IsGenericParameter)
{
if (UserDataHandler.IsTypeRegistered(t))
return UserDataHandler.CreateUserDataType(t);
return CreateNumericScriptType(true, false);
}
var typeCode = Type.GetTypeCode(t);
switch (typeCode)
{
case TypeCode.Boolean:
return CreateScriptType(TypeClass.Bool);
case TypeCode.Byte:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return CreateNumericScriptType(true, false);
case TypeCode.Decimal:
case TypeCode.Double:
case TypeCode.Single:
return CreateNumericScriptType(true, true);
case TypeCode.Char:
case TypeCode.String:
return CreateStringScriptType(false, 0);
case TypeCode.Object:
if (t == typeof(void))
{
return CreateScriptType(TypeClass.Nil);
}
if (UserDataHandler.IsTypeRegistered(t))
{
return UserDataHandler.CreateUserDataType(t);
}
if (typeof(IList).IsAssignableFrom(t))
{
return CreateUserDataListType(t);
}
if (typeof(IDictionary).IsAssignableFrom(t))
{
return CreateUserDataDictionaryType(t);
}
var attr = t.GetCustomAttribute<Attributes.PorygonUserdataAttribute>();
if (attr != null)
{
UserDataHandler.RegisterType(attr.Identifier, t);
return UserDataHandler.CreateUserDataType(t);
}
return null;
default:
return null;
}
}
internal static IntPtr? GetFunctionScriptType(MethodInfo info)
{
try
{
var returnType = GetScriptType(info.ReturnType);
if (!returnType.HasValue)
return null;
var parameters = info.GetParameters();
var parameterFuncs = new IntPtr[parameters.Length];
for (var index = 0; index < parameters.Length; index++)
{
var parameter = parameters[index];
var parameterType = GetScriptType(parameter.ParameterType);
if (!parameterType.HasValue)
{
return null;
}
parameterFuncs[index] = parameterType.Value;
}
return CreateUserDataFunctionScriptType(returnType.Value, parameterFuncs, parameters.Length);
}
catch
{
return IntPtr.Zero;
}
}
private static IntPtr? CreateUserDataListType(Type t)
{
var keyType = CreateNumericScriptType(true, false);
var valType = t.IsArray ? t.GetElementType() : t.GenericTypeArguments[0];
var valueType = GetScriptType(valType);
if (valueType.HasValue)
return CreateCollectionType(keyType, valueType.Value);
return null;
}
private static IntPtr? CreateUserDataDictionaryType(Type t)
{
var keyType = GetScriptType(t.GenericTypeArguments[0]);
var valueType = GetScriptType(t.GenericTypeArguments[1]);
if (keyType.HasValue && valueType.HasValue)
return CreateCollectionType(keyType.Value, valueType.Value);
return null;
}
[DllImport("PorygonLang", EntryPoint = "CreateScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateScriptType(TypeClass t);
[DllImport("PorygonLang", EntryPoint = "CreateNumericScriptType", CallingConvention = CallingConvention.Cdecl)]
internal static extern IntPtr CreateNumericScriptType(bool isAware, bool isFloat);
[DllImport("PorygonLang", EntryPoint = "CreateStringScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateStringScriptType(bool knownAtBind, uint hash);
[DllImport("PorygonLang", EntryPoint = "CreateUserDataFunctionScriptType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateUserDataFunctionScriptType(IntPtr returnType, IntPtr[] parameters, int parameterCount);
[DllImport("PorygonLang", EntryPoint = "CreateCollectionType", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateCollectionType(IntPtr keyType, IntPtr valueType);
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using PorygonSharp.EvalValues;
using PorygonSharp.HelperLibraries;
using PorygonSharp.Utilities;
namespace PorygonSharp
@ -10,7 +11,7 @@ namespace PorygonSharp
public static void RegisterStaticVariable(string name, object o)
{
var type = o.GetType();
var scriptType = ScriptType.ScriptType.GetScriptType(type);
var scriptType = ScriptType.ScriptTypeHandler.GetScriptType(type);
var hash = name.ScriptHash();
var value = EvalValueCreator.CreateValue(o);
if (!scriptType.HasValue)

View File

@ -1,4 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.Utilities;
namespace PorygonSharp.UserData
{
@ -6,10 +11,155 @@ namespace PorygonSharp.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;
public UserData(uint id)
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.FlattenHierarchy | 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 methods = Type.GetMethods(BindingFlags);
foreach (var method in methods)
{
RegisterFunction(method);
}
}
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 type = ScriptType.ScriptTypeHandler.GetFunctionScriptType(method);
if (type == IntPtr.Zero || !type.HasValue)
return;
var fieldName = method.Name.ScriptHash();
if (Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(method);
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 (_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 objPtr;
if (_implicitCasts.TryGetValue(expectedType, out var func))
{
var castVal = func.Invoke(null, new[] {obj});
var handle = GCHandle.Alloc(castVal, GCHandleType.WeakTrackResurrection);
return GCHandle.ToIntPtr(handle);
}
if (_explicitCasts.TryGetValue(expectedType, out func))
{
var castVal = func.Invoke(null, new[] {obj});
var handle = GCHandle.Alloc(castVal, GCHandleType.WeakTrackResurrection);
return GCHandle.ToIntPtr(handle);
}
throw new Exception("Invalid cast kind");
}
[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);
}
}

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.Attributes;
@ -13,7 +11,7 @@ namespace PorygonSharp.UserData
public static class UserDataHandler
{
private static readonly ConcurrentDictionary<Type, UserData> UserDataLookup = new ConcurrentDictionary<Type, UserData>();
private static readonly ConcurrentDictionary<uint, UserData> ReverseLookup = new ConcurrentDictionary<uint, UserData>();
internal static readonly ConcurrentDictionary<uint, UserData> ReverseLookup = new ConcurrentDictionary<uint, UserData>();
public static void RegisterAssembly(Assembly assembly)
@ -43,29 +41,10 @@ namespace PorygonSharp.UserData
if (UserDataLookup.ContainsKey(type))
return;
RegisterUserDataType(hash);
var ud = new UserData(hash);
var ud = new UserData(hash, type);
UserDataLookup.TryAdd(type, ud);
ReverseLookup.TryAdd(hash, ud);
}
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance |
BindingFlags.FlattenHierarchy | BindingFlags.Static;
var fields = type.GetFields(bindingFlags);
foreach (var field in fields)
{
RegisterField(field, hash);
}
var properties = type.GetProperties(bindingFlags);
foreach (var property in properties)
{
RegisterProperty(property, hash);
}
var methods = type.GetMethods(bindingFlags);
foreach (var method in methods)
{
RegisterFunction(method, hash);
ud.RegisterFields();
}
}
@ -77,7 +56,7 @@ namespace PorygonSharp.UserData
if (UserDataLookup.ContainsKey(type))
return;
RegisterUserDataType(hash);
var ud = new UserData(hash);
var ud = new UserData(hash, type);
UserDataLookup.TryAdd(type, ud);
ReverseLookup.TryAdd(hash, ud);
}
@ -88,67 +67,14 @@ namespace PorygonSharp.UserData
var fieldData = new UserDataField(getter, null);
var valueName = Enum.GetName(type, value);
var fieldName = valueName.ScriptHash();
var t = ScriptType.ScriptType.CreateNumericScriptType(true, false);
RegisterUserDataField(hash, fieldName,
CreateUserDataField(t, fieldData.GetGetterPointer(), IntPtr.Zero));
var t = ScriptType.ScriptTypeHandler.CreateNumericScriptType(true, false);
UserData.RegisterUserDataField(hash, fieldName,
UserData.CreateUserDataField(t, fieldData.GetGetterPointer(), IntPtr.Zero));
ReverseLookup[hash].Fields[fieldName] = fieldData;
}
}
private static void RegisterField(FieldInfo field, uint typeHash)
{
var scriptType = ScriptType.ScriptType.GetScriptType(field.FieldType);
if (!scriptType.HasValue)
return;
if (!ReverseLookup.TryGetValue(typeHash, out var userData))
return;
var fieldName = field.Name.ScriptHash();
if (userData.Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(field);
var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
RegisterUserDataField(typeHash, fieldName, userDataField);
userData.Fields.Add(fieldName, fieldData);
}
private static void RegisterProperty(PropertyInfo property, uint typeHash)
{
var scriptType = ScriptType.ScriptType.GetScriptType(property.PropertyType);
if (!scriptType.HasValue)
return;
if (!ReverseLookup.TryGetValue(typeHash, out var userData))
return;
var fieldName = property.Name.ScriptHash();
if (userData.Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(property);
var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
RegisterUserDataField(typeHash, fieldName, userDataField);
userData.Fields.Add(fieldName, fieldData);
}
private static void RegisterFunction(MethodInfo method, uint typeHash)
{
var type = ScriptType.ScriptType.GetFunctionScriptType(method);
if (type == IntPtr.Zero || !type.HasValue)
return;
if (!ReverseLookup.TryGetValue(typeHash, out var userData))
return;
var fieldName = method.Name.ScriptHash();
if (userData.Fields.ContainsKey(fieldName))
return;
var fieldData = new UserDataField(method);
var userDataField = CreateUserDataField(type.Value, fieldData.GetGetterPointer(), IntPtr.Zero);
RegisterUserDataField(typeHash, fieldName, userDataField);
userData.Fields.Add(fieldName, fieldData);
}
public static uint GetTypeId(Type t)
{
@ -188,12 +114,6 @@ namespace PorygonSharp.UserData
[DllImport("PorygonLang", EntryPoint = "RegisterUserDataType", CallingConvention = CallingConvention.Cdecl)]
private static extern void RegisterUserDataType(uint hashId);
[DllImport("PorygonLang", EntryPoint = "RegisterUserDataField", CallingConvention = CallingConvention.Cdecl)]
private static extern void RegisterUserDataField(uint hashId, uint fieldId, IntPtr field);
[DllImport("PorygonLang", EntryPoint = "CreateUserDataField", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr CreateUserDataField(IntPtr type, IntPtr getter, IntPtr setter);
[DllImport("PorygonLang", EntryPoint = "GetUserDataFieldCount", CallingConvention = CallingConvention.Cdecl)]
private static extern int GetUserDataFieldCount(uint hashId);

Binary file not shown.