335 lines
16 KiB
C#
335 lines
16 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Animancer
|
|
{
|
|
/// <summary>A dictionary which maps event names to callbacks.</summary>
|
|
/// <remarks>
|
|
/// <strong>Documentation:</strong>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/manual/events/animancer">
|
|
/// Animancer Events</see>
|
|
/// </remarks>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer/NamedEventDictionary
|
|
public class NamedEventDictionary : IDictionary<StringReference, Action>
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
private readonly Dictionary<StringReference, Action>
|
|
Dictionary = new();
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The number of items in this dictionary.</summary>
|
|
public int Count
|
|
=> Dictionary.Count;
|
|
|
|
/************************************************************************************************************************/
|
|
#region Access
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Accesses a callback in this dictionary.</summary>
|
|
public Action this[StringReference name]
|
|
{
|
|
get => Dictionary[name];
|
|
set
|
|
{
|
|
AssertNotEndEvent(name);
|
|
Dictionary[name] = value;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns the callback registered using the `name`.</summary>
|
|
/// <remarks>Returns <c>null</c> if nothing was registered.</remarks>
|
|
public Action Get(StringReference name)
|
|
=> Dictionary.Get(name);
|
|
|
|
/// <summary>Registers the callback using the `name`, replacing anything previously registered.</summary>
|
|
public void Set(StringReference name, Action callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
Dictionary[name] = callback;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Are any callbacks registered for the `name`?</summary>
|
|
/// <remarks>To get the registered callbacks at the same time, use <see cref="TryGetValue"/> instead.</remarks>
|
|
public bool ContainsKey(StringReference name)
|
|
=> Dictionary.ContainsKey(name);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Tries to get the `callback` registered with the `name` and returns <c>true</c> if successful.</summary>
|
|
public bool TryGetValue(StringReference name, out Action callback)
|
|
=> Dictionary.TryGetValue(name, out callback);
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Add
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Adds the `callback` to any existing ones registered with the `name`.</summary>
|
|
/// <remarks>
|
|
/// If you want an exception to be thrown if something is already registered with the `name`,
|
|
/// use <see cref="AddNew(StringReference, Action)"/> instead.
|
|
/// </remarks>
|
|
public void AddTo(StringReference name, Action callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
|
|
if (Dictionary.TryGetValue(name, out var existing))
|
|
callback = existing + callback;
|
|
|
|
Dictionary[name] = callback;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
|
|
/// if something was already registered with the same `name`.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/> behaviour,
|
|
/// unlike <see cref="AddTo(StringReference, Action)"/>.
|
|
/// </remarks>
|
|
public void AddNew(StringReference name, Action callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
Dictionary.Add(name, callback);
|
|
}
|
|
|
|
void IDictionary<StringReference, Action>.Add(StringReference name, Action callback)
|
|
=> AddNew(name, callback);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Adds the `callback` to any existing ones registered with the `name`.
|
|
/// <para></para>
|
|
/// It will be invoked using <see cref="AnimancerEvent.GetCurrentParameter{T}"/> to get its parameter.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If you want an exception to be thrown if something is already registered with the `name`,
|
|
/// use <see cref="AddNew{T}(StringReference, Action{T})"/> instead.
|
|
/// <para></para>
|
|
/// If <typeparamref name="T"/> is <see cref="string"/>,
|
|
/// consider using <see cref="AddTo(StringReference, Action{string})"/> instead of this overload.
|
|
/// <para></para>
|
|
/// If you want to later remove the `callback`,
|
|
/// you need to store and remove the returned <see cref="Action"/>.
|
|
/// </remarks>
|
|
public Action AddTo<T>(StringReference name, Action<T> callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
var parametized = AnimancerEvent.Parametize(callback);
|
|
|
|
if (Dictionary.TryGetValue(name, out var existing))
|
|
parametized = existing + parametized;
|
|
|
|
Dictionary[name] = parametized;
|
|
|
|
return parametized;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
|
|
/// if something was already registered with the same `name`.
|
|
/// <para></para>
|
|
/// It will be invoked using <see cref="AnimancerEvent.GetCurrentParameter{T}"/> to get its parameter.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/> behaviour,
|
|
/// unlike <see cref="AddTo{T}(StringReference, Action{T})"/>.
|
|
/// If <typeparamref name="T"/> is <see cref="string"/>,
|
|
/// consider using <see cref="AddTo(StringReference, Action{string})"/> instead of this overload.
|
|
/// <para></para>
|
|
/// If you want to later remove the `callback`,
|
|
/// you need to store and remove the returned <see cref="Action"/>.
|
|
/// </remarks>
|
|
public Action AddNew<T>(StringReference name, Action<T> callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
var parametized = AnimancerEvent.Parametize(callback);
|
|
Dictionary.Add(name, parametized);
|
|
return parametized;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// Adds the `callback` to any existing ones registered with the `name`.
|
|
/// <para></para>
|
|
/// It will be invoked using <see cref="object.ToString"/> on the
|
|
/// <see cref="AnimancerEvent.CurrentParameter"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// If you want an exception to be thrown if something is already registered with the `name`,
|
|
/// use <see cref="AddNew{T}(StringReference, Action{T})"/> instead.
|
|
/// <para></para>
|
|
/// If you want to later remove the `callback`,
|
|
/// you need to store and remove the returned <see cref="Action"/>.
|
|
/// </remarks>
|
|
public Action AddTo(StringReference name, Action<string> callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
var parametized = AnimancerEvent.Parametize(callback);
|
|
|
|
if (Dictionary.TryGetValue(name, out var existing))
|
|
parametized = existing + parametized;
|
|
|
|
Dictionary[name] = parametized;
|
|
return parametized;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers the `callback` with the `name` but throws an <see cref="ArgumentException"/>
|
|
/// if something was already registered with the same `name`.
|
|
/// <para></para>
|
|
/// It will be invoked using <see cref="object.ToString"/> on the
|
|
/// <see cref="AnimancerEvent.CurrentParameter"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This matches the standard <see cref="Dictionary{TKey, TValue}.Add(TKey, TValue)"/>
|
|
/// behaviour, unlike <see cref="AddTo(StringReference, Action{string})"/>.
|
|
/// <para></para>
|
|
/// If you want to later remove the `callback`,
|
|
/// you need to store and remove the returned <see cref="Action"/>.
|
|
/// </remarks>
|
|
public Action AddNew(StringReference name, Action<string> callback)
|
|
{
|
|
AssertNotEndEvent(name);
|
|
var parametized = AnimancerEvent.Parametize(callback);
|
|
Dictionary.Add(name, parametized);
|
|
return parametized;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>[Assert-Conditional]
|
|
/// Throws an <see cref="ArgumentException"/> if the `name` is the <see cref="AnimancerEvent.EndEventName"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In order to minimise the performance cost of End Events when there isn't one,
|
|
/// the <see cref="AnimancerEvent.Dispatcher"/> won't even check the end time
|
|
/// when there is no <see cref="AnimancerEvent.Sequence.OnEnd"/> callback.
|
|
/// <para></para>
|
|
/// That means if a callback was bound to the <see cref="AnimancerEvent.EndEventName"/>
|
|
/// it would be triggered by any state with an <see cref="AnimancerEvent.Sequence.OnEnd"/>
|
|
/// callback, but not by states without one. That would be very counterintuitive so it isn't allowed.
|
|
/// </remarks>
|
|
/// <exception cref="ArgumentException"/>
|
|
[System.Diagnostics.Conditional(Strings.Assertions)]
|
|
public static void AssertNotEndEvent(StringReference name)
|
|
{
|
|
if (name == AnimancerEvent.EndEventName)
|
|
throw new ArgumentException(
|
|
$"Binding event callbacks to the " +
|
|
$"{nameof(AnimancerEvent)}.{nameof(AnimancerEvent.EndEventName)}" +
|
|
$" is not supported for performance optimization reasons. See the documentation of" +
|
|
$" {nameof(NamedEventDictionary)}.{nameof(AssertNotEndEvent)} for more details.",
|
|
nameof(name));
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Remove
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Removes all callbacks registered with the `name`.</summary>
|
|
public bool Remove(StringReference name)
|
|
=> Dictionary.Remove(name);
|
|
|
|
/// <summary>Removes a specific `callback` registered with the `name`.</summary>
|
|
public bool Remove(StringReference name, Action callback)
|
|
{
|
|
if (!Dictionary.TryGetValue(name, out var callbacks))
|
|
return false;
|
|
|
|
if (callbacks == callback)
|
|
Dictionary.Remove(name);
|
|
else
|
|
Dictionary[name] = callbacks - callback;
|
|
|
|
return true;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Removes everything from this dictionary.</summary>
|
|
public void Clear()
|
|
=> Dictionary.Clear();
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Enumeration
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Returns an enumerator to go through every item in this dictionary.</summary>
|
|
public Dictionary<StringReference, Action>.Enumerator GetEnumerator()
|
|
=> Dictionary.GetEnumerator();
|
|
|
|
IEnumerator<KeyValuePair<StringReference, Action>>
|
|
IEnumerable<KeyValuePair<StringReference, Action>>.GetEnumerator()
|
|
=> Dictionary.GetEnumerator();
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
=> Dictionary.GetEnumerator();
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The names in this dictionary.</summary>
|
|
public Dictionary<StringReference, Action>.KeyCollection Keys
|
|
=> Dictionary.Keys;
|
|
|
|
/// <summary>The values in this dictionary.</summary>
|
|
public Dictionary<StringReference, Action>.ValueCollection Values
|
|
=> Dictionary.Values;
|
|
|
|
ICollection<StringReference> IDictionary<StringReference, Action>.Keys
|
|
=> Dictionary.Keys;
|
|
|
|
ICollection<Action> IDictionary<StringReference, Action>.Values
|
|
=> Dictionary.Values;
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
#region Explicit Dictionary Wrappers
|
|
/************************************************************************************************************************/
|
|
|
|
void ICollection<KeyValuePair<StringReference, Action>>
|
|
.Add(KeyValuePair<StringReference, Action> item)
|
|
=> AddTo(item.Key, item.Value);
|
|
|
|
bool ICollection<KeyValuePair<StringReference, Action>>
|
|
.Contains(KeyValuePair<StringReference, Action> item)
|
|
=> ((ICollection<KeyValuePair<StringReference, Action>>)Dictionary).Contains(item);
|
|
|
|
void ICollection<KeyValuePair<StringReference, Action>>
|
|
.CopyTo(KeyValuePair<StringReference, Action>[] array,
|
|
int arrayIndex)
|
|
=> ((ICollection<KeyValuePair<StringReference, Action>>)Dictionary).CopyTo(array, arrayIndex);
|
|
|
|
bool ICollection<KeyValuePair<StringReference, Action>>
|
|
.Remove(KeyValuePair<StringReference, Action> item)
|
|
=> Dictionary.Remove(item.Key);
|
|
|
|
bool ICollection<KeyValuePair<StringReference, Action>>.IsReadOnly
|
|
=> false;
|
|
|
|
/************************************************************************************************************************/
|
|
#endregion
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|