Created
May 16, 2025 02:48
-
-
Save simonwittber/e4c94f5b6a620428ccd8597bf82b070c to your computer and use it in GitHub Desktop.
Generic StateMachine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
public abstract class StateMachine<T> where T: struct, Enum | |
{ | |
public T state { get; protected set; } | |
public Action<T, T> OnStateChanged; | |
protected struct TransitionX | |
{ | |
public T? fromState; | |
public T toState; | |
public Func<bool>[] conditions; | |
} | |
protected List<TransitionX> transitions = new List<TransitionX>(); | |
private readonly Dictionary<T, Action> _enterHandlers = new(); | |
private readonly Dictionary<T, Action> _exitHandlers = new(); | |
private readonly Dictionary<T, Action> _stayHandlers = new(); | |
protected void From(T fromState, T toState, params Func<bool>[] conditions) | |
{ | |
transitions.Add(new TransitionX() { fromState = fromState, toState = toState, conditions = conditions }); | |
} | |
protected void FromAny(T toState, params Func<bool>[] conditions) | |
{ | |
transitions.Add(new TransitionX() { fromState = null, toState = toState, conditions = conditions }); | |
} | |
protected void OnEnter(T forState, Action handler) => _enterHandlers[forState] = handler; | |
protected void OnExit(T forState, Action handler) => _exitHandlers[forState] = handler; | |
protected void OnStay(T forState, Action handler) => _stayHandlers[forState] = handler; | |
public T NextState() | |
{ | |
var current = this.state; | |
foreach (var t in transitions) | |
{ | |
var noConditions = t.conditions.Length == 0; | |
var guard = false; | |
if(noConditions) | |
guard = true; | |
else | |
{ | |
foreach (var i in t.conditions) | |
{ | |
guard = i(); | |
if (!guard) | |
break; | |
} | |
} | |
if (t.fromState.HasValue) | |
{ | |
// check if matches current state and guard | |
var currentStateIsFromState = EqualityComparer<T>.Default.Equals(current,t.fromState.Value); | |
if(currentStateIsFromState && guard) | |
{ | |
return t.toState; | |
} | |
} | |
else // is the "Any" state, so just check guard. | |
{ | |
if (guard) return t.toState; | |
} | |
} | |
return current; | |
} | |
public void UpdateState() => SetState(NextState()); | |
public void SetState(T newState) | |
{ | |
var currentState = state; | |
if(Equals(currentState, newState)) | |
{ | |
InvokeStayInStateMethods(); | |
return; | |
} | |
OnStateChanged?.Invoke(currentState, newState); | |
InvokeExitFromStateMethods(); | |
state = newState; | |
InvokeEnterToStateMethods(); | |
} | |
private void InvokeEnterToStateMethods() | |
{ | |
if (_enterHandlers.TryGetValue(state, out Action handler)) handler(); | |
} | |
private void InvokeExitFromStateMethods() | |
{ | |
if (_exitHandlers.TryGetValue(state, out Action handler)) handler(); | |
} | |
private void InvokeStayInStateMethods() | |
{ | |
if (_stayHandlers.TryGetValue(state, out Action handler)) handler(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment