/************************************************************************************************************************/ /// <summary> /// Checks if it is currently possible to enter the specified `state`. This requires /// <see cref="IState.CanExitState"/> on the <see cref="CurrentState"/> and /// <see cref="IState.CanEnterState"/> on the specified `state` to both return true. /// </summary> public bool CanSetState(TState state) { #if UNITY_ASSERTIONS if (state == null && !AllowNullStates) { throw new ArgumentNullException(nameof(state), NullNotAllowed); } #endif StateChange <TState> .Begin(CurrentState, state, out var previouslyActiveChange); try { if (CurrentState != null && !CurrentState.CanExitState) { return(false); } if (state != null && !state.CanEnterState) { return(false); } return(true); } finally { StateChange <TState> .End(previouslyActiveChange); } }
/************************************************************************************************************************/ /// <summary> /// Calls <see cref="IState.OnExitState"/> on the <see cref="CurrentState"/> then changes to the /// specified `state` and calls <see cref="IState.OnEnterState"/> on it. /// <para></para> /// This method does not check <see cref="IState.CanExitState"/> or /// <see cref="IState.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead. /// </summary> public void ForceSetState(TState state) { #if UNITY_ASSERTIONS if (state == null && !AllowNullStates) { throw new ArgumentNullException(nameof(state), NullNotAllowed); } #endif StateChange <TState> .Begin(CurrentState, state, out var previouslyActiveChange); try { #if UNITY_EDITOR 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 CurrentState?.OnExitState(); CurrentState = state; state?.OnEnterState(); } finally { StateChange <TState> .End(previouslyActiveChange); } }
/************************************************************************************************************************/ /// <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="CurrentState"/>. To do so, use /// <see cref="TrySetState"/> instead. /// </summary> public bool TryResetState(TState state) { if (!CanSetState(state)) { return(false); } StateChange <TState> .Begin(CurrentState, state, out var previouslyActiveChange); try { ForceSetState(state); return(true); } finally { StateChange <TState> .End(previouslyActiveChange); } }
/// <summary>Creates a new <see cref="StateMachine{TState}"/> and immediately enters the `defaultState`.</summary> /// <remarks>This calls <see cref="IState.OnEnterState"/> but not <see cref="IState.CanEnterState"/>.</remarks> public StateMachine(TState defaultState) { #if UNITY_ASSERTIONS if (defaultState == null && !AllowNullStates) { throw new ArgumentNullException(nameof(defaultState), NullNotAllowed); } #endif StateChange <TState> .Begin(null, defaultState, out var previouslyActiveChange); try { CurrentState = defaultState; defaultState.OnEnterState(); } finally { StateChange <TState> .End(previouslyActiveChange); } }
/************************************************************************************************************************/ /// <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; }