291 lines
12 KiB
C#
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);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|