Prevent field getter/setter delegates from being garbage collected

This commit is contained in:
Deukhoofd 2019-08-25 12:28:05 +02:00
parent ef2a514e54
commit 29b2df793d
Signed by: Deukhoofd
GPG Key ID: ADF2E9256009EDCE
4 changed files with 162 additions and 80 deletions

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
namespace PorygonSharp.UserData
{
internal class UserData
{
public readonly Dictionary<uint, UserDataField> Fields = new Dictionary<uint, UserDataField>();
public uint Id { get; }
public UserData(uint id)
{
Id = id;
}
}
}

View File

@ -0,0 +1,103 @@
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using PorygonSharp.EvalValues;
namespace PorygonSharp.UserData
{
internal class UserDataField
{
internal delegate IntPtr GetterDelegate(IntPtr ptr);
internal delegate void SetterDelegate(IntPtr ptr, IntPtr val);
private delegate IntPtr CallerDelegate(IntPtr parent, IntPtr scriptOption,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]IntPtr[] parameters, int size);
private readonly GetterDelegate _getter;
private readonly SetterDelegate _setter;
public UserDataField(FieldInfo field)
{
_getter = ptr =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var value = field.GetValue(obj);
return EvalValueCreator.CreateValue(value).GetPointer();
};
if (!field.IsInitOnly)
{
_setter = (ptr, val) =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var evalValue = Convert.ChangeType(new EvalValue(val).GetObjectValue(), field.FieldType);
field.SetValue(obj, evalValue);
};
}
}
public UserDataField(PropertyInfo property)
{
if (property.GetGetMethod(false) != null)
{
_getter = ptr =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var value = property.GetValue(obj);
return EvalValueCreator.CreateValue(value).GetPointer();
};
}
if (property.GetSetMethod(false) != null)
{
_setter = (ptr, val) =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var evalValue = Convert.ChangeType(new EvalValue(val).GetObjectValue(), property.PropertyType);
property.SetValue(obj, evalValue);
};
}
}
public UserDataField(MethodInfo method)
{
var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
_getter = ptr =>
{
var func = new CallerDelegate((parent, scriptOptions, parameters, size) =>
{
var evaluatedParameters = new object[size];
for (var i = 0; i < size; i++)
{
var eval = new EvalValue(parameters[i]);
var val = eval.GetObjectValue();
var convertedType = Convert.ChangeType(val, parameterTypes[i]);
evaluatedParameters[i] = convertedType;
}
var parentObj = GCHandle.FromIntPtr(parent).Target;
var result = method.Invoke(parentObj, evaluatedParameters);
return EvalValueCreator.CreateValue(result).GetPointer();
});
var funcPtr = Marshal.GetFunctionPointerForDelegate(func);
return EvalValueCreator.FunctionEvalValue(funcPtr, ptr).GetPointer();
};
}
public UserDataField(GetterDelegate getter, SetterDelegate setter)
{
_getter = getter;
_setter = setter;
}
internal IntPtr GetGetterPointer()
{
return _getter == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(_getter);
}
internal IntPtr GetSetterPointer()
{
return _setter == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(_setter);
}
}
}

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -11,12 +12,9 @@ namespace PorygonSharp.UserData
{ {
public static class UserDataHandler public static class UserDataHandler
{ {
private static readonly Dictionary<Type, uint> UserDataLookup = new Dictionary<Type, uint>(); private static readonly ConcurrentDictionary<Type, UserData> UserDataLookup = new ConcurrentDictionary<Type, UserData>();
private static readonly ConcurrentDictionary<uint, UserData> ReverseLookup = new ConcurrentDictionary<uint, UserData>();
private delegate IntPtr GetterDelegate(IntPtr ptr);
private delegate void SetterDelegate(IntPtr ptr, IntPtr val);
private delegate IntPtr CallerDelegate(IntPtr parent, IntPtr scriptOption,
[MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)]IntPtr[] parameters, int size);
public static void RegisterAssembly(Assembly assembly) public static void RegisterAssembly(Assembly assembly)
{ {
@ -45,7 +43,9 @@ namespace PorygonSharp.UserData
if (UserDataLookup.ContainsKey(type)) if (UserDataLookup.ContainsKey(type))
return; return;
RegisterUserDataType(hash); RegisterUserDataType(hash);
UserDataLookup.Add(type, hash); var ud = new UserData(hash);
UserDataLookup.TryAdd(type, ud);
ReverseLookup.TryAdd(hash, ud);
} }
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance | const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.Instance |
@ -72,129 +72,93 @@ namespace PorygonSharp.UserData
public static void RegisterEnumType(string name, Type type) public static void RegisterEnumType(string name, Type type)
{ {
var hash = name.ScriptHash(); var hash = name.ScriptHash();
lock (UserDataLookup)
{
if (UserDataLookup.ContainsKey(type)) if (UserDataLookup.ContainsKey(type))
return; return;
RegisterUserDataType(hash); RegisterUserDataType(hash);
UserDataLookup.Add(type, hash); var ud = new UserData(hash);
UserDataLookup.TryAdd(type, ud);
ReverseLookup.TryAdd(hash, ud);
}
var values = Enum.GetValues(type); var values = Enum.GetValues(type);
foreach (var value in values) foreach (var value in values)
{ {
var getter = new GetterDelegate(ptr => EvalValueCreator.CreateIntegerEvalValue(Convert.ToInt64(value))); var getter = new UserDataField.GetterDelegate(ptr => EvalValueCreator.CreateIntegerEvalValue(Convert.ToInt64(value)));
var fieldData = new UserDataField(getter, null);
var valueName = Enum.GetName(type, value); var valueName = Enum.GetName(type, value);
var fieldName = valueName.ScriptHash(); var fieldName = valueName.ScriptHash();
var t = ScriptType.ScriptType.CreateNumericScriptType(true, false); var t = ScriptType.ScriptType.CreateNumericScriptType(true, false);
RegisterUserDataField(hash, fieldName, RegisterUserDataField(hash, fieldName,
CreateUserDataField(t, Marshal.GetFunctionPointerForDelegate(getter), IntPtr.Zero)); CreateUserDataField(t, Marshal.GetFunctionPointerForDelegate(getter), IntPtr.Zero));
ReverseLookup[hash].Fields[fieldName] = fieldData;
} }
} }
private static void RegisterField(FieldInfo field, uint typeHash) private static void RegisterField(FieldInfo field, uint typeHash)
{ {
var getter = new GetterDelegate(ptr =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var value = field.GetValue(obj);
return EvalValueCreator.CreateValue(value).GetPointer();
});
var setterPtr = IntPtr.Zero;
if (!field.IsInitOnly)
{
var setter = new SetterDelegate((ptr, val) =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var evalValue = Convert.ChangeType(new EvalValue(val).GetObjectValue(), field.FieldType);
field.SetValue(obj, evalValue);
});
setterPtr = Marshal.GetFunctionPointerForDelegate(setter);
}
var scriptType = ScriptType.ScriptType.GetScriptType(field.FieldType); var scriptType = ScriptType.ScriptType.GetScriptType(field.FieldType);
if (!scriptType.HasValue) if (!scriptType.HasValue)
return; return;
var userDataField = CreateUserDataField(scriptType.Value, Marshal.GetFunctionPointerForDelegate(getter), var fieldData = new UserDataField(field);
setterPtr); var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
var fieldName = field.Name.ScriptHash(); var fieldName = field.Name.ScriptHash();
RegisterUserDataField(typeHash, fieldName, userDataField); RegisterUserDataField(typeHash, fieldName, userDataField);
ReverseLookup[typeHash].Fields[fieldName] = fieldData;
} }
private static void RegisterProperty(PropertyInfo property, uint typeHash) private static void RegisterProperty(PropertyInfo property, uint typeHash)
{ {
var getterPtr = IntPtr.Zero;
if (property.GetGetMethod(false) != null)
{
var getter = new GetterDelegate(ptr =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var value = property.GetValue(obj);
return EvalValueCreator.CreateValue(value).GetPointer();
});
getterPtr = Marshal.GetFunctionPointerForDelegate(getter);
}
var setterPtr = IntPtr.Zero;
if (property.GetSetMethod(false) != null)
{
var setter = new SetterDelegate((ptr, val) =>
{
var obj = GCHandle.FromIntPtr(ptr).Target;
var evalValue = Convert.ChangeType(new EvalValue(val).GetObjectValue(), property.PropertyType);
property.SetValue(obj, evalValue);
});
setterPtr = Marshal.GetFunctionPointerForDelegate(setter);
}
var scriptType = ScriptType.ScriptType.GetScriptType(property.PropertyType); var scriptType = ScriptType.ScriptType.GetScriptType(property.PropertyType);
if (!scriptType.HasValue) if (!scriptType.HasValue)
return; return;
var userDataField = CreateUserDataField(scriptType.Value, getterPtr, setterPtr); var fieldData = new UserDataField(property);
var userDataField = CreateUserDataField(scriptType.Value, fieldData.GetGetterPointer(),
fieldData.GetSetterPointer());
var fieldName = property.Name.ScriptHash(); var fieldName = property.Name.ScriptHash();
RegisterUserDataField(typeHash, fieldName, userDataField); RegisterUserDataField(typeHash, fieldName, userDataField);
ReverseLookup[typeHash].Fields[fieldName] = fieldData;
} }
private static void RegisterFunction(MethodInfo method, uint typeHash) private static void RegisterFunction(MethodInfo method, uint typeHash)
{ {
var parameterTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
var getter = new GetterDelegate(ptr =>
{
var func = new CallerDelegate((parent, scriptOptions, parameters, size) =>
{
var evaluatedParameters = new object[size];
for (var i = 0; i < size; i++)
{
var eval = new EvalValue(parameters[i]);
var val = eval.GetObjectValue();
var convertedType = Convert.ChangeType(val, parameterTypes[i]);
evaluatedParameters[i] = convertedType;
}
var parentObj = GCHandle.FromIntPtr(parent).Target;
var result = method.Invoke(parentObj, evaluatedParameters);
return EvalValueCreator.CreateValue(result).GetPointer();
});
var funcPtr = Marshal.GetFunctionPointerForDelegate(func);
return EvalValueCreator.FunctionEvalValue(funcPtr, ptr).GetPointer();
});
var getterPtr = Marshal.GetFunctionPointerForDelegate(getter);
var type = ScriptType.ScriptType.GetFunctionScriptType(method); var type = ScriptType.ScriptType.GetFunctionScriptType(method);
if (type == IntPtr.Zero || !type.HasValue) if (type == IntPtr.Zero || !type.HasValue)
return; return;
var userDataField = CreateUserDataField(type.Value, getterPtr, IntPtr.Zero); var fieldData = new UserDataField(method);
var userDataField = CreateUserDataField(type.Value, fieldData.GetGetterPointer(), IntPtr.Zero);
var fieldName = method.Name.ScriptHash(); var fieldName = method.Name.ScriptHash();
RegisterUserDataField(typeHash, fieldName, userDataField); RegisterUserDataField(typeHash, fieldName, userDataField);
ReverseLookup[typeHash].Fields[fieldName] = fieldData;
} }
public static uint GetTypeId(Type t) public static uint GetTypeId(Type t)
{ {
return UserDataLookup[t]; if (UserDataLookup.TryGetValue(t, out var i))
return i.Id;
var attr = t.GetCustomAttribute<PorygonUserdataAttribute>();
if (attr == null)
return 0;
var id = attr.Identifier.ScriptHash();
if (ReverseLookup.ContainsKey(id))
return id;
return 0;
} }
public static bool IsTypeRegistered(Type t) public static bool IsTypeRegistered(Type t)
{ {
return UserDataLookup.ContainsKey(t); if (UserDataLookup.ContainsKey(t)) return true;
var attr = t.GetCustomAttribute<PorygonUserdataAttribute>();
if (attr == null)
return false;
var id = attr.Identifier.ScriptHash();
return ReverseLookup.ContainsKey(id);
} }
public static int GetUserDataFieldCount(Type t) public static int GetUserDataFieldCount(Type t)

Binary file not shown.