| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471 | 
							- // Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
 
- using System;
 
- using System.Collections;
 
- using System.Collections.Generic;
 
- using UnityEngine;
 
- namespace Animancer.FSM
 
- {
 
-     /// <summary>A simple keyless Finite State Machine system.</summary>
 
-     /// <remarks>
 
-     /// <strong>Documentation:</strong>
 
-     /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">
 
-     /// Finite State Machines</see>
 
-     /// </remarks>
 
-     /// https://kybernetik.com.au/animancer/api/Animancer.FSM/IStateMachine
 
-     /// 
 
-     public interface IStateMachine
 
-     {
 
-         /************************************************************************************************************************/
 
-         /// <summary>The currently active state.</summary>
 
-         object CurrentState { get; }
 
-         /// <summary>The <see cref="StateChange{TState}.PreviousState"/>.</summary>
 
-         object PreviousState { get; }
 
-         /// <summary>The <see cref="StateChange{TState}.NextState"/>.</summary>
 
-         object NextState { get; }
 
-         /// <summary>Is it currently possible to enter the specified `state`?</summary>
 
-         /// <remarks>
 
-         /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
 
-         /// <see cref="IState.CanEnterState"/> on the specified `state` to both return true.
 
-         /// </remarks>
 
-         bool CanSetState(object state);
 
-         /// <summary>Returns the first of the `states` which can currently be entered.</summary>
 
-         object CanSetState(IList states);
 
-         /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method returns true immediately if the specified `state` is already the <see cref="CurrentState"/>.
 
-         /// To allow directly re-entering the same state, use <see cref="TryResetState(object)"/> instead.
 
-         /// </remarks>
 
-         bool TrySetState(object state);
 
-         /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method returns true and does nothing else if the <see cref="CurrentState"/> is in the list.
 
-         /// To allow directly re-entering the same state, use <see cref="TryResetState(IList)"/> instead.
 
-         /// <para></para>
 
-         /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
 
-         /// </remarks>
 
-         bool TrySetState(IList states);
 
-         /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
 
-         /// <see cref="TrySetState(object)"/> instead.
 
-         /// </remarks>
 
-         bool TryResetState(object state);
 
-         /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
 
-         /// <see cref="TrySetState(IList)"/> instead.
 
-         /// <para></para>
 
-         /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
 
-         /// </remarks>
 
-         bool TryResetState(IList states);
 
-         /// <summary>
 
-         /// Calls <see cref="IState.OnExitState"/> on the <see cref="CurrentState"/> then changes it to the
 
-         /// specified `state` and calls <see cref="IState.OnEnterState"/> on it.
 
-         /// </summary>
 
-         /// <remarks>
 
-         /// This method does not check <see cref="IState.CanExitState"/> or
 
-         /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead.
 
-         /// </remarks>
 
-         void ForceSetState(object state);
 
- #if UNITY_ASSERTIONS
 
-         /// <summary>[Assert-Only] Should the <see cref="CurrentState"/> be allowed to be set to null? Default is false.</summary>
 
-         /// <remarks>Can be set by <see cref="SetAllowNullStates"/>.</remarks>
 
-         bool AllowNullStates { get; }
 
- #endif
 
-         /// <summary>[Assert-Conditional] Sets <see cref="AllowNullStates"/>.</summary>
 
-         void SetAllowNullStates(bool allow = true);
 
-         /************************************************************************************************************************/
 
- #if UNITY_EDITOR && UNITY_IMGUI
 
-         /************************************************************************************************************************/
 
-         /// <summary>[Editor-Only] The number of standard size lines that <see cref="DoGUI"/> will use.</summary>
 
-         int GUILineCount { get; }
 
-         /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine.</summary>
 
-         void DoGUI();
 
-         /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine in the given `area`.</summary>
 
-         void DoGUI(ref Rect area);
 
-         /************************************************************************************************************************/
 
- #endif
 
-         /************************************************************************************************************************/
 
-     }
 
-     /// <summary>A simple keyless Finite State Machine system.</summary>
 
-     /// <remarks>
 
-     /// This class doesn't keep track of any states other than the currently active one.
 
-     /// See <see cref="StateMachine{TKey, TState}"/> for a system that allows
 
-     /// states to be pre-registered and accessed using a separate key.
 
-     /// <para></para>
 
-     /// See <see cref="InitializeAfterDeserialize"/> if using this class in a serialized field.
 
-     /// <para></para>
 
-     /// <strong>Documentation:</strong>
 
-     /// <see href="https://kybernetik.com.au/animancer/docs/manual/fsm">
 
-     /// Finite State Machines</see>
 
-     /// </remarks>
 
-     /// https://kybernetik.com.au/animancer/api/Animancer.FSM/StateMachine_1
 
-     /// 
 
-     [HelpURL(StateExtensions.APIDocumentationURL + nameof(StateMachine<TState>) + "_1")]
 
-     [Serializable]
 
-     public partial class StateMachine<TState> : IStateMachine
 
-         where TState : class, IState
 
-     {
 
-         /************************************************************************************************************************/
 
-         [SerializeField]
 
-         private TState _CurrentState;
 
-         /// <summary>[<see cref="SerializeField"/>] The currently active state.</summary>
 
-         public TState CurrentState => _CurrentState;
 
-         /************************************************************************************************************************/
 
-         /// <summary>The <see cref="StateChange{TState}.PreviousState"/>.</summary>
 
-         public TState PreviousState => StateChange<TState>.PreviousState;
 
-         /// <summary>The <see cref="StateChange{TState}.NextState"/>.</summary>
 
-         public TState NextState => StateChange<TState>.NextState;
 
-         /************************************************************************************************************************/
 
-         /// <summary>Creates a new <see cref="StateMachine{TState}"/>, leaving the <see cref="CurrentState"/> null.</summary>
 
-         public StateMachine() { }
 
-         /// <summary>Creates a new <see cref="StateMachine{TState}"/> and immediately enters the `state`.</summary>
 
-         /// <remarks>This calls <see cref="IState.OnEnterState"/> but not <see cref="IState.CanEnterState"/>.</remarks>
 
-         public StateMachine(TState state)
 
-         {
 
- #if UNITY_ASSERTIONS
 
-             if (state == null)// AllowNullStates won't be true yet since this is the constructor.
 
-                 throw new ArgumentNullException(nameof(state), NullNotAllowed);
 
- #endif
 
-             using (new StateChange<TState>(this, null, state))
 
-             {
 
-                 _CurrentState = state;
 
-                 state.OnEnterState();
 
-             }
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>Call this after deserializing to properly initialize the <see cref="CurrentState"/>.</summary>
 
-         /// <remarks>
 
-         /// Unfortunately, <see cref="ISerializationCallbackReceiver"/> can't be used to automate this
 
-         /// because many Unity functions aren't available during serialization such as getting or setting a
 
-         /// <see cref="Behaviour.enabled"/> like <see cref="StateBehaviour.OnEnterState"/> does.
 
-         /// <para></para>
 
-         /// <strong>Example:</strong><code>
 
-         /// public class MyComponent : MonoBehaviour
 
-         /// {
 
-         ///     [SerializeField]
 
-         ///     private StateMachine<MyState> _StateMachine;
 
-         ///     
 
-         ///     protected virtual void Awake()
 
-         ///     {
 
-         ///         _StateMachine.InitializeAfterDeserialize();
 
-         ///     }
 
-         /// }
 
-         /// </code></remarks>
 
-         public virtual void InitializeAfterDeserialize()
 
-         {
 
-             if (_CurrentState != null)
 
-                 using (new StateChange<TState>(this, null, _CurrentState))
 
-                     _CurrentState.OnEnterState();
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>Is it currently possible to enter the specified `state`?</summary>
 
-         /// <remarks>
 
-         /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
 
-         /// <see cref="IState.CanEnterState"/> on the specified `state` to both return true.
 
-         /// </remarks>
 
-         public bool CanSetState(TState state)
 
-         {
 
- #if UNITY_ASSERTIONS
 
-             if (state == null && !AllowNullStates)
 
-                 throw new ArgumentNullException(nameof(state), NullNotAllowed);
 
- #endif
 
-             using (new StateChange<TState>(this, _CurrentState, state))
 
-             {
 
-                 if (_CurrentState != null && !_CurrentState.CanExitState)
 
-                     return false;
 
-                 if (state != null && !state.CanEnterState)
 
-                     return false;
 
-                 return true;
 
-             }
 
-         }
 
-         /// <summary>Returns the first of the `states` which can currently be entered.</summary>
 
-         /// <remarks>
 
-         /// This requires <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and
 
-         /// <see cref="IState.CanEnterState"/> on one of the `states` to both return true.
 
-         /// <para></para>
 
-         /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
 
-         /// </remarks>
 
-         public TState CanSetState(IList<TState> states)
 
-         {
 
-             // We call CanSetState so that it will check CanExitState for each individual pair in case it does
 
-             // something based on the next state.
 
-             var count = states.Count;
 
-             for (int i = 0; i < count; i++)
 
-             {
 
-                 var state = states[i];
 
-                 if (CanSetState(state))
 
-                     return state;
 
-             }
 
-             return null;
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method returns true immediately if the specified `state` is already the <see cref="CurrentState"/>.
 
-         /// To allow directly re-entering the same state, use <see cref="TryResetState(TState)"/> instead.
 
-         /// </remarks>
 
-         public bool TrySetState(TState state)
 
-         {
 
-             if (_CurrentState == state)
 
-             {
 
- #if UNITY_ASSERTIONS
 
-                 if (state == null && !AllowNullStates)
 
-                     throw new ArgumentNullException(nameof(state), NullNotAllowed);
 
- #endif
 
-                 return true;
 
-             }
 
-             return TryResetState(state);
 
-         }
 
-         /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method returns true and does nothing else if the <see cref="CurrentState"/> is in the list.
 
-         /// To allow directly re-entering the same state, use <see cref="TryResetState(IList{TState})"/> instead.
 
-         /// <para></para>
 
-         /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
 
-         /// </remarks>
 
-         public bool TrySetState(IList<TState> states)
 
-         {
 
-             var count = states.Count;
 
-             for (int i = 0; i < count; i++)
 
-                 if (TrySetState(states[i]))
 
-                     return true;
 
-             return false;
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>Attempts to enter the specified `state` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
 
-         /// <see cref="TrySetState(TState)"/> instead.
 
-         /// </remarks>
 
-         public bool TryResetState(TState state)
 
-         {
 
-             if (!CanSetState(state))
 
-                 return false;
 
-             ForceSetState(state);
 
-             return true;
 
-         }
 
-         /// <summary>Attempts to enter any of the specified `states` and returns true if successful.</summary>
 
-         /// <remarks>
 
-         /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use
 
-         /// <see cref="TrySetState(IList{TState})"/> instead.
 
-         /// <para></para>
 
-         /// States are checked in ascending order (i.e. from <c>[0]</c> to <c>[states.Count - 1]</c>).
 
-         /// </remarks>
 
-         public bool TryResetState(IList<TState> states)
 
-         {
 
-             var count = states.Count;
 
-             for (int i = 0; i < count; i++)
 
-                 if (TryResetState(states[i]))
 
-                     return true;
 
-             return false;
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>
 
-         /// Calls <see cref="IState.OnExitState"/> on the <see cref="CurrentState"/> then changes it to the
 
-         /// specified `state` and calls <see cref="IState.OnEnterState"/> on it.
 
-         /// </summary>
 
-         /// <remarks>
 
-         /// This method does not check <see cref="IState.CanExitState"/> or
 
-         /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead.
 
-         /// </remarks>
 
-         public void ForceSetState(TState state)
 
-         {
 
- #if UNITY_ASSERTIONS
 
-             if (state == null)
 
-             {
 
-                 if (!AllowNullStates)
 
-                     throw new ArgumentNullException(nameof(state), NullNotAllowed);
 
-             }
 
-             else if (state is IOwnedState<TState> owned && owned.OwnerStateMachine != this)
 
-             {
 
-                 throw new InvalidOperationException(
 
-                     $"Attempted to use a state in a machine that is not its owner." +
 
-                     $"\n• State: {state}" +
 
-                     $"\n• Machine: {this}");
 
-             }
 
- #endif
 
-             using (new StateChange<TState>(this, _CurrentState, state))
 
-             {
 
-                 _CurrentState?.OnExitState();
 
-                 _CurrentState = state;
 
-                 state?.OnEnterState();
 
-             }
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>Returns a string describing the type of this state machine and its <see cref="CurrentState"/>.</summary>
 
-         public override string ToString() => $"{GetType().Name} -> {_CurrentState}";
 
-         /************************************************************************************************************************/
 
- #if UNITY_ASSERTIONS
 
-         /// <summary>[Assert-Only] Should the <see cref="CurrentState"/> be allowed to be set to null? Default is false.</summary>
 
-         /// <remarks>Can be set by <see cref="SetAllowNullStates"/>.</remarks>
 
-         public bool AllowNullStates { get; private set; }
 
-         /// <summary>[Assert-Only] The error given when attempting to set the <see cref="CurrentState"/> to null.</summary>
 
-         private const string NullNotAllowed =
 
-             "This " + nameof(StateMachine<TState>) + " does not allow its state to be set to null." +
 
-             " Use " + nameof(SetAllowNullStates) + " to allow it if this is intentional.";
 
- #endif
 
-         /// <summary>[Assert-Conditional] Sets <see cref="AllowNullStates"/>.</summary>
 
-         [System.Diagnostics.Conditional("UNITY_ASSERTIONS")]
 
-         public void SetAllowNullStates(bool allow = true)
 
-         {
 
- #if UNITY_ASSERTIONS
 
-             AllowNullStates = allow;
 
- #endif
 
-         }
 
-         /************************************************************************************************************************/
 
-         #region GUI
 
-         /************************************************************************************************************************/
 
- #if UNITY_EDITOR && UNITY_IMGUI
 
-         /************************************************************************************************************************/
 
-         /// <summary>[Editor-Only] The number of standard size lines that <see cref="DoGUI"/> will use.</summary>
 
-         public virtual int GUILineCount => 1;
 
-         /************************************************************************************************************************/
 
-         /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine.</summary>
 
-         public void DoGUI()
 
-         {
 
-             var spacing = UnityEditor.EditorGUIUtility.standardVerticalSpacing;
 
-             var lines = GUILineCount;
 
-             var height =
 
-                 UnityEditor.EditorGUIUtility.singleLineHeight * lines +
 
-                 spacing * (lines - 1);
 
-             var area = GUILayoutUtility.GetRect(0, height);
 
-             area.height -= spacing;
 
-             DoGUI(ref area);
 
-         }
 
-         /************************************************************************************************************************/
 
-         /// <summary>[Editor-Only] Draws GUI fields to display the status of this state machine in the given `area`.</summary>
 
-         public virtual void DoGUI(ref Rect area)
 
-         {
 
-             area.height = UnityEditor.EditorGUIUtility.singleLineHeight;
 
-             UnityEditor.EditorGUI.BeginChangeCheck();
 
-             var state = StateMachineUtilities.DoGenericField(area, "Current State", _CurrentState);
 
-             if (UnityEditor.EditorGUI.EndChangeCheck())
 
-             {
 
-                 if (Event.current.control)
 
-                     ForceSetState(state);
 
-                 else
 
-                     TrySetState(state);
 
-             }
 
-             StateMachineUtilities.NextVerticalArea(ref area);
 
-         }
 
-         /************************************************************************************************************************/
 
- #endif
 
-         #endregion
 
-         /************************************************************************************************************************/
 
-         #region IStateMachine
 
-         /************************************************************************************************************************/
 
-         /// <inheritdoc/>
 
-         object IStateMachine.CurrentState => _CurrentState;
 
-         /// <inheritdoc/>
 
-         object IStateMachine.PreviousState => PreviousState;
 
-         /// <inheritdoc/>
 
-         object IStateMachine.NextState => NextState;
 
-         /// <inheritdoc/>
 
-         object IStateMachine.CanSetState(IList states) => CanSetState((List<TState>)states);
 
-         /// <inheritdoc/>
 
-         bool IStateMachine.CanSetState(object state) => CanSetState((TState)state);
 
-         /// <inheritdoc/>
 
-         void IStateMachine.ForceSetState(object state) => ForceSetState((TState)state);
 
-         /// <inheritdoc/>
 
-         bool IStateMachine.TryResetState(IList states) => TryResetState((List<TState>)states);
 
-         /// <inheritdoc/>
 
-         bool IStateMachine.TryResetState(object state) => TryResetState((TState)state);
 
-         /// <inheritdoc/>
 
-         bool IStateMachine.TrySetState(IList states) => TrySetState((List<TState>)states);
 
-         /// <inheritdoc/>
 
-         bool IStateMachine.TrySetState(object state) => TrySetState((TState)state);
 
-         /// <inheritdoc/>
 
-         void IStateMachine.SetAllowNullStates(bool allow) => SetAllowNullStates(allow);
 
-         /************************************************************************************************************************/
 
-         #endregion
 
-         /************************************************************************************************************************/
 
-     }
 
- }
 
 
  |