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