2025-05-09 15:40:34 +08:00

291 lines
12 KiB
C#

// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
//#define ASSERT_CLONE_TYPES
using System.Collections.Generic;
using UnityEngine;
namespace Animancer
{
/// <summary>An object that can be cloned.</summary>
/// <remarks>See <see cref="Clone(CloneContext)"/> for example usage.</remarks>
/// https://kybernetik.com.au/animancer/api/Animancer/ICloneable_1
public interface ICloneable<out T>
{
/************************************************************************************************************************/
/// <summary>Creates a new object with the same type and values this.</summary>
///
/// <remarks>
/// Calling this method directly is not generally recommended.
/// Use <see cref="CloneableExtensions.Clone{T}(ICloneable{T})"/> if you don't have a `context`
/// or <see cref="CloneContext.GetOrCreateClone{T}(ICloneable{T})"/> if you do have one.
/// <para></para>
/// <strong>Example:</strong><code>
/// 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.
/// }
/// }
/// </code></remarks>
T Clone(CloneContext context);
/************************************************************************************************************************/
}
/// <summary>Extension methods for <see cref="ICloneable{T}"/>.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer/CloneableExtensions
public static partial class CloneableExtensions
{
/************************************************************************************************************************/
/// <summary>
/// Calls <see cref="ICloneable{T}.Clone"/> using a <see cref="CloneContext"/> from the
/// <see cref="CloneContext.Pool"/> and casts the result.
/// </summary>
/// <remarks>
/// Returns <c>null</c> if the `original` is <c>null</c>.
/// <para></para>
/// Use <see cref="CloneContext.GetOrCreateClone{T}(ICloneable{T})"/>
/// if you already have a <see cref="CloneContext"/>.
/// </remarks>
public static T Clone<T>(this ICloneable<T> original)
{
if (original == null)
return default;
var context = CloneContext.Pool.Instance.Acquire();
var clone = original.Clone(context);
CloneContext.Pool.Instance.Release(context);
return clone;
}
/************************************************************************************************************************/
/// <summary>[Assert-Conditional] Asserts that the `clone` has the same type as the `original`.</summary>
[System.Diagnostics.Conditional(Strings.Assertions)]
internal static void AssertCloneType<T>(this ICloneable<T> 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
}
/************************************************************************************************************************/
}
/// <summary>A dictionary which maps objects to their copies.</summary>
/// <remarks>
/// 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.
/// </remarks>
public class CloneContext : Dictionary<object, object>
{
/************************************************************************************************************************/
/// <summary>Will the <see cref="IUpdatable"/>s be cloned as part of the current operation?</summary>
/// <remarks>
/// This is used to prevent <see cref="AnimancerNode"/>s from cloning their <see cref="FadeGroup"/>s
/// individually while cloning a whole <see cref="AnimancerGraph"/> because it will clone the whole groups
/// after cloning all the nodes.
/// </remarks>
public bool WillCloneUpdatables { get; set; }
/************************************************************************************************************************/
#region Pooling
/************************************************************************************************************************/
/// <summary>An <see cref="ObjectPool{T}"/> for <see cref="CloneContext"/>.</summary>
/// https://kybernetik.com.au/animancer/api/Animancer/Pool
public class Pool : ObjectPool<CloneContext>
{
/************************************************************************************************************************/
/// <summary>Singleton.</summary>
public static Pool Instance = new();
/************************************************************************************************************************/
/// <inheritdoc/>
protected override CloneContext New()
=> new();
/************************************************************************************************************************/
/// <inheritdoc/>
public override CloneContext Acquire()
{
var context = base.Acquire();
CollectionPool<KeyValuePair<object, object>, CloneContext>.AssertEmpty(context);
return context;
}
/************************************************************************************************************************/
/// <inheritdoc/>
public override void Release(CloneContext context)
{
context.Clear();
base.Release(context);
}
/************************************************************************************************************************/
}
/************************************************************************************************************************/
#endregion
/************************************************************************************************************************/
/// <summary>
/// Returns the value registered using `original` as its key if there is one.
/// Otherwise, calls <see cref="CloneableExtensions.Clone"/>, adds the clone to this dictionary, and returns it.
/// </summary>
public T GetOrCreateClone<T>(ICloneable<T> original)
{
if (original == null)
return default;
if (TryGetValue(original, out var value))
return (T)value;
return Clone(original);
}
/************************************************************************************************************************/
/// <summary>
/// Returns the value registered using `original` as its key if there is one.
/// Otherwise, if the `original` is <see cref="ICloneable{T}"/> it calls <see cref="CloneableExtensions.Clone"/>,
/// adds the clone to this dictionary, and returns it.
/// Otherwise, just returns the `original`.
/// </summary>
/// <remarks>
/// If <typeparamref name="T"/> is <see cref="ICloneable{T}"/>,
/// use <see cref="GetOrCreateClone{T}(ICloneable{T})"/> instead.
/// </remarks>
public T GetOrCreateCloneOrOriginal<T>(T original)
{
TryGetOrCreateCloneOrOriginal(original, out original);
return original;
}
/// <summary>
/// Returns <c>true</c> if there is a `clone` registered for the `original`.
/// Otherwise, if the `original` is <see cref="ICloneable{T}"/> it calls <see cref="CloneableExtensions.Clone"/>,
/// adds the `clone` to this dictionary, and returns <c>true</c>.
/// Otherwise, outputs the `original` as the `clone` and returns <c>false</c>.
/// </summary>
/// <remarks>Outputs <c>null</c> and returns <c>true</c> if the `original` is <c>null</c>.</remarks>
public bool TryGetOrCreateCloneOrOriginal<T>(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<T> cloneable)
{
clone = Clone(cloneable);
return true;
}
clone = original;
return false;
}
/************************************************************************************************************************/
/// <summary>Calls <see cref="ICloneable{T}.Clone"/> and registers the clone.</summary>
/// <exception cref="System.ArgumentException">A clone is already registered for the `original`.</exception>
public T Clone<T>(ICloneable<T> original)
{
var clone = original.Clone(this);
if (clone != null)
{
original.AssertCloneType(clone);
Add(original, clone);
}
return clone;
}
/************************************************************************************************************************/
/// <summary>Returns the clone of the `original` if one was registered. Otherwise, throws.</summary>
/// <exception cref="KeyNotFoundException">No clone of the `original` is registered.</exception>
public T GetClone<T>(T original)
=> (T)this[original];
/************************************************************************************************************************/
/// <summary>Returns the clone of the `original` if one is registered. Otherwise, returns the `original`.</summary>
public T GetCloneOrOriginal<T>(T original)
=> original != null && TryGetValue(original, out var value)
? (T)value
: original;
/************************************************************************************************************************/
/// <summary>Replaces the `item` with its clone and returns true if one is registered.</summary>
public bool TryGetClone<T>(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;
}
/// <summary>Calls <see cref="Dictionary{TKey, TValue}.TryGetValue(TKey, out TValue)"/> and casts the result.</summary>
public bool TryGetClone<T>(T original, out T clone)
{
clone = original;
return TryGetClone(ref clone);
}
/************************************************************************************************************************/
}
}