Support deep cloning for BattleRandom

This commit is contained in:
2024-12-29 15:00:15 +01:00
parent 40803f0269
commit 9bdd584b54
3 changed files with 64 additions and 15 deletions

View File

@@ -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();
}