// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // //#define ASSERT_CLONE_TYPES using System.Collections.Generic; using UnityEngine; namespace Animancer { /// An object that can be cloned. /// See for example usage. /// https://kybernetik.com.au/animancer/api/Animancer/ICloneable_1 public interface ICloneable { /************************************************************************************************************************/ /// Creates a new object with the same type and values this. /// /// /// Calling this method directly is not generally recommended. /// Use if you don't have a `context` /// or if you do have one. /// /// Example: /// class BaseClass : ICloneable /// { /// // Explicit implementation so that the recommended methods will be used instead. /// object ICloneable.Clone(CloneContext context) /// { /// var clone = (BaseClass)MemberwiseClone(); /// clone.CopyFrom(this, context); /// return clone; /// } /// /// // Protected method which should only be called by Clone. /// protected virtual void InitializeClone(CloneContext context) /// { /// // Alter any necessary BaseClass fields according to the context. /// } /// } /// /// class DerivedClass : BaseClass /// { /// protected override void InitializeClone(CloneContext context) /// { /// base.CopyFrom(copyFrom, context); /// /// var derived = (DerivedClass)copyFrom; /// // Alter any necessary DerivedClass fields according to the context. /// } /// } /// T Clone(CloneContext context); /************************************************************************************************************************/ } /// Extension methods for . /// https://kybernetik.com.au/animancer/api/Animancer/CloneableExtensions public static partial class CloneableExtensions { /************************************************************************************************************************/ /// /// Calls using a from the /// and casts the result. /// /// /// Returns null if the `original` is null. /// /// Use /// if you already have a . /// public static T Clone(this ICloneable original) { if (original == null) return default; var context = CloneContext.Pool.Instance.Acquire(); var clone = original.Clone(context); CloneContext.Pool.Instance.Release(context); return clone; } /************************************************************************************************************************/ /// [Assert-Conditional] Asserts that the `clone` has the same type as the `original`. [System.Diagnostics.Conditional(Strings.Assertions)] internal static void AssertCloneType(this ICloneable original, object clone) { #if UNITY_ASSERTIONS var cloneType = clone.GetType(); var originalType = original.GetType(); if (cloneType != originalType) Debug.LogError($"Cloned object type ({cloneType.FullName}" + $" doesn't match original {originalType.FullName}." + $"\n• Original: {original}" + $"\n• Clone: {clone}"); #endif } /************************************************************************************************************************/ } /// A dictionary which maps objects to their copies. /// /// This class is used to clone complex object trees with potentially interconnected references so that /// references to original objects can be replaced with references to their equivalent clones. /// public class CloneContext : Dictionary { /************************************************************************************************************************/ /// Will the s be cloned as part of the current operation? /// /// This is used to prevent s from cloning their s /// individually while cloning a whole because it will clone the whole groups /// after cloning all the nodes. /// public bool WillCloneUpdatables { get; set; } /************************************************************************************************************************/ #region Pooling /************************************************************************************************************************/ /// An for . /// https://kybernetik.com.au/animancer/api/Animancer/Pool public class Pool : ObjectPool { /************************************************************************************************************************/ /// Singleton. public static Pool Instance = new(); /************************************************************************************************************************/ /// protected override CloneContext New() => new(); /************************************************************************************************************************/ /// public override CloneContext Acquire() { var context = base.Acquire(); CollectionPool, CloneContext>.AssertEmpty(context); return context; } /************************************************************************************************************************/ /// public override void Release(CloneContext context) { context.Clear(); base.Release(context); } /************************************************************************************************************************/ } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// /// Returns the value registered using `original` as its key if there is one. /// Otherwise, calls , adds the clone to this dictionary, and returns it. /// public T GetOrCreateClone(ICloneable original) { if (original == null) return default; if (TryGetValue(original, out var value)) return (T)value; return Clone(original); } /************************************************************************************************************************/ /// /// Returns the value registered using `original` as its key if there is one. /// Otherwise, if the `original` is it calls , /// adds the clone to this dictionary, and returns it. /// Otherwise, just returns the `original`. /// /// /// If is , /// use instead. /// public T GetOrCreateCloneOrOriginal(T original) { TryGetOrCreateCloneOrOriginal(original, out original); return original; } /// /// Returns true if there is a `clone` registered for the `original`. /// Otherwise, if the `original` is it calls , /// adds the `clone` to this dictionary, and returns true. /// Otherwise, outputs the `original` as the `clone` and returns false. /// /// Outputs null and returns true if the `original` is null. public bool TryGetOrCreateCloneOrOriginal(T original, out T clone) { if (original == null) { clone = default; return true; } if (TryGetValue(original, out var value)) { clone = (T)value; return true; } if (original is ICloneable cloneable) { clone = Clone(cloneable); return true; } clone = original; return false; } /************************************************************************************************************************/ /// Calls and registers the clone. /// A clone is already registered for the `original`. public T Clone(ICloneable original) { var clone = original.Clone(this); if (clone != null) { original.AssertCloneType(clone); Add(original, clone); } return clone; } /************************************************************************************************************************/ /// Returns the clone of the `original` if one was registered. Otherwise, throws. /// No clone of the `original` is registered. public T GetClone(T original) => (T)this[original]; /************************************************************************************************************************/ /// Returns the clone of the `original` if one is registered. Otherwise, returns the `original`. public T GetCloneOrOriginal(T original) => original != null && TryGetValue(original, out var value) ? (T)value : original; /************************************************************************************************************************/ /// Replaces the `item` with its clone and returns true if one is registered. public bool TryGetClone(ref T item) { if (item == null) return false; if (!TryGetValue(item, out var value) || value is not T valueT) { item = default; return false; } item = valueT; return true; } /// Calls and casts the result. public bool TryGetClone(T original, out T clone) { clone = original; return TryGetClone(ref clone); } /************************************************************************************************************************/ } }