using System.Collections;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.Serialization;
using Pcg;
namespace PkmnLib.Static.Utils;
///
/// 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.
///
public interface IDeepCloneable;
///
/// Handles deep cloning of objects.
///
public static class DeepCloneHandler
{
///
/// 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.
///
public static T DeepClone(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 null;
if (objects != null && objects.TryGetValue((obj.GetType(), obj.GetHashCode()), out var value))
return value;
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)!;
// 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);
}
return newObj;
}
private static readonly HashSet ExternalDeepCloneTypes = new()
{
typeof(PcgRandom),
typeof(Pcg32Single),
};
private static object? DeepCloneInternal(object? obj, Type type, Dictionary<(Type, int), object> objects)
{
if (obj == 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 || 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(obj, objects);
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++)
newArray.SetValue(DeepCloneInternal(array.GetValue(i), type.GetElementType()!, objects),
i);
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)
newDictionary.Add(
DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!,
DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects));
return newDictionary;
}
}
return obj;
}
///
/// 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.
///
///
/// 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.
///
private static (Func