2024-12-29 12:51:59 +00:00
|
|
|
using System.Collections;
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
using System.Reflection;
|
|
|
|
using System.Runtime.Serialization;
|
2024-12-29 14:00:15 +00:00
|
|
|
using Pcg;
|
2024-12-29 12:51:59 +00:00
|
|
|
|
|
|
|
namespace PkmnLib.Static.Utils;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Marks the type as deep cloneable. This means that when a deep clone is made, the object will be cloned recursively.
|
|
|
|
/// Any reference types that are not marked as deep cloneable will be copied by reference, and not cloned.
|
|
|
|
/// </summary>
|
|
|
|
public interface IDeepCloneable;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Handles deep cloning of objects.
|
|
|
|
/// </summary>
|
|
|
|
public static class DeepCloneHandler
|
|
|
|
{
|
|
|
|
/// <summary>
|
|
|
|
/// This will clone an object including all fields and properties, and recursively clone any reference types
|
|
|
|
/// that are marked as deep cloneable. Value types will be copied by value, and reference types that are not marked as
|
|
|
|
/// deep cloneable will be copied by reference.
|
|
|
|
///
|
|
|
|
/// Recursive references will be handled correctly, and will only be cloned once, to prevent infinite loops and invalid
|
|
|
|
/// references.
|
|
|
|
/// </summary>
|
2025-03-02 16:19:57 +00:00
|
|
|
public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null)
|
|
|
|
where T : IDeepCloneable => (T)DeepClone((object?)obj, objects)!;
|
|
|
|
|
2024-12-29 14:00:15 +00:00
|
|
|
private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null)
|
2024-12-29 12:51:59 +00:00
|
|
|
{
|
|
|
|
if (obj == null)
|
2024-12-29 14:00:15 +00:00
|
|
|
return null;
|
2024-12-29 12:51:59 +00:00
|
|
|
if (objects != null && objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value))
|
2024-12-29 14:00:15 +00:00
|
|
|
return value;
|
2024-12-29 12:51:59 +00:00
|
|
|
|
|
|
|
var type = obj.GetType();
|
|
|
|
// We use GetUninitializedObject to create an object without calling the constructor. This is necessary to prevent
|
|
|
|
// side effects from the constructor, and to not require a parameterless constructor.
|
|
|
|
var newObj = FormatterServices.GetUninitializedObject(type)!;
|
2025-03-02 16:19:57 +00:00
|
|
|
|
2024-12-29 12:51:59 +00:00
|
|
|
// If the objects dictionary is null, we create a new one. We use this dictionary to keep track of objects that have
|
|
|
|
// already been cloned, so we can re-use them instead of cloning them again. This is necessary to prevent infinite
|
|
|
|
// loops and invalid references.
|
|
|
|
objects ??= new Dictionary<(Type, int), object>();
|
|
|
|
objects.Add((obj.GetType(), obj.GetHashCode()), newObj);
|
|
|
|
|
|
|
|
var expressions = GetDeepCloneExpressions(type);
|
|
|
|
foreach (var (getter, setter) in expressions)
|
|
|
|
{
|
|
|
|
var v = getter.Invoke(obj);
|
|
|
|
if (v == null)
|
|
|
|
continue;
|
|
|
|
var cloned = DeepCloneInternal(v, v.GetType(), objects);
|
|
|
|
setter.Invoke(newObj, cloned);
|
|
|
|
}
|
|
|
|
|
2024-12-29 14:00:15 +00:00
|
|
|
return newObj;
|
2024-12-29 12:51:59 +00:00
|
|
|
}
|
|
|
|
|
2024-12-29 14:00:15 +00:00
|
|
|
private static readonly HashSet<Type> ExternalDeepCloneTypes = new()
|
|
|
|
{
|
|
|
|
typeof(PcgRandom),
|
|
|
|
typeof(Pcg32Single),
|
|
|
|
};
|
|
|
|
|
|
|
|
private static object? DeepCloneInternal(object? obj, Type type, Dictionary<(Type, int), object> objects)
|
2024-12-29 12:51:59 +00:00
|
|
|
{
|
|
|
|
if (obj == null)
|
2024-12-29 14:00:15 +00:00
|
|
|
return null;
|
2024-12-29 12:51:59 +00:00
|
|
|
// If the object is a value type or a string, we can just return it.
|
|
|
|
if (type.IsValueType || type == typeof(string))
|
|
|
|
return obj;
|
2025-03-02 16:19:57 +00:00
|
|
|
|
2024-12-29 12:51:59 +00:00
|
|
|
// If the object is marked as deep cloneable, we will clone it.
|
2024-12-29 14:00:15 +00:00
|
|
|
if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type))
|
2024-12-29 12:51:59 +00:00
|
|
|
{
|
|
|
|
// 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;
|
2024-12-29 14:00:15 +00:00
|
|
|
var o = DeepClone(obj, objects);
|
2024-12-29 12:51:59 +00:00
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.IsArray)
|
|
|
|
{
|
|
|
|
// ReSharper disable once SuspiciousTypeConversion.Global
|
|
|
|
var array = (Array)obj;
|
|
|
|
var newArray = Array.CreateInstance(type.GetElementType()!, array.Length);
|
|
|
|
for (var i = 0; i < array.Length; i++)
|
2025-03-02 16:19:57 +00:00
|
|
|
{
|
|
|
|
newArray.SetValue(DeepCloneInternal(array.GetValue(i), type.GetElementType()!, objects), i);
|
|
|
|
}
|
2024-12-29 12:51:59 +00:00
|
|
|
return newArray;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type.IsGenericType)
|
|
|
|
{
|
|
|
|
var genericType = type.GetGenericTypeDefinition();
|
|
|
|
if (genericType == typeof(List<>))
|
|
|
|
{
|
|
|
|
// ReSharper disable once SuspiciousTypeConversion.Global
|
|
|
|
var list = (IList)obj;
|
|
|
|
var newList = (IList)Activator.CreateInstance(type);
|
|
|
|
foreach (var item in list)
|
|
|
|
newList.Add(DeepCloneInternal(item, type.GetGenericArguments()[0], objects));
|
|
|
|
return newList;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (genericType == typeof(Dictionary<,>))
|
|
|
|
{
|
|
|
|
// ReSharper disable once SuspiciousTypeConversion.Global
|
|
|
|
var dictionary = (IDictionary)obj;
|
|
|
|
var newDictionary = (IDictionary)Activator.CreateInstance(type);
|
|
|
|
foreach (DictionaryEntry entry in dictionary)
|
2025-03-02 16:19:57 +00:00
|
|
|
{
|
|
|
|
newDictionary.Add(DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!,
|
2024-12-29 12:51:59 +00:00
|
|
|
DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects));
|
2025-03-02 16:19:57 +00:00
|
|
|
}
|
2024-12-29 12:51:59 +00:00
|
|
|
return newDictionary;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Helper method to get the compiled expressions for deep cloning a type. This will create a getter and setter for each field
|
|
|
|
/// in the type, which can be used to clone the object.
|
|
|
|
/// </summary>
|
|
|
|
/// <remarks>
|
|
|
|
/// 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>
|
2025-03-02 16:19:57 +00:00
|
|
|
private static (Func<object, object?> getter, Action<object, object?> setter)[] GetDeepCloneExpressions(Type type)
|
2024-12-29 12:51:59 +00:00
|
|
|
{
|
|
|
|
// We use a lock here to prevent multiple threads from trying to create the expressions at the same time.
|
|
|
|
lock (DeepCloneExpressions)
|
|
|
|
{
|
|
|
|
if (DeepCloneExpressions.TryGetValue(type, out var value))
|
|
|
|
return value;
|
|
|
|
|
|
|
|
var fields = GetFields(type).ToArray();
|
2024-12-29 14:00:15 +00:00
|
|
|
var expressions = new (Func<object, object?> getter, Action<object, object?> setter)[fields.Length];
|
2024-12-29 12:51:59 +00:00
|
|
|
for (var i = 0; i < fields.Length; i++)
|
|
|
|
{
|
|
|
|
var field = fields[i];
|
|
|
|
// Create a compiled getter for the field.
|
|
|
|
// 1. Set up the instance parameter.
|
|
|
|
var obj = Expression.Parameter(typeof(object));
|
|
|
|
// 2. Cast the instance (which we want to pass as an object) to the correct type.
|
|
|
|
var cast = Expression.Convert(obj, type);
|
|
|
|
// 3. Get the field value from the instance.
|
|
|
|
var get = Expression.Field(cast, field);
|
|
|
|
// 4. Cast the field value to an object.
|
|
|
|
var getCasted = Expression.Convert(get, typeof(object));
|
|
|
|
// 5. Wrap the cast in a lambda so we can compile it.
|
|
|
|
var lambda = Expression.Lambda<Func<object, object>>(getCasted, obj);
|
|
|
|
|
|
|
|
// This is a slight hack to allow us to set readonly fields. We can't set them directly through expression trees,
|
|
|
|
// as Expression.Assign checks for this. We can however set them through reflection, so we create a setter that
|
|
|
|
// does this. This is not ideal as it is slower, but works for now.
|
|
|
|
if (field.IsInitOnly)
|
|
|
|
{
|
2024-12-29 14:00:15 +00:00
|
|
|
void Setter(object instance, object? v)
|
2024-12-29 12:51:59 +00:00
|
|
|
{
|
|
|
|
field.SetValue(instance, v);
|
|
|
|
}
|
|
|
|
|
|
|
|
expressions[i] = (getter: lambda.Compile(), Setter);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Create a compiled setter for the field.
|
|
|
|
// 1. Set up the parameter for the value.
|
|
|
|
var valueLambda = Expression.Parameter(typeof(object));
|
|
|
|
// 2. Cast the value to the correct type.
|
|
|
|
var valueCast = Expression.Convert(valueLambda, field.FieldType);
|
|
|
|
// 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.
|
2024-12-29 14:00:15 +00:00
|
|
|
var set = Expression.Lambda<Action<object, object?>>(assign, obj, valueLambda);
|
2024-12-29 12:51:59 +00:00
|
|
|
expressions[i] = (getter: lambda.Compile(), setter: set.Compile());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DeepCloneExpressions.Add(type, expressions);
|
|
|
|
return expressions;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static IEnumerable<FieldInfo> GetFields(Type type)
|
|
|
|
{
|
|
|
|
IEnumerable<FieldInfo> fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance |
|
|
|
|
BindingFlags.NonPublic | BindingFlags.DeclaredOnly);
|
|
|
|
// Note that we do the above with DeclaredOnly, while we do want to get the fields from the base type.
|
|
|
|
// This is because even without DeclaredOnly, we will only get public fields from our base type. As we want
|
|
|
|
// to get all fields, we need to do this recursively.
|
|
|
|
if (type.BaseType != null)
|
|
|
|
fields = fields.Concat(GetFields(type.BaseType));
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
|
2024-12-29 14:00:15 +00:00
|
|
|
private static readonly Dictionary<Type, (Func<object, object?> getter, Action<object, object?> setter)[]>
|
2024-12-29 12:51:59 +00:00
|
|
|
DeepCloneExpressions = new();
|
|
|
|
}
|