// Animancer // Copyright 2020 Kybernetik // using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Animancer.FSM { /// /// A simple Finite State Machine system that registers each state with a particular key. /// /// This class allows states to be registered with a particular key upfront and then accessed later using that key. /// See for a system that does not bother keeping track of any states other than /// the active one. /// [HelpURL(StateExtensions.APIDocumentationURL + "StateMachine_2")] public partial class StateMachine : StateMachine, IDictionary where TState : class, IState { /************************************************************************************************************************/ /// The collection of states mapped to a particular key. public IDictionary Dictionary { get; set; } /// The current state. public TKey CurrentKey { get; private set; } /************************************************************************************************************************/ /// /// Constructs a new with a new . /// public StateMachine() { Dictionary = new Dictionary(); } /// /// Constructs a new which uses the specified `dictionary`. /// public StateMachine(IDictionary dictionary) { Dictionary = dictionary; } /// /// Constructs a new with a new and /// immediately uses the `defaultKey` to enter the `defaultState`. /// public StateMachine(TKey defaultKey, TState defaultState) { Dictionary = new Dictionary(); ForceSetState(defaultKey, defaultState); } /// /// Constructs a new which uses the specified `dictionary` and /// immediately uses the `defaultKey` to enter the `defaultState`. /// public StateMachine(IDictionary dictionary, TKey defaultKey, TState defaultState) { Dictionary = dictionary; ForceSetState(defaultKey, defaultState); } /************************************************************************************************************************/ /// /// Attempts to enter the specified `state` and returns true if successful. /// /// This method returns true immediately if the specified `state` is already the /// . To allow directly re-entering the same state, use /// instead. /// public bool TrySetState(TKey key, TState state) { if (CurrentState == state) return true; else return TryResetState(key, state); } /// /// Attempts to enter the specified state associated with the specified `key` and returns it if successful. /// /// This method returns true immediately if the specified `key` is already the . To /// allow directly re-entering the same state, use instead. /// public TState TrySetState(TKey key) { if (Equals(CurrentKey, key)) return CurrentState; else return TryResetState(key); } /************************************************************************************************************************/ /// /// Attempts to enter the specified `state` and returns true if successful. /// /// This method does not check if the `state` is already the . /// To do so, use instead. /// public bool TryResetState(TKey key, TState state) { if (!CanSetState(state)) return false; ForceSetState(key, state); return true; } /// /// Attempts to enter the specified state associated with the specified `key` and returns it if successful. /// /// This method does not check if the `key` is already the . To do so, use /// instead. /// public TState TryResetState(TKey key) { TState state; if (Dictionary.TryGetValue(key, out state)) { if (TryResetState(key, state)) return state; } return null; } /************************************************************************************************************************/ /// /// Calls on the current state then changes to the specified key and /// state and calls on it. /// /// Note that this method does not check or /// . To do that, you should use /// instead. /// public void ForceSetState(TKey key, TState state) { CurrentKey = key; ForceSetState(state); } /// /// Uses to change to the state mapped to the `key`. If nothing is mapped, /// it changes to default(TState). /// public TState ForceSetState(TKey key) { TState state; Dictionary.TryGetValue(key, out state); ForceSetState(key, state); return state; } /************************************************************************************************************************/ #region Dictionary Wrappers /************************************************************************************************************************/ /// Gets or sets a particular state in the . public TState this[TKey key] { get { return Dictionary[key]; } set { Dictionary[key] = value; } } /// Gets an containing the keys of the . public ICollection Keys { get { return Dictionary.Keys; } } /// Gets an containing the state of the . public ICollection Values { get { return Dictionary.Values; } } /// Gets the number of states contained in the . public int Count { get { return Dictionary.Count; } } /// Indicates whether the is read-only. public bool IsReadOnly { get { return Dictionary.IsReadOnly; } } /// Adds a state to the . public void Add(TKey key, TState state) { Dictionary.Add(key, state); } /// Removes a state from the . public bool Remove(TKey key) { return Dictionary.Remove(key); } /// Removes all state from the . public void Clear() { Dictionary.Clear(); } /// Determines whether the contains a state with the specified `key`. public bool ContainsKey(TKey key) { return Dictionary.ContainsKey(key); } /// Gets the state associated with the specified `key` in the . public bool TryGetValue(TKey key, out TState state) { return Dictionary.TryGetValue(key, out state); } /// Adds a state to the . public void Add(KeyValuePair item) { Dictionary.Add(item); } /// Removes a state from the . public bool Remove(KeyValuePair item) { return Dictionary.Remove(item); } /// Determines whether the contains a specific value. public bool Contains(KeyValuePair item) { return Dictionary.Contains(item); } /// Returns an enumerator that iterates through the . public IEnumerator> GetEnumerator() { return Dictionary.GetEnumerator(); } /// Returns an enumerator that iterates through the . IEnumerator IEnumerable.GetEnumerator() { return Dictionary.GetEnumerator(); } /// /// Copies the elements of the to an `array` starting at the specified `arrayIndex`. /// public void CopyTo(KeyValuePair[] array, int arrayIndex) { Dictionary.CopyTo(array, arrayIndex); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ /// /// Returns the state associated with the specified `key`, or null if none is present. /// public TState GetState(TKey key) { TState state; TryGetValue(key, out state); return state; } /************************************************************************************************************************/ /// Adds the specified `keys` and `states`. Both arrays must be the same size. public void AddRange(TKey[] keys, TState[] states) { Debug.Assert(keys.Length == states.Length, "Both arrays must be the same size."); for (int i = 0; i < keys.Length; i++) { Dictionary.Add(keys[i], states[i]); } } /************************************************************************************************************************/ /// Sets the without actually changing the state. public void SetFakeKey(TKey key) { CurrentKey = key; } /************************************************************************************************************************/ /// /// Returns a string describing the type of this state machine and its and /// . /// public override string ToString() { return GetType().FullName + " -> " + CurrentKey + (CurrentState != null ? (" -> " + CurrentState.ToString()) : " -> null"); } /************************************************************************************************************************/ } }