Last active
February 9, 2017 14:17
-
-
Save johnlcox/ae9443c3c16e8d691123cc4c712ca795 to your computer and use it in GitHub Desktop.
Event Driven State Machine
This file contains 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
public class Example | |
{ | |
public enum States { One, Two } | |
public interface IExampleMessage | |
{ | |
} | |
public class OneMessage : IExampleMessage | |
{ | |
public string one = "one"; | |
} | |
public class TwoMessage : IExampleMessage | |
{ | |
public string two = "two"; | |
} | |
public class MyStateMachine : StateMachine<States, IExampleMessage> | |
{ | |
protected override void Configure() | |
{ | |
When(States.One, state => | |
{ | |
state.Receive<OneMessage>(message => | |
{ | |
Console.WriteLine(message.one); | |
}); | |
}); | |
When(States.Two, state => | |
{ | |
state.Receive <TwoMessage>(message => | |
{ | |
Console.WriteLine(message.two); | |
}); | |
}); | |
} | |
} | |
} |
This file contains 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; | |
namespace SparrowHawk.Scripts.FSM | |
{ | |
public abstract class StateMachine<TS, TM> | |
{ | |
private readonly Dictionary<TS, Behaviour> _states = new Dictionary<TS, Behaviour>(); | |
private TS _initialState; | |
private Behaviour _currentBehaviour; | |
protected abstract void Configure(); | |
protected void StartWith(TS state) | |
{ | |
_initialState = state; | |
} | |
protected void When(TS state, Action<Behaviour> stateHandler) | |
{ | |
if (_states.ContainsKey(state)) throw new InvalidOperationException(); | |
Behaviour behaviour = new Behaviour(this); | |
stateHandler(behaviour); | |
_states.Add(state, behaviour); | |
} | |
protected void OnReceive(TM message) | |
{ | |
if (_currentBehaviour == null && _initialState == null) throw new InvalidOperationException(); | |
if (_currentBehaviour == null && !_states.ContainsKey(_initialState)) throw new InvalidOperationException(); | |
if (_currentBehaviour == null) | |
{ | |
_currentBehaviour = _states[_initialState]; | |
} | |
_currentBehaviour.OnReceive(message); | |
} | |
private void TransitionTo(TS state) | |
{ | |
if (!_states.ContainsKey(state)) throw new InvalidOperationException(); | |
_currentBehaviour = _states[state]; | |
} | |
protected class Behaviour | |
{ | |
// TODO: Add real predicate/guard support | |
private static readonly Predicate<TM> TruePredicate = _ => true; | |
private readonly StateMachine<TS, TM> _stateMachine; | |
private readonly Dictionary<Type, PredicateAndAction> _receiveHandlers = | |
new Dictionary<Type, PredicateAndAction>(); | |
public Behaviour(StateMachine<TS, TM> stateMachine) | |
{ | |
_stateMachine = stateMachine; | |
} | |
internal void OnReceive(TM message) | |
{ | |
PredicateAndAction predicateAndAction; | |
if (!_receiveHandlers.TryGetValue(message.GetType(), out predicateAndAction)) return; | |
if (((Predicate<TM>) predicateAndAction.Predicate)(message)) | |
{ | |
((Action<TM>) predicateAndAction.Action)(message); | |
} | |
} | |
public void Receive<TMu>(Action<TMu> handler) where TMu : class, TM, new() | |
{ | |
var handlerType = typeof(TMu); | |
_receiveHandlers.Add(handlerType, new PredicateAndAction(TruePredicate, handler)); | |
} | |
public void TransitionTo(TS state) | |
{ | |
_stateMachine.TransitionTo(state); | |
} | |
private class PredicateAndAction | |
{ | |
private readonly object _predicate; | |
private readonly object _action; | |
public PredicateAndAction(object predicate, object action) | |
{ | |
_predicate = predicate; | |
_action = action; | |
} | |
public object Predicate | |
{ | |
get { return _predicate; } | |
} | |
public object Action | |
{ | |
get { return _action; } | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment