You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Plugins/Animancer/Utilities/State Machine/StateMachine2.cs

263 lines
12 KiB
C#

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