// Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik // using System; namespace Animancer.FSM { /// <summary>A static access point for the details of a state change in a <see cref="StateMachine{TState}"/>.</summary> /// <remarks> /// This system is thread-safe. /// <para></para> /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/fsm/changing-states">Changing States</see> /// </remarks> /// https://kybernetik.com.au/animancer/api/Animancer.FSM/StateChange_1 /// public struct StateChange<TState> : IDisposable where TState : class, IState { /************************************************************************************************************************/ [ThreadStatic] private static StateChange<TState> _Current; private StateMachine<TState> _StateMachine; private TState _PreviousState; private TState _NextState; /************************************************************************************************************************/ /// <summary>Is a <see cref="StateChange{TState}"/> of this type currently occurring?</summary> public static bool IsActive => _Current._StateMachine != null; /// <summary>The <see cref="StateMachine{TState}"/> in which the current change is occurring.</summary> /// <remarks>This will be null if no change is currently occurring.</remarks> public static StateMachine<TState> StateMachine => _Current._StateMachine; /************************************************************************************************************************/ /// <summary>The state currently being changed from.</summary> /// <exception cref="InvalidOperationException">[Assert-Only] /// <see cref="IsActive"/> is false so this property is likely being accessed on the wrong generic type. /// </exception> public static TState PreviousState { get { #if UNITY_ASSERTIONS if (!IsActive) throw new InvalidOperationException(StateExtensions.GetChangeError(typeof(TState), typeof(StateMachine<>))); #endif return _Current._PreviousState; } } /************************************************************************************************************************/ /// <summary>The state being changed into.</summary> /// <exception cref="InvalidOperationException">[Assert-Only] /// <see cref="IsActive"/> is false so this property is likely being accessed on the wrong generic type. /// </exception> public static TState NextState { get { #if UNITY_ASSERTIONS if (!IsActive) throw new InvalidOperationException(StateExtensions.GetChangeError(typeof(TState), typeof(StateMachine<>))); #endif return _Current._NextState; } } /************************************************************************************************************************/ /// <summary>[Internal] /// Assigns the parameters as the details of the currently active change and creates a new /// <see cref="StateChange{TState}"/> containing the details of the previously active change so that disposing /// it will re-assign those previous details to be current again in case of recursive state changes. /// </summary> /// <example><code> /// using (new StateChange<TState>(stateMachine, previousState, nextState)) /// { /// // Do the actual state change. /// } /// </code></example> internal StateChange(StateMachine<TState> stateMachine, TState previousState, TState nextState) { this = _Current; _Current._StateMachine = stateMachine; _Current._PreviousState = previousState; _Current._NextState = nextState; } /************************************************************************************************************************/ /// <summary>[<see cref="IDisposable"/>] /// Re-assigns the values of this change (which were the previous values from when it was created) to be the /// currently active change. See the constructor for recommended usage. /// </summary> /// <remarks> /// Usually this will be returning to default values (nulls), but if one state change causes another then the /// second one ending will return to the first which will then return to the defaults. /// </remarks> public void Dispose() { _Current = this; } /************************************************************************************************************************/ /// <summary>Returns a string describing the contents of this <see cref="StateChange{TState}"/>.</summary> public override string ToString() => IsActive ? $"{nameof(StateChange<TState>)}<{typeof(TState).FullName}" + $">({nameof(PreviousState)}='{_PreviousState}'" + $", {nameof(NextState)}='{_NextState}')" : $"{nameof(StateChange<TState>)}<{typeof(TState).FullName}(Not Currently Active)"; /// <summary>Returns a string describing the contents of the current <see cref="StateChange{TState}"/>.</summary> public static string CurrentToString() => _Current.ToString(); /************************************************************************************************************************/ } }