diff --git a/PorygonSharp/ScriptType/ScriptType.cs b/PorygonSharp/ScriptType/ScriptType.cs index 0a9d77e..bbd2a22 100644 --- a/PorygonSharp/ScriptType/ScriptType.cs +++ b/PorygonSharp/ScriptType/ScriptType.cs @@ -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(); - 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); + } } \ No newline at end of file diff --git a/PorygonSharp/ScriptType/ScriptTypeHandler.cs b/PorygonSharp/ScriptType/ScriptTypeHandler.cs new file mode 100644 index 0000000..055ebd8 --- /dev/null +++ b/PorygonSharp/ScriptType/ScriptTypeHandler.cs @@ -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(); + 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); + } +} \ No newline at end of file diff --git a/PorygonSharp/StaticScope.cs b/PorygonSharp/StaticScope.cs index 09c5f0a..7d3979c 100644 --- a/PorygonSharp/StaticScope.cs +++ b/PorygonSharp/StaticScope.cs @@ -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) diff --git a/PorygonSharp/UserData/UserData.cs b/PorygonSharp/UserData/UserData.cs index e7415cc..b1ae19f 100644 --- a/PorygonSharp/UserData/UserData.cs +++ b/PorygonSharp/UserData/UserData.cs @@ -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 Fields = new Dictionary(); public uint Id { get; } + public Type Type { get; } + private readonly Dictionary _implicitCasts; + private readonly Dictionary _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); + } } \ No newline at end of file diff --git a/PorygonSharp/UserData/UserDataHandler.cs b/PorygonSharp/UserData/UserDataHandler.cs index 6af8379..e1b54a0 100644 --- a/PorygonSharp/UserData/UserDataHandler.cs +++ b/PorygonSharp/UserData/UserDataHandler.cs @@ -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 UserDataLookup = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary ReverseLookup = new ConcurrentDictionary(); + internal static readonly ConcurrentDictionary ReverseLookup = new ConcurrentDictionary(); 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); diff --git a/PorygonSharp/libPorygonLang.so b/PorygonSharp/libPorygonLang.so index a0cafda..7a98049 100755 Binary files a/PorygonSharp/libPorygonLang.so and b/PorygonSharp/libPorygonLang.so differ