Support deep cloning for BattleRandom
This commit is contained in:
@@ -2,6 +2,7 @@ using System.Collections;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Pcg;
|
||||
|
||||
namespace PkmnLib.Static.Utils;
|
||||
|
||||
@@ -25,11 +26,16 @@ public static class DeepCloneHandler
|
||||
/// references.
|
||||
/// </summary>
|
||||
public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null) where T : IDeepCloneable
|
||||
{
|
||||
return (T)DeepClone((object?)obj, objects)!;
|
||||
}
|
||||
|
||||
private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null)
|
||||
{
|
||||
if (obj == null)
|
||||
return default!;
|
||||
return null;
|
||||
if (objects != null && objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value))
|
||||
return (T)value;
|
||||
return value;
|
||||
|
||||
var type = obj.GetType();
|
||||
// We use GetUninitializedObject to create an object without calling the constructor. This is necessary to prevent
|
||||
@@ -52,24 +58,30 @@ public static class DeepCloneHandler
|
||||
setter.Invoke(newObj, cloned);
|
||||
}
|
||||
|
||||
return (T)newObj;
|
||||
return newObj;
|
||||
}
|
||||
|
||||
private static object DeepCloneInternal(object? obj, Type type, Dictionary<(Type, int), object> objects)
|
||||
private static readonly HashSet<Type> ExternalDeepCloneTypes = new()
|
||||
{
|
||||
typeof(PcgRandom),
|
||||
typeof(Pcg32Single),
|
||||
};
|
||||
|
||||
private static object? DeepCloneInternal(object? obj, Type type, Dictionary<(Type, int), object> objects)
|
||||
{
|
||||
if (obj == null)
|
||||
return null!;
|
||||
return null;
|
||||
// If the object is a value type or a string, we can just return it.
|
||||
if (type.IsValueType || type == typeof(string))
|
||||
return obj;
|
||||
|
||||
// If the object is marked as deep cloneable, we will clone it.
|
||||
if (type.GetInterface(nameof(IDeepCloneable)) != null)
|
||||
if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type))
|
||||
{
|
||||
// If the object is already cloned, we return the cloned object to prevent infinite loops and invalid references.
|
||||
if (objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value))
|
||||
return value;
|
||||
var o = DeepClone((IDeepCloneable)obj, objects);
|
||||
var o = DeepClone(obj, objects);
|
||||
return o;
|
||||
}
|
||||
|
||||
@@ -104,7 +116,7 @@ public static class DeepCloneHandler
|
||||
var newDictionary = (IDictionary)Activator.CreateInstance(type);
|
||||
foreach (DictionaryEntry entry in dictionary)
|
||||
newDictionary.Add(
|
||||
DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects),
|
||||
DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!,
|
||||
DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects));
|
||||
return newDictionary;
|
||||
}
|
||||
@@ -121,7 +133,7 @@ public static class DeepCloneHandler
|
||||
/// This method is thread safe, and will only create the expressions once for each type. It returns compiled expressions for
|
||||
/// each field in the type, so that we can get high performance deep cloning.
|
||||
/// </remarks>
|
||||
private static (Func<object, object> getter, Action<object, object> setter)[]
|
||||
private static (Func<object, object?> getter, Action<object, object?> setter)[]
|
||||
GetDeepCloneExpressions(Type type)
|
||||
{
|
||||
// We use a lock here to prevent multiple threads from trying to create the expressions at the same time.
|
||||
@@ -131,7 +143,7 @@ public static class DeepCloneHandler
|
||||
return value;
|
||||
|
||||
var fields = GetFields(type).ToArray();
|
||||
var expressions = new (Func<object, object> getter, Action<object, object> setter)[fields.Length];
|
||||
var expressions = new (Func<object, object?> getter, Action<object, object?> setter)[fields.Length];
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
var field = fields[i];
|
||||
@@ -152,7 +164,7 @@ public static class DeepCloneHandler
|
||||
// does this. This is not ideal as it is slower, but works for now.
|
||||
if (field.IsInitOnly)
|
||||
{
|
||||
void Setter(object instance, object v)
|
||||
void Setter(object instance, object? v)
|
||||
{
|
||||
field.SetValue(instance, v);
|
||||
}
|
||||
@@ -169,7 +181,7 @@ public static class DeepCloneHandler
|
||||
// 3. Assign the value to the field.
|
||||
var assign = Expression.Assign(Expression.Field(cast, field), valueCast);
|
||||
// 4. Wrap the assign in a lambda so we can compile it.
|
||||
var set = Expression.Lambda<Action<object, object>>(assign, obj, valueLambda);
|
||||
var set = Expression.Lambda<Action<object, object?>>(assign, obj, valueLambda);
|
||||
expressions[i] = (getter: lambda.Compile(), setter: set.Compile());
|
||||
}
|
||||
}
|
||||
@@ -191,6 +203,6 @@ public static class DeepCloneHandler
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, (Func<object, object> getter, Action<object, object> setter)[]>
|
||||
private static readonly Dictionary<Type, (Func<object, object?> getter, Action<object, object?> setter)[]>
|
||||
DeepCloneExpressions = new();
|
||||
}
|
||||
Reference in New Issue
Block a user