Skip to content

Instantly share code, notes, and snippets.

@simonwittber
Created May 16, 2025 02:48
Show Gist options
  • Save simonwittber/e4c94f5b6a620428ccd8597bf82b070c to your computer and use it in GitHub Desktop.
Save simonwittber/e4c94f5b6a620428ccd8597bf82b070c to your computer and use it in GitHub Desktop.
Generic StateMachine
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