Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add optional name, add Delegates to not require (vars), make calling … #6

Merged
merged 1 commit into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Demo/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
mainState.AddTransitionTo(oddState, (vars) => vars["x"] % 2 == 1);
mainState.AddTransitionTo(evenState, (vars) => vars["x"] % 2 == 0);

oddState.AddTransitionTo(incrementState, null);
oddState.AlwaysTransitionTo(incrementState);

evenState.AddTransitionTo(incrementState, null);
evenState.AlwaysTransitionTo(incrementState);

incrementState.AddTransitionTo(StateMachine.ExitState, (vars) => vars["x"] > 10);
incrementState.AddTransitionTo(mainState, null);
incrementState.AlwaysTransitionTo(mainState);

classSM.InitialState = mainState;

Expand Down
17 changes: 15 additions & 2 deletions StateMachine/Delegates.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
namespace RoadieRichStateMachine
{
/// <summary>
/// Determines whether to take a certain transition
/// </summary>
/// <returns>true if the transition should be taken</returns>
public delegate bool TransitionConditionDelegate();

/// <summary>
/// Determines whether to take a certain transition
/// </summary>
/// <param name="vars">A dictionary of variables shared between all states and transitions</param>
/// <returns>true if the transition should be taken</returns>
public delegate bool TransitionConditionDelegate(IDictionary<string, dynamic> vars);
public delegate bool TransitionConditionDelegateWithVars(IDictionary<string, dynamic> vars);

/// <summary>
/// A function run by <see cref="FunctionState"/>
/// </summary>
/// <param name="vars">A dictionary of variables shared between all states and transitions</param>
public delegate void FunctionStateFunctionDelegate();


/// <summary>
/// A function run by <see cref="FunctionState"/>
/// </summary>
/// <param name="vars">A dictionary of variables shared between all states and transitions</param>
public delegate void FunctionStateFunctionDelegate(IDictionary<string, dynamic> vars);
public delegate void FunctionStateFunctionDelegateWithVars(IDictionary<string, dynamic> vars);
}
21 changes: 9 additions & 12 deletions StateMachine/ExitState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@
{
public sealed class ExitState : State
{
protected override void Enter(IDictionary<string, dynamic> vars)
{
throw new NotImplementedException();
}
internal ExitState() : base("Exit State") { }

protected override void Exit(IDictionary<string, dynamic> vars)
{
throw new NotImplementedException();
}
protected override void Enter(IDictionary<string, dynamic> vars) => throw new NotImplementedException();

protected override void Inner(IDictionary<string, dynamic> vars)
{
throw new NotImplementedException();
}
protected override void Exit(IDictionary<string, dynamic> vars) => throw new NotImplementedException();

protected override void Inner(IDictionary<string, dynamic> vars) => throw new NotImplementedException();

[Obsolete("You cannot add transitions to ExitState", true)] public new State AddTransitionTo(State to, TransitionConditionDelegate condition) => throw new InvalidOperationException();
[Obsolete("You cannot add transitions to ExitState", true)] public new State AddTransitionTo(State to, TransitionConditionDelegateWithVars condition) => throw new InvalidOperationException();
[Obsolete("You cannot add transitions to ExitState", true)] public new State AlwaysTransitionTo(State to) => throw new InvalidOperationException();
}
}
72 changes: 61 additions & 11 deletions StateMachine/FunctionState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,83 @@
/// </summary>
public sealed class FunctionState : State
{
private readonly FunctionStateFunctionDelegate? _enterFunc;
private readonly FunctionStateFunctionDelegate? _exitFunc;
private readonly FunctionStateFunctionDelegate _innerFunc;
private readonly FunctionStateFunctionDelegateWithVars? _enterFunc;
private readonly FunctionStateFunctionDelegateWithVars? _exitFunc;
private readonly FunctionStateFunctionDelegateWithVars _innerFunc;

/// <summary>
/// Create an instance of <see cref="FunctionState"/> with the passed <see cref="State.Inner(Dictionary{string, dynamic})"/>.
/// Create an instance of <see cref="FunctionState"/> with the passed <see cref="FunctionStateFunctionDelegate"/>.
/// </summary>
/// <param name="name">Name of the state</param>
/// <param name="innerFunc"></param>
public FunctionState(string name, FunctionStateFunctionDelegate innerFunc) : this(name, null, innerFunc, null) { }

/// <summary>
/// Create an instance of <see cref="FunctionState"/> with the passed <see cref="FunctionStateFunctionDelegate"/>.
/// </summary>
/// <param name="innerFunc">The function to call every cycle.</param>
public FunctionState(FunctionStateFunctionDelegate innerFunc)
{
_innerFunc = innerFunc;
}
public FunctionState(FunctionStateFunctionDelegate innerFunc) : this("FunctionState", null, innerFunc, null) { }

/// <summary>
/// Creates an instance of <see cref="FunctionState"/> with name, entry, inner and exit <see cref="FunctionStateFunctionDelegate"/>.
/// </summary>
/// <param name="name"></param>
/// <param name="enterFunc">Function to be called when entering the state, or <c>null</c>.</param>
/// <param name="innerFunc">Function to be called every cycle.</param>
/// <param name="exitFunc">function to be called when exiting the state, or <c>null</c>.</param>
public FunctionState(string name,
FunctionStateFunctionDelegate? enterFunc,
FunctionStateFunctionDelegate innerFunc,
FunctionStateFunctionDelegate? exitFunc) : this(name,
enterFunc != null ? ((vars) => enterFunc()) : null,
(vars) => innerFunc(),
exitFunc != null ? ((vars) => exitFunc()) : null)
{}


/// <summary>
/// Creates an instance of <see cref="FunctionState"/> with entry, inner and exit states
/// Creates an instance of <see cref="FunctionState"/> with entry, inner and exit <see cref="FunctionStateFunctionDelegate"/>.
/// </summary>
/// <param name="enterFunc">Function to be called when entering the state, or <c>null</c>.</param>
/// <param name="innerFunc">Function to be called every cycle.</param>
/// <param name="exitFunc">Function to be called when exiting the state, or <c>null</c>.</param>
public FunctionState(FunctionStateFunctionDelegate? enterFunc,
FunctionStateFunctionDelegate innerFunc,
FunctionStateFunctionDelegate? exitFunc) : this("FunctionState",
enterFunc != null ? ((vars) => enterFunc()) : null,
(vars) => innerFunc(),
exitFunc != null ? ((vars) => exitFunc()) : null) { }

/// <summary>
/// Create an instance of <see cref="FunctionState"/> with the passed <see cref="FunctionStateFunctionDelegateWithVars"/>.
/// </summary>
/// <param name="name">Name of the state</param>
/// <param name="innerFunc"></param>
public FunctionState(string name, FunctionStateFunctionDelegateWithVars innerFunc) : this(name, null, innerFunc, null) { }

/// <summary>
/// Create an instance of <see cref="FunctionState"/> with the passed <see cref="FunctionStateFunctionDelegateWithVars"/>.
/// </summary>
/// <param name="innerFunc">The function to call every cycle.</param>
public FunctionState(FunctionStateFunctionDelegateWithVars innerFunc) : this("FunctionState", null, innerFunc, null) { }

/// <summary>
/// Creates an instance of <see cref="FunctionState"/> with name, entry, inner and exit <see cref="FunctionStateFunctionDelegateWithVars"/>.
/// </summary>
/// <param name="name"></param>
/// <param name="enterFunc">Function to be called when entering the state, or <c>null</c>.</param>
/// <param name="innerFunc">Function to be called every cycle.</param>
/// <param name="exitFunc">function to be called when exiting the state, or <c>null</c>.</param>
public FunctionState(FunctionStateFunctionDelegate? enterFunc, FunctionStateFunctionDelegate innerFunc, FunctionStateFunctionDelegate? exitFunc)
public FunctionState(string name,
FunctionStateFunctionDelegateWithVars? enterFunc,
FunctionStateFunctionDelegateWithVars innerFunc,
FunctionStateFunctionDelegateWithVars? exitFunc) : base(name)
{
_enterFunc = enterFunc;
_exitFunc = exitFunc;
_innerFunc = innerFunc;
}


protected sealed override void Enter(IDictionary<string, dynamic> vars)
{
_enterFunc?.Invoke(vars);
Expand Down
38 changes: 37 additions & 1 deletion StateMachine/State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,50 @@ public abstract class State : IDisposable
private readonly List<Transition> transitions = new();
private bool disposedValue;

public string? Name { get; }

protected State(string? name = null)
{
Name = name;
}


/// <summary>
/// Adds a state this state can transition to
/// </summary>
/// <param name="to">state to transition to</param>
/// <param name="condition">condition to transition. Evaluated each time <see cref="Inner(Dictionary{string, dynamic})"/> is run. If true, the state machine moves to the associated state. Use <c>null</c> to always transition.</param>
/// <remarks>Transition conditions are evaulated in the order they are added.</remarks>
/// <returns>The state this method was called on</returns>
public State AddTransitionTo(State to, TransitionConditionDelegateWithVars condition)
{
transitions.Add(new Transition(to, condition));
return this;
}

/// <summary>
/// Adds a state this state can transition to
/// </summary>
/// <param name="to">state to transition to</param>
/// <param name="condition">condition to transition. Evaluated each time <see cref="Inner(Dictionary{string, dynamic})"/> is run. If true, the state machine moves to the associated state. Use <c>null</c> to always transition.</param>
/// <remarks>Transition conditions are evaulated in the order they are added.</remarks>
/// <returns>The state this method was called on</returns>
public State AddTransitionTo(State to, TransitionConditionDelegate? condition)
public State AddTransitionTo(State to, TransitionConditionDelegate condition)
{
transitions.Add(new Transition(to, condition));
return this;
}

/// <summary>
/// Adds a state this state will always transition to
/// </summary>
/// <param name="to">state to always transition to</param>
/// <remarks>Transition conditions are evaulated in the order they are added.</remarks>
public void AlwaysTransitionTo(State to)
{
transitions.Add(new Transition(to));
}

internal State RunAndGetNextState(int delay, IDictionary<string, dynamic> vars)
{
Enter(vars);
Expand Down Expand Up @@ -94,5 +125,10 @@ void IDisposable.Dispose()
Dispose(disposing: true);
GC.SuppressFinalize(this);
}

public override string ToString()
{
return Name ?? GetType().Name[..GetType().Name.LastIndexOf("State")];
}
}
}
10 changes: 10 additions & 0 deletions StateMachine/StateEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace RoadieRichStateMachine
{
public delegate void StateEventHandler(object sender, StateEventArgs e);
public class StateEventArgs
{
State State { get; }

public StateEventArgs(State state) { State = state; }
}
}
18 changes: 17 additions & 1 deletion StateMachine/StateMachine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,21 @@ public class StateMachine : IDisposable
/// </summary>
public State InitialState { get; set; } = ExitState;

/// <summary>
/// Time to pause between executions of <see cref="State.Inner(IDictionary{string, dynamic})"/>
/// </summary>
public int Delay { get; set; } = 0;

/// <summary>
/// Raised before a State is started
/// </summary>
public event StateEventHandler? StateStarting;

/// <summary>
/// Raised when a State has finished
/// </summary>
public event StateEventHandler? StateFinished;

/// <summary>
/// If a state's <see cref="Transition"/> points to this state, the state machine is terminated.
/// </summary>
Expand All @@ -30,7 +43,10 @@ public void Run(IDictionary<string, dynamic>? vars = null)

while (state != ExitState)
{
state = state.RunAndGetNextState(Delay, myVars);
StateStarting?.Invoke(this, new StateEventArgs(state));
State nextState = state.RunAndGetNextState(Delay, myVars);
StateFinished?.Invoke(this, new StateEventArgs(state));
state = nextState;
}
}

Expand Down
12 changes: 10 additions & 2 deletions StateMachine/Transition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@ internal class Transition : IDisposable
{
private bool disposedValue;

internal Transition(State to, TransitionConditionDelegate? condition)
public Transition(State to)
{
To = to;
Condition = null;
}

internal Transition(State to, TransitionConditionDelegate condition) : this(to, (vars) => condition()) { }

internal Transition(State to, TransitionConditionDelegateWithVars condition) : this(to)
{
Condition = condition;
}

public State To { get; }
public TransitionConditionDelegate? Condition { get; }
public TransitionConditionDelegateWithVars? Condition { get; }
public bool CheckCondition(IDictionary<string, dynamic> vars)
{
return Condition == null || Condition(vars);
Expand Down
26 changes: 13 additions & 13 deletions UnitTests/FunctionStateUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ public void FunctionStateRunsInnerFunctions()
{
var b = false;

var funcState = new FunctionState((vars) => b = true);
var funcState = new FunctionState(() => b = true);

funcState.AddTransitionTo(StateMachine.ExitState, null);
funcState.AlwaysTransitionTo(StateMachine.ExitState);

using var sm = new StateMachine();

Expand All @@ -36,9 +36,9 @@ public void FunctionStateRunsEntryFunction()
{
var aBool = false;

var funcState = new FunctionState((vars) => aBool = true, (vars) => { }, null);
var funcState = new FunctionState(() => aBool = true, () => { }, null);

funcState.AddTransitionTo(StateMachine.ExitState, null);
funcState.AlwaysTransitionTo(StateMachine.ExitState);

using var sm = new StateMachine();

Expand All @@ -53,9 +53,9 @@ public void FunctionStateRunsExitFunction()
{
var aBool = false;

var funcState = new FunctionState(null, (vars) => { }, (vars) => aBool = true);
var funcState = new FunctionState(null, () => { }, () => aBool = true);

funcState.AddTransitionTo(StateMachine.ExitState, null);
funcState.AlwaysTransitionTo(StateMachine.ExitState);

using var sm = new StateMachine();

Expand All @@ -66,13 +66,13 @@ public void FunctionStateRunsExitFunction()
}

[TestMethod]
public void InnerIsOnlyCalledRepeatedly()
public void InnerIsCalledRepeatedly()
{
var anInt = 0;

var funcState = new FunctionState((vars) => anInt++);
var funcState = new FunctionState(() => anInt++);

funcState.AddTransitionTo(StateMachine.ExitState, (vars) => anInt > 10);
funcState.AddTransitionTo(StateMachine.ExitState, () => anInt > 10);

using var sm = new StateMachine();

Expand All @@ -88,9 +88,9 @@ public void EnterIsOnlyCalledOnce()
var anInt = 0;
var anotherInt = 0;

var funcState = new FunctionState((vars) => anInt++, (vars) => anotherInt++, null);
var funcState = new FunctionState(() => anInt++, () => anotherInt++, null);

funcState.AddTransitionTo(StateMachine.ExitState, (vars) => anotherInt > 10);
funcState.AddTransitionTo(StateMachine.ExitState, () => anotherInt > 10);

using var sm = new StateMachine();

Expand All @@ -107,9 +107,9 @@ public void ExitIsOnlyCalledOnce()
var anInt = 0;
var anotherInt = 0;

var funcState = new FunctionState(null, (vars) => anotherInt++, (vars) => anInt++);
var funcState = new FunctionState(null, () => anotherInt++, () => anInt++);

funcState.AddTransitionTo(StateMachine.ExitState, (vars) => anotherInt > 10);
funcState.AddTransitionTo(StateMachine.ExitState, () => anotherInt > 10);

using var sm = new StateMachine();

Expand Down
Loading