diff --git a/README.md b/README.md
index c6df671..330a5af 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,9 @@
# Welcome to state.cs
-The current stable release is 5.1.3.
+The current stable release is 5.2.0.
If you're using state.cs I'd love to hear about it; please e-mail me at mesmo@steelbreeze.net
-## Version 5.0 notes
-Version 5.0 is a complete re-write of the state machine. It makes large use of multicast delegates to pre-evaulate the steps required to perform state transitions. This leads to considerable performance benefits no evaluations are made relating to the structure of the model at runtime as it does not change at runtime.
-
-
## Introduction
State.js provides a hierarchical state machine capable of managing orthogonal regions; a variety of pseudo state kinds are implemented including initial, shallow & deep history, choice, junction and entry & exit points.
diff --git a/RELEASES.md b/RELEASES.md
new file mode 100644
index 0000000..4f1a77e
--- /dev/null
+++ b/RELEASES.md
@@ -0,0 +1,21 @@
+# Release notes
+
+# Version 5.0.0
+Version 5.0 is a complete re-write of the state machine. It makes large use of multicast delegates to pre-evaulate the steps required to perform state transitions. This leads to considerable performance benefits no evaluations are made relating to the structure of the model at runtime as it does not change at runtime.
+
+# Version 5.2.0
+Renamed IContext to IActiveStateConfiguration
+
+Renamed Context to StateMachineInstance
+
+Renamed ContextBase to StateMachineInstanceBase
+
+Removed XContext and XContextBase
+
+Added Create... extension methods to main classes to faciltate simpler model building.
+
+Changed StateMachine to inherit from State
+
+Added a visitor pattern implementation
+
+Moved the bootstrapping to use the visitor
\ No newline at end of file
diff --git a/examples/Player.cs b/examples/Player.cs
index e23a2cc..934edd1 100644
--- a/examples/Player.cs
+++ b/examples/Player.cs
@@ -6,8 +6,7 @@
using System;
using System.Xml.Linq;
-namespace Steelbreeze.Behavior.StateMachines.Examples
-{
+namespace Steelbreeze.Behavior.StateMachines.Examples {
///
/// A controller for a simple cassette player
///
@@ -15,76 +14,72 @@ namespace Steelbreeze.Behavior.StateMachines.Examples
/// The player now inherits from a Region, so can be used in isolation, or as a region in a larger device.
/// The player now implements an 'operational' composite state so the off command can be used while in any sub-state.
///
- public class Player
- {
- static void Main() {
- // create the state machine model
- var model = new StateMachine( "player" );
- var initial = new PseudoState( "initial", model, PseudoStateKind.Initial );
- var operational = new State( "operational", model );
- var choice = new PseudoState( "choice", model, PseudoStateKind.Choice );
- var final = new FinalState( "final", model );
+ public class Player {
+ public static StateMachine Model;
- var history = new PseudoState( "history", operational, PseudoStateKind.DeepHistory );
- var stopped = new State( "stopped", operational );
- var active = new State( "active", operational ).Entry( EngageHead ).Exit( DisengageHead );
+ static Player () {
+ // create the state machine model
+ Model = new StateMachine ("model");
- var running = new State( "running", active ).Entry( StartMotor ).Exit( StopMotor );
- var paused = new State( "paused", active );
+ // create the vertices within the model
+ var initial = Model.CreatePseudoState ("initial", PseudoStateKind.Initial);
+ var operational = Model.CreateState ("operational");
+ var choice = Model.CreatePseudoState ("choice", PseudoStateKind.Choice);
+ var final = Model.CreateFinalState ("final");
+ var history = operational.CreatePseudoState ("history", PseudoStateKind.DeepHistory);
+ var stopped = operational.CreateState ("stopped");
+ var active = operational.CreateState ("active").Entry (EngageHead).Exit (DisengageHead);
+ var running = active.CreateState ("running").Entry (StartMotor).Exit (StopMotor);
+ var paused = active.CreateState ("paused");
// create the transitions between vertices of the model
- initial.To( operational ).Effect( DisengageHead, StartMotor );
- history.To( stopped );
- stopped.To( running ).When( command => command == "play" );
- active.To( stopped ).When( command => command == "stop" );
- running.To( paused ).When( command => command == "pause" );
- paused.To( running ).When( command => command == "play" );
- operational.To( final ).When( command => command == "off" );
- operational.To( choice ).When( command => command == "rand" );
- choice.To( operational ).Effect( () => Console.WriteLine( "- transition A back to operational" ) );
- choice.To( operational ).Effect( () => Console.WriteLine( "- transition B back to operational" ) );
-
- // add an internal transition to show the current state at any time
- operational.When( command => command == "current" ).Effect( state => Console.WriteLine( state.XElement ) );
+ initial.To (operational).Effect (DisengageHead, StartMotor);
+ history.To (stopped);
+ stopped.To (running).When (command => command == "play");
+ active.To (stopped).When (command => command == "stop");
+ running.To (paused).When (command => command == "pause");
+ paused.To (running).When (command => command == "play");
+ operational.To (final).When (command => command == "off");
+ operational.To (choice).When (command => command == "rand");
+ choice.To (operational).Effect (() => Console.WriteLine ("- transition A back to operational"));
+ choice.To (operational).Effect (() => Console.WriteLine ("- transition B back to operational"));
+ }
+ static void Main () {
// create an instance of the state machine state
- var context = new XContext();
+ var instance = new StateMachineInstance ("player");
// initialises the state machine state (enters the region for the first time, causing transition from the initial PseudoState)
- model.Initialise( context );
+ Model.Initialise (instance);
// main event loop
- while( !model.IsComplete( context ) ) {
+ while (!Model.IsComplete (instance)) {
// write a prompt
- Console.Write( "alamo> " );
+ Console.Write ("alamo> ");
// process lines read from the console
- if( !model.Evaluate( Console.ReadLine(), context ) )
- Console.WriteLine( "unknown command" );
+ if (!Model.Evaluate (Console.ReadLine (), instance))
+ Console.WriteLine ("unknown command");
}
- Console.WriteLine( "Press any key to quit" );
- Console.ReadKey();
+ Console.WriteLine ("Press any key to quit");
+ Console.ReadKey ();
}
- private static void EngageHead()
- {
- Console.WriteLine( "- engaging head" );
+ private static void EngageHead () {
+ Console.WriteLine ("- engaging head");
}
- private static void DisengageHead()
- {
- Console.WriteLine( "- disengaging head" );
+ private static void DisengageHead () {
+ Console.WriteLine ("- disengaging head");
}
- private static void StartMotor()
- {
- Console.WriteLine( "- starting motor" );
+ private static void StartMotor () {
+ Console.WriteLine ("- starting motor");
}
- private static void StopMotor()
- {
- Console.WriteLine( "- stopping motor" );
+ private static void StopMotor () {
+ Console.WriteLine ("- stopping motor");
}
}
}
\ No newline at end of file
diff --git a/src/Bootstrap.cs b/src/Bootstrap.cs
new file mode 100644
index 0000000..3d7f46c
--- /dev/null
+++ b/src/Bootstrap.cs
@@ -0,0 +1,202 @@
+/* State v5 finite state machine library
+ * http://www.steelbreeze.net/state.cs
+ * Copyright (c) 2014-5 Steelbreeze Limited
+ * Licensed under MIT and GPL v3 licences
+ */
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+namespace Steelbreeze.Behavior.StateMachines {
+ ///
+ /// Bootstraps a state machine model.
+ ///
+ /// The type of the state machine instnce.
+ /// Bootstrapping a state machine model pre-determines all operations and evaluations required when traversing a transition; the results are then cached within the transition.
+ internal class Bootstrap : Visitor where TInstance : class, IActiveStateConfiguration {
+ private class Actions {
+ internal Action Leave;
+ internal Action BeginEnter;
+ internal Action EndEnter;
+ internal Action Enter;
+ }
+
+ ///
+ /// Cache of the behaviour required within the state machine model.
+ ///
+ private Dictionary, Actions> behaviour;
+
+ ///
+ /// Returns the behaviour for a given element within the state machine model.
+ ///
+ /// The element to return the behaviour for.
+ /// The state machine behaviour for a given model element.
+ private Actions Behaviour (Element element) {
+ Actions result = null;
+
+ if (!behaviour.TryGetValue (element, out result))
+ behaviour.Add (element, result = new Actions ());
+
+ return result;
+ }
+
+ public override void VisitElement (Element element, Boolean deepHistoryAbove) {
+#if DEBUG
+ Behaviour (element).Leave += (message, instance, history) => Console.WriteLine ("{0} leave {1}", instance, element);
+ Behaviour (element).BeginEnter += (message, instance, history) => Console.WriteLine ("{0} enter {1}", instance, element);
+#endif
+ }
+
+ public override void VisitRegion (Region region, Boolean deepHistoryAbove) {
+ foreach (var vertex in region.Vertices)
+ vertex.Accept (this, deepHistoryAbove || (region.Initial != null && region.Initial.Kind == PseudoStateKind.DeepHistory));
+
+ Behaviour (region).Leave += (message, instance, history) => {
+ State current = instance[ region ];
+
+ if (Behaviour (current).Leave != null) {
+ Behaviour (current).Leave (message, instance, history);
+ }
+ };
+
+ if (deepHistoryAbove || region.Initial == null || region.Initial.IsHistory) {
+ Behaviour (region).EndEnter += (message, instance, history) => {
+ Vertex initial = region.Initial;
+
+ if (history || region.Initial.IsHistory) {
+ initial = instance[ region ];
+
+ if (initial == null)
+ initial = region.Initial;
+ }
+
+ Behaviour (initial).Enter (message, instance, history || region.Initial.Kind == PseudoStateKind.DeepHistory);
+ };
+ } else
+ Behaviour (region).EndEnter += Behaviour (region.Initial).Enter;
+
+ this.VisitElement (region, deepHistoryAbove);
+
+ Behaviour (region).Enter = Behaviour (region).BeginEnter + Behaviour (region).EndEnter;
+ }
+
+ public override void VisitVertex (Vertex vertex, Boolean deepHistoryAbove) {
+ this.VisitElement (vertex, deepHistoryAbove);
+
+ Behaviour (vertex).EndEnter += vertex.Completion;
+ Behaviour (vertex).Enter = Behaviour (vertex).BeginEnter + Behaviour (vertex).EndEnter;
+ }
+
+ public override void VisitPseudoState (PseudoState pseudoState, Boolean deepHistoryAbove) {
+ this.VisitVertex (pseudoState, deepHistoryAbove);
+
+ if (pseudoState.Kind == PseudoStateKind.Terminate)
+ Behaviour (pseudoState).Enter += (message, instance, history) => instance.IsTerminated = true;
+ }
+
+ public override void VisitState (State state, Boolean deepHistoryAbove) {
+ foreach (var region in state.Regions) {
+ region.Accept (this, deepHistoryAbove);
+
+ Behaviour (state).Leave += Behaviour (region).Leave;
+ Behaviour (state).EndEnter += Behaviour (region).Enter;
+ }
+
+ this.VisitVertex (state, deepHistoryAbove);
+
+ if (state.exit != null)
+ Behaviour (state).Leave += state.OnExit;
+
+ if (state.entry != null)
+ Behaviour (state).BeginEnter += state.OnEntry;
+
+ Behaviour (state).BeginEnter += (message, instance, history) => {
+ if (state.Region != null)
+ instance[ state.Region ] = state;
+ };
+
+ Behaviour (state).Enter = Behaviour (state).BeginEnter + Behaviour (state).EndEnter;
+ }
+
+ public override void VisitStateMachine (StateMachine stateMachine, bool param) {
+ behaviour = new Dictionary, Actions> ();
+
+ base.VisitStateMachine (stateMachine, param);
+
+ stateMachine.initialise = Behaviour (stateMachine).Enter;
+ }
+
+ public override void VisitTransition (Transition transition, Boolean deepHistoryAbove) {
+ // reset the traverse operation to cater for re-initialisation
+ transition.Traverse = null;
+
+ // internal transitions
+ if (transition.Target == null) {
+ // just perform the transition effect; no actual transition
+ if (transition.effect != null)
+ transition.Traverse += transition.OnEffect;
+
+ // local transitions
+ } else if (transition.Target.Region == transition.Source.Region) {
+ // leave the source
+ transition.Traverse += Behaviour (transition.Source).Leave;
+
+ // perform the transition effect
+ if (transition.effect != null)
+ transition.Traverse += transition.OnEffect;
+
+ // enter the target
+ transition.Traverse += Behaviour (transition.Target).Enter;
+
+ // complex (external) transitions
+ } else {
+ var sourceAncestors = transition.Source.Ancestors;
+ var targetAncestors = transition.Target.Ancestors;
+ var sourceAncestorsCount = sourceAncestors.Count ();
+ var targetAncestorsCount = targetAncestors.Count ();
+ int i = 0, l = Math.Min (sourceAncestorsCount, sourceAncestorsCount);
+
+ // find the index of the first uncommon ancestor
+ while ((i < l) && sourceAncestors.ElementAt (i) == targetAncestors.ElementAt (i)) ++i;
+
+ // validation rule (not in the UML spec currently)
+ Trace.Assert (sourceAncestors.ElementAt (i) is Region == false, "Transitions may not cross sibling orthogonal regions");
+
+ // leave the first uncommon ancestor
+ transition.Traverse = Behaviour (i < sourceAncestorsCount ? sourceAncestors.ElementAt (i) : transition.Source).Leave;
+
+ // perform the transition effect
+ if (transition.effect != null)
+ transition.Traverse += transition.OnEffect;
+
+ // edge case when transitioning to a state in the vertex ancestry
+ if (i >= targetAncestorsCount)
+ transition.Traverse += Behaviour (transition.Target).BeginEnter;
+
+ // enter the target ancestry
+ while (i < targetAncestorsCount) {
+ var element = targetAncestors.ElementAt (i++);
+ var next = i < targetAncestorsCount ? targetAncestors.ElementAt (i) : null;
+
+ transition.Traverse += Behaviour (element).BeginEnter;
+
+ if (element is State) {
+ var state = element as State;
+
+ if (state.IsOrthogonal) {
+ foreach (var region in state.Regions) {
+ if (region != next) {
+ transition.Traverse += Behaviour (region).Enter;
+ }
+ }
+ }
+ }
+ }
+
+ // trigger cascade
+ transition.Traverse += Behaviour (transition.Target).EndEnter;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DictionaryContext.cs b/src/DictionaryContext.cs
deleted file mode 100644
index 36ea8e9..0000000
--- a/src/DictionaryContext.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-/* State v5 finite state machine library
- * http://www.steelbreeze.net/state.cs
- * Copyright (c) 2014-5 Steelbreeze Limited
- * Licensed under MIT and GPL v3 licences
- */
-using System;
-using System.Collections.Generic;
-
-namespace Steelbreeze.Behavior.StateMachines {
- ///
- /// A simple sample of an object to extend as a base for a state machine context object.
- ///
- public class DictionaryContext : DictionaryContextBase { }
-}
\ No newline at end of file
diff --git a/src/Element.cs b/src/Element.cs
index 6cd22ba..e385028 100644
--- a/src/Element.cs
+++ b/src/Element.cs
@@ -10,8 +10,9 @@ namespace Steelbreeze.Behavior.StateMachines {
///
/// A structural element of a state machine model.
///
- /// The type of the state machine instance.
- public abstract class Element where TContext : IContext {
+ /// The type of the state machine instance.
+ public abstract class Element where TInstance : class, IActiveStateConfiguration {
+ #region Static members
///
/// The string used to seperate names within a fully qualified namespace.
///
@@ -20,9 +21,10 @@ public abstract class Element where TContext : IContext {
///
/// Initialises the static members of NamedElement.
///
- static Element() {
+ static Element () {
NamespaceSeperator = ".";
}
+ #endregion
///
/// The name of the NamedElement
@@ -38,72 +40,73 @@ static Element() {
public readonly String QualifiedName;
///
- /// The name of the type without generic considerations
+ /// Creates a new instance of a subtype of the Element class.
///
- public abstract String Type { get; }
-
- ///
- /// Returns the elements parent.
- ///
- public abstract Element Parent { get; }
+ /// The name of the element.
+ /// The parent of the element.
+ internal protected Element (String name, Element parent = null) {
+ this.Name = name;
+ this.QualifiedName = parent != null ? parent.QualifiedName + NamespaceSeperator + name : name;
+ }
///
- /// The parent state machine that this element forms a part of.
+ /// Returns the elements parent element.
///
- public StateMachine Root { get; protected set; }
+ public abstract Element Parent { get; }
///
- /// Returns the elements ancestors.
+ /// Returns the state machine model root element.
///
- public IEnumerable> Ancestors { get { if( this.Parent != null ) foreach( var namedElement in this.Parent.Ancestors ) yield return namedElement; yield return this; } } // yield! please...
-
- internal Action Leave;
- internal Action BeginEnter;
- internal Action EndEnter;
- internal Action Enter;
-
- internal Element( String name, Element parent ) {
- this.Name = name;
- this.QualifiedName = parent != null ? parent.QualifiedName + NamespaceSeperator + name : name;
-
- if( parent != null )
- this.Root = parent.Root;
+ public virtual StateMachine Root {
+ get {
+ return this.Parent.Root;
+ }
}
- internal void Reset() {
- this.Leave = null;
- this.BeginEnter = null;
- this.EndEnter = null;
- this.Enter = null;
+ ///
+ /// Returns the elements ancestors back from the state machines root element.
+ ///
+ public IEnumerable> Ancestors {
+ get {
+ if (this.Parent != null)
+ foreach (var namedElement in this.Parent.Ancestors) // yield! would be great...
+ yield return namedElement;
+
+ yield return this;
+ }
}
///
/// Tests the element to determine if it is part of the current active state confuguration
///
- /// The state machine context.
+ /// The state machine instance.
/// True if the element is active.
- internal protected abstract Boolean IsActive( IContext context );
+ /// An element is part of the active state configuration if it has been entered but not yet exited.
+ public abstract Boolean IsActive (TInstance instance);
- internal virtual void BootstrapElement( Boolean deepHistoryAbove ) {
-#if DEBUG
- this.Leave += ( message, context, history ) => Console.WriteLine( "{0} leave {1}", context, this.QualifiedName );
- this.BeginEnter += ( message, context, history ) => Console.WriteLine( "{0} enter {1}", context, this.QualifiedName );
-#endif
- this.Enter = this.BeginEnter + this.EndEnter;
- }
-
- internal abstract void BootstrapTransitions();
+ ///
+ /// Tests the element to determine if it is deemed to be complete.
+ ///
+ /// The state machine instance.
+ /// True if the element is complete.
+ public abstract Boolean IsComplete (TInstance instance);
- internal virtual void BootstrapEnter( ref Action traverse, Element next ) {
- traverse += this.BeginEnter;
- }
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
+ ///
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
+ ///
+ public abstract void Accept (Visitor visitor, TParam param);
///
/// Returns a string representation of the element.
///
/// Returns the fully qualified name of the element.
- public override String ToString() {
+ public override String ToString () {
return this.QualifiedName;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Extensions.cs b/src/Extensions.cs
new file mode 100644
index 0000000..ab790d8
--- /dev/null
+++ b/src/Extensions.cs
@@ -0,0 +1,95 @@
+/* State v5 finite state machine library
+ * http://www.steelbreeze.net/state.cs
+ * Copyright (c) 2014-5 Steelbreeze Limited
+ * Licensed under MIT and GPL v3 licences
+ */
+using System;
+
+namespace Steelbreeze.Behavior.StateMachines {
+ ///
+ /// Set of extension methods to facilitate easier model building
+ ///
+ public static class Extensions {
+ ///
+ /// Creates a new instance of the Region class under the parent State.
+ ///
+ /// The type of the state machine instance.
+ /// The parent state of the new Region.
+ /// The name of the Region.
+ /// The newly created Region.
+ public static Region CreateRegion (this State state, String name) where TInstance : class, IActiveStateConfiguration {
+ return new Region (name, state);
+ }
+
+ ///
+ /// Creates a new instance of the PseudoState class under the parent Region.
+ ///
+ /// The type of the state machine instance.
+ /// The parent region of the new PseudoState.
+ /// The name of the PseudoState.
+ /// The kind of the PseudoState.
+ /// The newly created PseudoState.
+ public static PseudoState CreatePseudoState (this Region region, String name, PseudoStateKind kind = PseudoStateKind.Initial) where TInstance : class, IActiveStateConfiguration {
+ return new PseudoState (name, region, kind);
+ }
+
+ ///
+ /// Creates a new instance of the PseudoState class under the parent State.
+ ///
+ /// The type of the state machine instance.
+ /// The parent state of the new PseudoState.
+ /// The name of the PseudoState.
+ /// The kind of the PseudoState.
+ /// The newly created PseudoState.
+ /// Note that the PseudoState will be created under the parent state's default region.
+ public static PseudoState CreatePseudoState (this State state, String name, PseudoStateKind kind = PseudoStateKind.Initial) where TInstance : class, IActiveStateConfiguration {
+ return new PseudoState (name, state, kind);
+ }
+
+ ///
+ /// Creates a new instance of the State class under the parent Region.
+ ///
+ /// The type of the state machine instance.
+ /// The parent region of the new State.
+ /// The name of the State.
+ /// The newly created State.
+ public static State CreateState (this Region region, String name) where TInstance : class, IActiveStateConfiguration {
+ return new State (name, region);
+ }
+
+ ///
+ /// Creates a new instance of the State class under the parent State.
+ ///
+ /// The type of the state machine instance.
+ /// The parent state of the new State.
+ /// The name of the State.
+ /// The newly created State.
+ /// Note that the State will be created under the parent state's default region.
+ public static State CreateState (this State state, String name) where TInstance : class, IActiveStateConfiguration {
+ return new State (name, state);
+ }
+
+ ///
+ /// Creates a new instance of the FinalState class under the parent Region.
+ ///
+ /// The type of the state machine instance.
+ /// The parent region of the new FinalState.
+ /// The name of the FinalState.
+ /// The newly created FinalState.
+ public static FinalState CreateFinalState (this Region region, String name) where TInstance : class, IActiveStateConfiguration {
+ return new FinalState (name, region);
+ }
+
+ ///
+ /// Creates a new instance of the FinalState class under the parent State.
+ ///
+ /// The type of the state machine instance.
+ /// The parent state of the new FinalState.
+ /// The name of the FinalState.
+ /// The newly created FinalState.
+ /// Note that the FinalState will be created under the parent state's default region.
+ public static FinalState CreateFinalState (this State state, String name) where TInstance : class, IActiveStateConfiguration {
+ return new FinalState (name, state);
+ }
+ }
+}
diff --git a/src/FinalState.cs b/src/FinalState.cs
index a76f4f5..1ab4de5 100644
--- a/src/FinalState.cs
+++ b/src/FinalState.cs
@@ -10,44 +10,73 @@ namespace Steelbreeze.Behavior.StateMachines {
///
/// A special kind of State signifying that its parent Region is completed.
///
- /// The type of the state machine context.
+ /// The type of the state machine instance.
/// To be complete, final states cannot have any child model structure beneath them (Region's) or outgoing transitions.
- public sealed class FinalState : State where TContext : IContext {
- ///
- /// The name of the type without generic considerations
- ///
- public override string Type { get { return "finalState"; } }
-
+ public sealed class FinalState : State where TInstance : class, IActiveStateConfiguration {
///
/// Initializes a new instance of a FinalState within the parent Region.
///
/// The name of the FinalState.
/// The parent Region of the FinalState.
- public FinalState( String name, Region parent ) : base( name, parent, Transition.Terminate ){
- Trace.Assert( name != null, "FinalStates must have a name" );
- Trace.Assert( parent != null, "FinalStates must have a parent Region" );
+ public FinalState (String name, Region parent)
+ : base (name, parent) {
+ Trace.Assert (name != null, "FinalStates must have a name");
+ Trace.Assert (parent != null, "FinalStates must have a parent Region");
}
- // override State's implementation of IsComplete
- internal override Boolean IsComplete( TContext context ) {
+ ///
+ /// Tests the final state to determine if it is deemed to be complete.
+ ///
+ /// The state machine instance.
+ /// True if the final state is complete
+ /// FinalStates are always deemed to be complete.
+ public override Boolean IsComplete (TInstance instance) {
return true;
}
- // do not allow FinalState's to become composite
- internal override void Add( Region region ) {
- throw new NotSupportedException( "A FinalState may not be the parent of a Region" );
+ ///
+ /// Adds a region to the state.
+ ///
+ /// The region to add to the state.
+ /// Final states cannot be composite states so Add will always throw this exception.
+ internal override void Add (Region region) {
+ throw new NotSupportedException ("A FinalState may not be the parent of a Region");
}
///
/// Creates a new transition from this Vertex.
///
/// The Vertex to transition to.
- /// An intance of the Transition class.
+ /// This implementation of To will not return a value.
+ ///
+ ///
+ /// As final states may not have outbound transitions, this implementation will always throw an exception.
+ ///
+ public override Transition To (Vertex target) {
+ throw new NotSupportedException ("A FinalState may not have outbound transitions");
+ }
+
+ ///
+ /// Selects a transition for a given message and state machine instance for the final state.
+ ///
+ /// The message that may trigger a transition.
+ /// The state machine instance.
+ /// Always returns null.
+ /// As a final state cannot have transitions, this implementation of Select will always return null.
+ protected internal override Transition Select (object message, TInstance instance) {
+ return null;
+ }
+
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
///
- /// FinalState's are not permitted to have outgoing transitions; this method will therefore throw an exception.
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
///
- public override Transition To( Vertex target ) {
- throw new NotSupportedException( "Transitions my not originate from a FinalState" );
+ public override void Accept (Visitor visitor, TParam param) {
+ visitor.VisitFinalState (this, param);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/IContext.cs b/src/IActiveStateConfiguration.cs
similarity index 68%
rename from src/IContext.cs
rename to src/IActiveStateConfiguration.cs
index 7f5ab63..0a37ceb 100644
--- a/src/IContext.cs
+++ b/src/IActiveStateConfiguration.cs
@@ -4,20 +4,19 @@
* Licensed under MIT and GPL v3 licences
*/
using System;
-using System.Linq;
namespace Steelbreeze.Behavior.StateMachines {
///
- /// The interface required of state machine context objects.
+ /// The interface required of state machine instances to manage the active state configuration.
///
- /// The type of the derived class.
+ /// The type of the derived class.
///
/// By passing the type of the derived class into this base, it allows the callbacks generated by the state machine to pass the fully typed derived class.
/// This interface is intended for internal use only.
///
- public interface IContext where TContext : IContext {
+ public interface IActiveStateConfiguration where TInstance : class, IActiveStateConfiguration {
///
- /// Indicates that the state machine context has been terminated.
+ /// Indicates that the state machine intance has been terminated.
///
/// A state machine is only deemed terminated if a transitions target is a Terminate PseudoState.
Boolean IsTerminated { get; set; }
@@ -27,6 +26,6 @@ public interface IContext where TContext : IContext {
///
/// The Regions to get/set the current state for.
/// The current state of the Region or null if no value has previously been set.
- Vertex this[ Region region ] { get; set; }
+ State this[ Region region ] { get; set; }
}
-}
+}
\ No newline at end of file
diff --git a/src/PseudoState.cs b/src/PseudoState.cs
index 3fd7fd4..d98900e 100644
--- a/src/PseudoState.cs
+++ b/src/PseudoState.cs
@@ -11,16 +11,17 @@ namespace Steelbreeze.Behavior.StateMachines {
///
/// A PseudoState is an abstraction that encompasses different types of transient vertices in the state machine.
///
- /// The type of the state machine instance.
+ /// The type of the state machine instance.
///
/// Pseudostates are typically used to connect multiple transitions into more complex state transitions path.
///
- public sealed class PseudoState : Vertex where TContext : IContext {
+ public sealed class PseudoState : Vertex where TInstance : class, IActiveStateConfiguration {
+ #region Static members
///
- /// The name of the type without generic considerations
+ /// For use in Choice pseudo states where multiple outbound transitions guards evaluate true.
///
- public override string Type { get { return "pseudoState"; } }
-
+ private static readonly Random random = new Random ();
+ #endregion
///
/// Determines the precise type of the Pseudostate.
///
@@ -29,7 +30,14 @@ public sealed class PseudoState : Vertex where TContext : IC
///
public readonly PseudoStateKind Kind;
+ ///
+ /// True if the pseudo state is a DeepHistory or ShallowHistory kinds.
+ ///
internal Boolean IsHistory { get { return this.Kind == PseudoStateKind.DeepHistory || this.Kind == PseudoStateKind.ShallowHistory; } }
+
+ ///
+ /// True if the pseudo state is an Initial, DeepHistory or ShallowHistory kinds.
+ ///
internal Boolean IsInitial { get { return this.Kind == PseudoStateKind.Initial || this.IsHistory; } }
///
@@ -41,32 +49,88 @@ public sealed class PseudoState : Vertex where TContext : IC
///
/// The kind of the PseudoState dictates is use and semantics; see the documentation of PseudoStateKind.
///
- public PseudoState( String name, Region parent, PseudoStateKind kind = PseudoStateKind.Initial )
- : base( name, parent, Transition.PseudoState( kind ) ) {
- Trace.Assert( name != null, "PseudoStates must have a name" );
- Trace.Assert( parent != null, "PseudoStates must have a parent Region" );
+ public PseudoState (String name, Region parent, PseudoStateKind kind = PseudoStateKind.Initial)
+ : base (name, parent) {
+ Trace.Assert (name != null, "PseudoStates must have a name");
+ Trace.Assert (parent != null, "PseudoStates must have a parent Region");
this.Kind = kind;
- if( this.IsInitial )
+ if (this.IsInitial)
this.Region.Initial = this;
}
///
- /// Creates a new transition from this PseudoState.
+ /// Tests the vertex to determine if it is part of the current active state confuguration
///
- /// The Vertex to transition to.
- /// An intance of the Transition class.
- public override Transition To( Vertex target ) {
- Trace.Assert( target != null, "Transitions from PseudoStates must have a target" );
- return base.To( target );
+ /// The state machine instance.
+ /// True if the element is active.
+ public override Boolean IsActive (TInstance instance) {
+ return this.Parent.IsActive (instance);
}
- internal override void BootstrapElement( bool deepHistoryAbove ) {
- base.BootstrapElement( deepHistoryAbove );
+ ///
+ /// Tests the pseudo state to determine if it is deemed to be complete.
+ ///
+ /// The state machine instance.
+ /// True if the pseudo state is complete.
+ /// Pseudo states are always deemed to be complete.
+ public override bool IsComplete (TInstance instance) {
+ return true;
+ }
+
+ ///
+ /// Selects a transition for a given message and state machine instance for the pseudo state.
+ ///
+ /// The message that may trigger a transition.
+ /// The state machine instance.
+ /// The selected transition.
+ /// This exception is thown if no transition is found from the pseudo state.
+ protected internal override Transition Select (object message, TInstance instance) {
+ switch (this.Kind) {
+ case PseudoStateKind.Initial:
+ case PseudoStateKind.DeepHistory:
+ case PseudoStateKind.ShallowHistory:
+ return this.Transitions.Single ();
+
+ case PseudoStateKind.Choice:
+ case PseudoStateKind.Junction:
+ var transitions = this.Transitions.Where (t => t.Predicate (message, instance));
- if( this.Kind == PseudoStateKind.Terminate )
- this.Enter += ( message, context, history ) => context.IsTerminated = true;
+ switch (transitions.Count ()) {
+ case 1:
+ return transitions.Single ();
+
+ case 0:
+ transitions = this.Transitions.Where (t => t.Predicate.Equals( Transition.IsElse));
+
+ if( transitions.Count() == 0 )
+ throw new Exception (String.Format ("No transitions found from {0} for message {1} on instance {2}", this, message, instance));
+
+ return transitions.Single ();
+
+ default:
+ if( this.Kind == PseudoStateKind.Junction )
+ throw new Exception( String.Format( "Multiple transitions possble from {0} for message {1} on instance {2}", this, message, instance));
+
+ return transitions.ElementAt (random.Next (transitions.Count ()));
+ }
+
+ default:
+ return null;
+ }
+ }
+
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
+ ///
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
+ ///
+ public override void Accept (Visitor visitor, TParam param) {
+ visitor.VisitPseudoState (this, param);
}
}
}
\ No newline at end of file
diff --git a/src/PseudoStateKind.cs b/src/PseudoStateKind.cs
index 820e483..0802a0c 100644
--- a/src/PseudoStateKind.cs
+++ b/src/PseudoStateKind.cs
@@ -24,10 +24,6 @@ public enum PseudoStateKind {
///
DeepHistory,
- // TODO: EntryPoint
-
- // TODO: ExitPoint
-
///
/// ShallowHistory represents the most recent active substate of its containing state (but not the substates of that substate).
/// A Region can have at most one shallow history vertex.
@@ -52,11 +48,11 @@ public enum PseudoStateKind {
/// Choice vertices should be distinguished from static branch points that are based on junction points.
///
Choice,
-
+
///
- /// Entering a Terminate pseudostate implies that the execution of this state machine by means of its context object is terminated.
+ /// Entering a Terminate pseudostate implies that the execution of this state machine instance is terminated.
/// The state machine does not exit any states nor does it perform any exit actions other than those associated with the transition leading to the terminate pseudostate.
///
Terminate
}
-}
+}
\ No newline at end of file
diff --git a/src/RELEASES.md b/src/RELEASES.md
new file mode 100644
index 0000000..dfe1e0c
--- /dev/null
+++ b/src/RELEASES.md
@@ -0,0 +1,21 @@
+# Release notes
+
+# Version 5.0.0
+Version 5.0 is a complete re-write of the state machine. It makes large use of multicast delegates to pre-evaulate the steps required to perform state transitions. This leads to considerable performance benefits no evaluations are made relating to the structure of the model at runtime as it does not change at runtime.
+
+# Version 5.2.0
+Renamed IContext to IActiveStateConfiguration: this is more in line with the terminology in the UML specification.
+
+Renamed Context to StateMachineInstance and ContextBase to StateMachineInstanceBase.
+
+Removed XContext and XContextBase. While an interesting example of another type of state machine instance information management, it wasn't fully serializable so of little interest going forward.
+
+Added Create... extension methods to main classes to faciltate simpler model building. This removes the need for many template parameters in model building.
+
+Changed StateMachine to inherit from State. This is intented for a potential future enhancement to allow one state machine to be used within another.
+
+Implemented a Visitor patten with the introcuction of the Visitor class and Accept methods on the Transition and all Element classes.
+
+Moved the bootstrapping code to use the visitor and refactored a little.
+
+Migrated array-based code to use HashSets for simpler, cleaner code.
diff --git a/src/Region.cs b/src/Region.cs
index b558887..b4e89a4 100644
--- a/src/Region.cs
+++ b/src/Region.cs
@@ -12,11 +12,11 @@ namespace Steelbreeze.Behavior.StateMachines {
///
/// A container of vertices within a state machine model.
///
- /// The type of the state machine instance.
+ /// The type of the state machine instance.
///
/// All state machines and composite states will contain at least one Region; orthogonal composite states will contain more than one.
///
- public sealed class Region : Element where TContext : IContext {
+ public sealed class Region : Element where TInstance : class, IActiveStateConfiguration {
#region Static members
///
/// The name used for default regions.
@@ -27,22 +27,10 @@ public sealed class Region : Element where TContext : IConte
public static String DefaultName { get; set; }
// Initialise the static members.
- static Region() {
+ static Region () {
DefaultName = "default";
}
- ///
- /// Provides an implicit conversion from a StateMachine to a Region, initialising a new instance of the Region class if required under this State.
- ///
- /// The StateMachine to return the default region for.
- /// The default Region of the StateMAchine.
- ///
- /// The default Region of a StateMachine is one that has the same name as defined by Region.DefaultName.
- ///
- public static implicit operator Region( StateMachine stateMachine ) {
- return ( stateMachine.regions == null ? null : stateMachine.regions.SingleOrDefault( r => r.Name == Region.DefaultName ) ) ?? new Region( Region.DefaultName, stateMachine );
- }
-
///
/// Provides an implicit conversion from a State to a Region, initialising a new instance of the Region class if required under this State.
///
@@ -51,51 +39,25 @@ public static implicit operator Region( StateMachine stateMa
///
/// The default Region of a composite State is one that has the same name as defined by Region.DefaultName.
///
- public static implicit operator Region( State state ) {
- if( state.regions != null ) {
- var region = state.regions.SingleOrDefault( r => r.Name == Region.DefaultName );
-
- if( region != null )
- return region;
- }
-
- return new Region( Region.DefaultName, state );
+ public static implicit operator Region (State state) {
+ return state.Regions.SingleOrDefault (r => r.Name == Region.DefaultName) ?? new Region (Region.DefaultName, state);
}
#endregion
///
- /// The name of the type without generic considerations
- ///
- public override string Type { get { return "region"; } }
-
- ///
- /// Returns the Region's parent element.
+ /// The initial stating state for the region.
///
- public override Element Parent { get { return this.parent; } }
+ /// Note that all regions do not have to have an initial state if all entry is via external transitions.
+ internal PseudoState Initial = null;
///
- /// The child Vertices
+ /// The vertices owned by this region.
///
- public IEnumerable> Vertices { get { return this.vertices; } }
-
- internal PseudoState Initial = null;
-
- private readonly HashSet> vertices = new HashSet>();
- private readonly Element parent;
+ private readonly HashSet> vertices = new HashSet> ();
///
- /// Initialises a new instance of the Region class within a StateMachine.
+ /// The regions parement state.
///
- /// The name of the Region.
- /// The parent StateMachine.
- public Region( String name, StateMachine parent )
- : base( name, parent ) {
- Trace.Assert( name != null, "Regions must have a name" );
- Trace.Assert( parent != null, "Regions must have a parent" );
-
- this.parent = parent;
-
- parent.Add( this );
- }
+ private readonly State parent;
///
/// Initialises a new instance of the Region class within a composite State.
@@ -105,61 +67,90 @@ public Region( String name, StateMachine parent )
///
/// Adding a Region to a State makes the State a composite State.
///
- public Region( String name, State parent )
- : base( name, parent ) {
- Trace.Assert( name != null, "Regions must have a name" );
- Trace.Assert( parent != null, "Regions must have a parent" );
+ public Region (String name, State parent)
+ : base (name, parent) {
+ Trace.Assert (name != null, "Regions must have a name");
+ Trace.Assert (parent != null, "Regions must have a parent");
- this.Root = parent.Root;
this.parent = parent;
- parent.Add( this );
+ parent.Add (this);
}
+
///
- /// Tests the Region to determine if it is part of the current active state confuguration
+ /// Returns the Region's parent element.
///
- /// The state machine context.
- /// True if the element is active.
- internal protected override Boolean IsActive( IContext context ) {
- return this.Parent.IsActive( context );
+ public override Element Parent {
+ get {
+ return this.parent;
+ }
}
- internal void Add( Vertex vertex ) {
- Trace.Assert( vertex != null, "Cannot add a null vertex" );
- Trace.Assert( this.vertices.Where( v => v.Name == vertex.Name ).Count() == 0, "Vertices must have a unique name within the scope of their parent Region" );
-
- this.vertices.Add( vertex );
-
- vertex.Root.Clean = false;
+ ///
+ /// The child Vertices
+ ///
+ public IEnumerable> Vertices {
+ get {
+ return this.vertices;
+ }
}
- internal Boolean IsComplete( TContext context ) {
- return context[ this ].IsFinal;
+ ///
+ /// Tests the Region to determine if it is part of the current active state confuguration
+ ///
+ /// The state machine instance.
+ /// True if the element is active.
+ public override Boolean IsActive (TInstance instance) {
+ return this.Parent.IsActive (instance);
}
- internal override void BootstrapElement( Boolean deepHistoryAbove ) {
- foreach( var vertex in this.vertices ) {
- vertex.Reset();
- vertex.BootstrapElement( deepHistoryAbove || ( this.Initial != null && this.Initial.Kind == PseudoStateKind.DeepHistory ) );
- }
+ ///
+ /// Adds a child vertex to the region
+ ///
+ ///
+ internal void Add (Vertex vertex) {
+ // some validation
+ Trace.Assert (vertex != null, "Cannot add a null vertex");
+ Trace.Assert (this.vertices.Where (v => v.Name == vertex.Name).Count () == 0, "Vertices must have a unique name within the scope of their parent Region");
- this.Leave += ( message, context, history ) => { var current = context[ this ]; if( current.Leave != null ) current.Leave( message, context, history ); };
+ // add the vertex
+ this.vertices.Add (vertex);
- if( deepHistoryAbove || this.Initial == null || this.Initial.IsHistory )
- this.EndEnter += ( message, context, history ) => ( history || this.Initial.IsHistory ? context[ this ] ?? this.Initial : this.Initial ).Enter( message, context, history || this.Initial.Kind == PseudoStateKind.DeepHistory );
- else this.EndEnter += this.Initial.Enter;
+ // invalidate the model
+ vertex.Root.Clean = false;
+ }
- base.BootstrapElement( deepHistoryAbove );
+ ///
+ /// Tests the region to determine if it is deemed to be complete.
+ ///
+ /// The state machine instance.
+ /// True if the region is complete.
+ /// A region is deemed to be complete if it current active state is complete.
+ public override Boolean IsComplete (TInstance instance) {
+ return instance[ this ].IsFinal;
}
- internal override void BootstrapTransitions() {
- foreach( var vertex in this.vertices )
- vertex.BootstrapTransitions();
+ ///
+ /// Evaluate a message to trigger a state transition.
+ ///
+ /// The messaege that may trigger a state transition.
+ /// The state machine instance.
+ /// True if a transition was triggered.
+ internal Boolean Evaluate (Object message, TInstance instance) {
+ return instance[ this ].Evaluate (message, instance);
}
- internal Boolean Evaluate( Object message, TContext context ) {
- return context[ this ].Evaluate( message, context );
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
+ ///
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
+ ///
+ public override void Accept (Visitor visitor, TParam param) {
+ visitor.VisitRegion (this, param);
}
}
}
\ No newline at end of file
diff --git a/src/State.cs b/src/State.cs
index 75e0ffa..dd02e61 100644
--- a/src/State.cs
+++ b/src/State.cs
@@ -12,15 +12,44 @@ namespace Steelbreeze.Behavior.StateMachines {
///
/// A state models a situation during which some (usually implicit) invariant condition holds.
///
- /// The type of the state machine instance.
+ /// The type of the state machine instance.
///
/// The invariant may represent a static situation such as an object waiting for some external event to occur.
///
- public class State : Vertex where TContext : IContext {
+ public class State : Vertex where TInstance : class, IActiveStateConfiguration {
///
- /// The name of the type without generic considerations
+ /// The child Regions where the State is composite.
+ ///
+ private readonly HashSet> regions = new HashSet>();
+
+ ///
+ /// The behaviour to execute when exiting the state.
+ ///
+ internal Action exit;
+
+ ///
+ /// The behaviour to execute when entering the state.
+ ///
+ internal Action entry;
+
+ ///
+ /// Creates a new instance of the State class.
+ ///
+ /// The name of the state.
+ /// The parent Region.
+ public State (String name, Region parent)
+ : base (name, parent) {
+ Trace.Assert (name != null, "States must have a name");
+ }
+
+ ///
+ /// The child Regions where the State is composite.
///
- public override string Type { get { return "state"; } }
+ public IEnumerable> Regions {
+ get {
+ return this.regions;
+ }
+ }
///
/// True if the State is a simple State.
@@ -28,7 +57,11 @@ public class State : Vertex where TContext : IContext
/// A simple State is one that has no child Regions.
///
- public Boolean IsSimple { get { return this.regions == null || this.regions.Length == 0; } }
+ public Boolean IsSimple {
+ get {
+ return this.regions == null || this.regions.Count == 0;
+ }
+ }
///
/// True if the State is a composite State.
@@ -36,7 +69,11 @@ public class State : Vertex where TContext : IContext
/// A composite State is one that has one or more child Regions.
///
- public Boolean IsComposite { get { return this.regions != null && this.regions.Length > 0; } }
+ public Boolean IsComposite {
+ get {
+ return this.regions != null && this.regions.Count > 0;
+ }
+ }
///
/// True if the State is an orthogonal State.
@@ -44,51 +81,42 @@ public class State : Vertex where TContext : IContext
/// A composite State is one that has more than one child Regions.
///
- public Boolean IsOrthogonal { get { return this.regions != null && this.regions.Length > 1; } }
-
- ///
- /// The child Regions where the State is composite.
- ///
- public IEnumerable> Regions { get { return this.regions; } }
-
- internal Region[] regions;
-
- private event Action exit;
- private event Action entry;
+ public Boolean IsOrthogonal {
+ get {
+ return this.regions != null && this.regions.Count > 1;
+ }
+ }
///
- /// Creates a new instance of the State class.
+ /// Test the state to see if it is a final state
///
- /// The name of the state.
- /// The parent Region.
- public State( String name, Region parent )
- : base( name, parent, Transition.State ) {
- Trace.Assert( name != null, "States must have a name" );
- Trace.Assert( parent != null, "States must have a parent Region" );
+ /// Final states are oned that have no outbound transitions.
+ public Boolean IsFinal {
+ get {
+ return this.Transitions.Count () == 0;
+ }
}
- // Constructor used by FinalState
- internal State( String name, Region parent, Func[], Object, TContext, Transition> selector ) : base( name, parent, selector ) { }
///
/// Tests the state to determine if it is part of the current active state confuguration
///
- /// The state machine context.
+ /// The state machine instance.
/// True if the element is active.
- internal protected override Boolean IsActive( IContext context ) {
- return base.IsActive( context ) && context[ this.Region ] == this;
+ public override Boolean IsActive (TInstance instance) {
+ return this.Parent.IsActive (instance) && instance[ this.Region ] == this;
}
///
/// Sets optional exit behavior that is called when leaving the State.
///
/// The type of the message that triggered the state transition.
- /// One or more actions that take both the state machine context and the triggering message as parameters.
+ /// One or more actions that take both the state machine instance and the triggering message as parameters.
/// Returns the State itself.
/// If the type of the triggering message does not match TMessage the behavior will not be called.
- public State Exit( params Action[] behavior ) where TMessage : class {
- foreach( var exit in behavior )
- this.exit += ( message, context ) => { if( message is TMessage ) exit( message as TMessage, context ); };
+ public State Exit (params Action[] behavior) where TMessage : class {
+ foreach (var exit in behavior)
+ this.exit += (message, instance) => { if (message is TMessage) exit (message as TMessage, instance); };
this.Root.Clean = false;
@@ -102,9 +130,9 @@ public State Exit( params Action[] behav
/// One or more actions that takes the triggering message as a parameter.
///
/// If the type of the triggering message does not match TMessage the behavior will not be called.
- public State Exit( params Action[] behavior ) where TMessage : class {
- foreach( var exit in behavior )
- this.exit += ( message, context ) => { if( message is TMessage ) exit( message as TMessage ); };
+ public State Exit (params Action[] behavior) where TMessage : class {
+ foreach (var exit in behavior)
+ this.exit += (message, instance) => { if (message is TMessage) exit (message as TMessage); };
this.Root.Clean = false;
@@ -114,11 +142,11 @@ public State Exit( params Action[] behavior ) wher
///
/// Sets optional exit behabiour that is called when leaving the State.
///
- /// One or more actions that takes the state machine context as a parameter.
+ /// One or more actions that takes the state machine instance as a parameter.
/// Returns the State itself.
- public State Exit( params Action[] behavior ) {
- foreach( var exit in behavior )
- this.exit += ( message, context ) => exit( context );
+ public State Exit (params Action[] behavior) {
+ foreach (var exit in behavior)
+ this.exit += (message, instance) => exit (instance);
this.Root.Clean = false;
@@ -130,9 +158,9 @@ public State Exit( params Action[] behavior ) {
///
/// One or more actions that take no parameters.
/// Returns the State itself.
- public State Exit( params Action[] behavior ) {
- foreach( var exit in behavior )
- this.exit += ( message, context ) => exit();
+ public State Exit (params Action[] behavior) {
+ foreach (var exit in behavior)
+ this.exit += (message, instance) => exit ();
this.Root.Clean = false;
@@ -143,12 +171,12 @@ public State Exit( params Action[] behavior ) {
/// Sets optional entry behavior that is called when entering the State.
///
/// The type of the message that triggered the state transition.
- /// One or more actions that take both the state machine context and the triggering message as parameters.
+ /// One or more actions that take both the state machine instance and the triggering message as parameters.
/// Returns the State itself.
/// If the type of the triggering message does not match TMessage the behavior will not be called.
- public State Entry( params Action[] behavior ) where TMessage : class {
- foreach( var entry in behavior )
- this.entry += ( message, context ) => { if( message is TMessage ) entry( message as TMessage, context ); };
+ public State Entry (params Action[] behavior) where TMessage : class {
+ foreach (var entry in behavior)
+ this.entry += (message, instance) => { if (message is TMessage) entry (message as TMessage, instance); };
this.Root.Clean = false;
@@ -162,9 +190,9 @@ public State Entry( params Action[] beha
/// One or more actions that takes the triggering message as a parameter.
/// Returns the State itself.
/// If the type of the triggering message does not match TMessage the behavior will not be called.
- public State Entry( params Action[] behavior ) where TMessage : class {
- foreach( var entry in behavior )
- this.entry += ( message, context ) => { if( message is TMessage ) entry( message as TMessage ); };
+ public State Entry (params Action[] behavior) where TMessage : class {
+ foreach (var entry in behavior)
+ this.entry += (message, instance) => { if (message is TMessage) entry (message as TMessage); };
this.Root.Clean = false;
@@ -174,11 +202,11 @@ public State Entry( params Action[] behavior ) whe
///
/// Sets optional entry behavior that is called when entering the State.
///
- /// One or more actions that takes the state machine context as a parameter.
+ /// One or more actions that takes the state machine instance as a parameter.
/// Returns the State itself.
- public State Entry( params Action[] behavior ) {
- foreach( var entry in behavior )
- this.entry += ( message, context ) => entry( context );
+ public State Entry (params Action[] behavior) {
+ foreach (var entry in behavior)
+ this.entry += (message, instance) => entry (instance);
this.Root.Clean = false;
@@ -190,9 +218,9 @@ public State Entry( params Action[] behavior ) {
///
/// One or more actions that takes no parameters.
/// Returns the State itself.
- public State Entry( params Action[] behavior ) {
- foreach( var entry in behavior )
- this.entry += ( message, context ) => entry();
+ public State Entry (params Action[] behavior) {
+ foreach (var entry in behavior)
+ this.entry += (message, instance) => entry ();
this.Root.Clean = false;
@@ -208,8 +236,8 @@ public State Entry( params Action[] behavior ) {
///
/// An internal transition does not exit or enter any states, however the transitions effect will be invoked if the guard condition of the transition is met
///
- public Transition When( Func guard ) where TMessage : class {
- return this.To( null ).When( guard );
+ public Transition When (Func guard) where TMessage : class {
+ return this.To (null).When (guard);
}
///
@@ -218,109 +246,95 @@ public Transition When( Func gu
/// The type of the messaage that the internal transition will react to.
/// The guard condition that must be met for hte transition to be traversed.
/// The internal transition.
- public Transition When( Func guard ) where TMessage : class {
- return this.To( null ).When( guard );
+ public Transition When (Func guard) where TMessage : class {
+ return this.To (null).When (guard);
}
///
/// Invokes the Exit behavior upon exiting a State.
///
- /// The state machine context.
+ /// The state machine instance.
/// The message that triggered the state transition.
/// A flag denoting if history semantics were in play during the transition.
- protected virtual void OnExit( Object message, TContext context, Boolean history ) {
- this.exit( message, context );
+ public virtual void OnExit (Object message, TInstance instance, Boolean history) {
+ this.exit (message, instance);
}
///
/// Invokes the Entry behavior upon entering a State.
///
- /// The state machine context.
+ /// The state machine instance.
/// The message that triggered the state transition.
/// A flag denoting if history semantics were in play during the transition.
- protected virtual void OnEntry( Object message, TContext context, Boolean history ) {
- this.entry( message, context );
+ public virtual void OnEntry (Object message, TInstance instance, Boolean history) {
+ this.entry (message, instance);
}
- internal override Boolean IsComplete( TContext context ) {
- return this.IsSimple || this.regions.All( region => region.IsComplete( context ) );
+ ///
+ /// Tests the element to determine if it is deemed to be complete.
+ ///
+ /// The state machine instance.
+ /// True if the state is complete
+ /// Simple states are always deemed to be complete; composite states are complete if all child regions are complete.
+ public override Boolean IsComplete (TInstance instance) {
+ return this.IsSimple || this.regions.All (region => region.IsComplete (instance));
}
- internal virtual void Add( Region region ) {
- if( this.regions == null )
- this.regions = new Region[ 1 ] { region };
- else {
- Trace.Assert( this.regions.Where( r => r.Name == region.Name ).Count() == 0, "Regions must have a unique name within the scope of their parent State" );
-
- var regions = new Region[ this.regions.Length + 1 ];
-
- this.regions.CopyTo( regions, 0 );
-
- regions[ this.regions.Length ] = region;
-
- this.regions = regions;
- }
+ ///
+ /// Adds a new child region to the state.
+ ///
+ /// The child region to add to the state.
+ /// Adding regions to a state turns the state in to a composite state.
+ internal virtual void Add (Region region) {
+ this.regions.Add (region);
region.Root.Clean = false;
}
- internal override void BootstrapElement( Boolean deepHistoryAbove ) {
- if( this.IsComposite ) {
- foreach( var region in this.regions ) {
- region.Reset();
- region.BootstrapElement( deepHistoryAbove );
-
- this.Leave += ( message, context, history ) => region.Leave( message, context, history );
- this.EndEnter += region.Enter;
- }
- }
-
- base.BootstrapElement( deepHistoryAbove );
-
- if( this.exit != null )
- this.Leave += this.OnExit;
-
- if( this.entry != null )
- this.BeginEnter += this.OnEntry;
-
- this.BeginEnter += ( message, context, history ) => context[ this.Region ] = this;
-
- this.Enter = this.BeginEnter + this.EndEnter;
- }
-
- internal override void BootstrapTransitions() {
- if( this.IsComposite )
- foreach( var region in this.regions )
- region.BootstrapTransitions();
-
- base.BootstrapTransitions();
- }
-
- internal override void BootstrapEnter( ref Action traverse, Element next ) {
- base.BootstrapEnter( ref traverse, next );
-
- if( this.IsOrthogonal )
- foreach( var region in this.regions )
- if( region != next )
- traverse += region.Enter;
+ ///
+ /// Algorithm for selecting the transitions
+ ///
+ /// The message that may trigger a transition.
+ /// The state machine instance.
+ /// Returns a single transition whose predicate evaluates true for the message and state machine instance, or null.
+ /// If more than one transitions predicate evaluates true the model is deemed to be malformed.
+ protected internal override Transition Select (object message, TInstance instance) {
+ return this.Transitions.SingleOrDefault( t => t.Predicate( message, instance ) );
}
- internal override Boolean Evaluate( Object message, TContext context ) {
+ ///
+ /// Evaluates a state to determine if a transition should be traversed.
+ ///
+ /// The message that may cause a state transition.
+ /// The state machine instance.
+ /// True if a state transition occured.
+ internal override Boolean Evaluate (Object message, TInstance instance) {
var processed = false;
- if( this.IsComposite )
- for( int i = 0, l = this.regions.Length; i < l; ++i )
- if( this.IsActive( context ) == true )
- if( this.regions[ i ].Evaluate( message, context ) )
- processed = true;
+ foreach (var region in this.regions)
+ if (this.IsActive (instance) == true)
+ if (region.Evaluate (message, instance))
+ processed = true;
- if( !processed )
- processed = base.Evaluate( message, context );
+ if (!processed)
+ processed = base.Evaluate (message, instance);
- if( processed == true && message != this )
- this.EvaluateCompletions( this, context, false );
+ if (processed == true && message != this)
+ this.Completion (this, instance, false);
return processed;
}
+
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
+ ///
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
+ ///
+ public override void Accept (Visitor visitor, TParam param) {
+ visitor.VisitState (this, param);
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/StateMachine.cs b/src/StateMachine.cs
index 0807b3d..759a735 100644
--- a/src/StateMachine.cs
+++ b/src/StateMachine.cs
@@ -4,75 +4,63 @@
* Licensed under MIT and GPL v3 licences
*/
using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
+//using System.Collections.Generic;
-// TODO: inherit from State (thereby enabling a machine to be used in another region if required)
namespace Steelbreeze.Behavior.StateMachines {
///
/// A StateMachine is the root node of a hierarchical state machine model.
///
- /// The type of the state machine context.
- public sealed class StateMachine : Element where TContext : IContext {
+ /// The type of the state machine instance.
+ public class StateMachine : State where TInstance : class, IActiveStateConfiguration {
+ #region Static members
///
- /// The name of the type without generic considerations
+ /// The visitor used to bootstrap the state machine model.
///
- public override string Type { get { return "stateMachineModel"; } }
-
+ private static Bootstrap Bootstrap = new Bootstrap ();
+ #endregion
+
///
- /// The child Regions.
+ /// Initialises a state machine instance to its initial state.
///
- public IEnumerable> Regions { get { return this.regions; } }
-
- internal Boolean Clean { get; set; }
- internal Region[] regions;
-
+ internal Action initialise = null;
+
///
- /// Returns the elements parent.
+ /// Flag to denote that a state machine model requires bootstrapping.
///
- ///
- /// A StateMachine will have no parent; this value will always be null.
- ///
- public override Element Parent { get { return null; } }
+ internal Boolean Clean { get; set; }
///
/// Initialises a new instance of the StateMachine class.
///
/// The name of the StateMachine.
- public StateMachine( String name )
- : base( name, null ) {
-
- Trace.Assert( name != null, "StateMachines must have a name" );
-
- this.Root = this;
+ public StateMachine (String name)
+ : base (name, null) {
}
///
- /// Tests the StateMachine to determine if it is part of the current active state confuguration
+ /// The parent state machine that this element forms a part of.
///
- /// The state machine context.
- /// True if the element is active.
- internal protected override Boolean IsActive( IContext context ) {
- return true;
+ public override StateMachine Root {
+ get {
+ return Parent == null ? this : Parent.Root;
+ }
}
- internal void Add( Region region ) {
- if( this.regions == null )
- this.regions = new Region[ 1 ] { region };
- else {
- Trace.Assert( this.regions.Where( r => r.Name == region.Name ).Count() == 0, "Regions must have a unique name within the scope of their parent StateMachine" );
-
- var regions = new Region[ this.regions.Length + 1 ];
-
- this.regions.CopyTo( regions, 0 );
-
- regions[ this.regions.Length ] = region;
-
- this.regions = regions;
- }
+ ///
+ /// Returns the elements parent.
+ ///
+ ///
+ /// A StateMachine will have no parent; this value will always be null.
+ ///
+ public override Element Parent { get { return null; } }
- this.Clean = false;
+ ///
+ /// Tests the element to determine if it is part of the current active state confuguration
+ ///
+ /// The state machine instance.
+ /// True if the element is active.
+ public override bool IsActive (TInstance instance) {
+ return Parent == null ? true : base.IsActive (instance);
}
///
@@ -80,81 +68,57 @@ internal void Add( Region region ) {
///
///
/// Initialising a state machine model pre-compiles all the transitions.
- /// This process will be triggered automatically on any call to StateMachine.Initialise( TContext context ) or StateMachine.Process if the model structure has changed.
- /// If you want to take greater control of when this happens, pass autoInitialise = false to StateMachine.Initialise( TContext context ) or StateMachine.Process and call Initialise as required instead.
+ /// This process will be triggered automatically on any call to StateMachine.Initialise or StateMachine.Process if the model structure has changed.
+ /// If you want to take greater control of when this happens, pass autoInitialise = false to StateMachine.Initialise or StateMachine.Process and call Initialise as required instead.
///
- public void Initialise() {
- this.Reset();
+ public void Initialise () {
this.Clean = true;
- this.BootstrapElement( false );
- this.BootstrapTransitions();
+ this.Accept (Bootstrap, false);
}
///
- /// Initialises a state machine context to its initial state; this causes the state machine context to enter its initial state.
+ /// Initialises a state machine instance to its initial state; this causes the state machine instance to enter its initial state.
///
- /// The state machine context.
- /// True if you wish to automatically re-initialise the state machine model prior to initialising the state machine context.
- public void Initialise( TContext context, Boolean autoInitialise = true ) {
- if( !this.Clean && autoInitialise )
- this.Initialise();
-
- this.Enter( null, context, false );
- }
+ /// The state machine instance.
+ /// True if you wish to automatically re-initialise the state machine model prior to initialising the state machine instance.
+ public void Initialise (TInstance instance, Boolean autoInitialise = true) {
+ if (!this.Clean && autoInitialise)
+ this.Initialise ();
- ///
- /// Determines if the state machine context has completed its processsing.
- ///
- /// The state machine context to test completeness for.
- /// True if the state machine context has completed.
- ///
- /// A state machine context is deemed complete when all its child Regions are complete.
- /// A Region is deemed complete if its current state is a FinalState (States are also considered to be FinalStates if there are no outbound transitions).
- /// In addition, if a state machine context is terminated (by virtue of a transition to a Terminate PseudoState) it is also deemed to be completed.
- ///
- public Boolean IsComplete( TContext context ) {
- return context.IsTerminated || this.regions.All( region => region.IsComplete( context ) );
+ this.initialise (null, instance, false);
}
///
- /// Pass a message to a state machine context for evaluation.
+ /// Pass a message to a state machine instance for evaluation.
///
- /// The state machine context to evaluate the message against.
+ /// The state machine instance to evaluate the message against.
/// The message to evaluate.
/// True if you wish to automatically re-initialise the state machine model prior to evaluating the message.
/// True if the message triggered a state transition.
///
/// Note that due to the potential for orthogonal Regions in composite States, it is possible for multiple transitions to be triggered.
///
- public Boolean Evaluate( Object message, TContext context, Boolean autoInitialise = true ) {
- if( !this.Clean && autoInitialise )
- this.Initialise();
+ public Boolean Evaluate (Object message, TInstance instance, Boolean autoInitialise = true) {
+ if (!this.Clean && autoInitialise)
+ this.Initialise ();
- Boolean processed = false;
+ if (instance.IsTerminated)
+ return false;
- if( !context.IsTerminated )
- for( int i = 0, l = this.regions.Length; i < l; ++i )
- if( this.regions[ i ].Evaluate( message, context ) )
- processed = true;
-
- return processed;
- }
-
- internal override void BootstrapElement( Boolean deepHistoryAbove ){
- foreach( var region in this.regions ) {
- region.Reset();
- region.BootstrapElement( deepHistoryAbove );
-
- this.EndEnter += region.Enter;
- }
-
- base.BootstrapElement( deepHistoryAbove);
+ return base.Evaluate (message, instance);
}
- internal override void BootstrapTransitions() {
- foreach( var region in this.regions )
- region.BootstrapTransitions();
+ ///
+ /// Accepts a visitor
+ ///
+ /// The visitor to visit.
+ /// A parameter passed to the visitor when visiting elements.
+ ///
+ /// A visitor will walk the state machine model from this element to all child elements including transitions calling the approritate visit method on the visitor.
+ ///
+ public override void Accept (Visitor visitor, TParam param) {
+ visitor.VisitStateMachine (this, param);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/StateMachineInstance.cs b/src/StateMachineInstance.cs
new file mode 100644
index 0000000..ad0b28f
--- /dev/null
+++ b/src/StateMachineInstance.cs
@@ -0,0 +1,34 @@
+/* State v5 finite state machine library
+ * http://www.steelbreeze.net/state.cs
+ * Copyright (c) 2014-5 Steelbreeze Limited
+ * Licensed under MIT and GPL v3 licences
+ */
+using System;
+
+namespace Steelbreeze.Behavior.StateMachines {
+ ///
+ /// A simple class to extend as a base for state machine intsances.
+ ///
+ public class StateMachineInstance : StateMachineInstanceBase {
+ ///
+ /// The name of the state machine instance
+ ///
+ public readonly String Name;
+
+ ///
+ /// Create a new instance of hte StateMachineInstance class.
+ ///
+ /// The name of the state machin instance
+ public StateMachineInstance (String name) {
+ this.Name = name;
+ }
+
+ ///
+ /// Returns the name of the state machine instance
+ ///
+ /// The name of the state machine instance
+ public override string ToString () {
+ return this.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/DictionaryContextBase.cs b/src/StateMachineInstanceBase.cs
similarity index 54%
rename from src/DictionaryContextBase.cs
rename to src/StateMachineInstanceBase.cs
index 3eee60d..f3a5937 100644
--- a/src/DictionaryContextBase.cs
+++ b/src/StateMachineInstanceBase.cs
@@ -8,34 +8,34 @@
namespace Steelbreeze.Behavior.StateMachines {
///
- /// A simple sample of an object to extend as a base for a state machine context object.
+ /// A simple abstract class to extend as a base for a state machine instances.
///
- /// The type of the derived context class.
+ /// The type of the derived IActiveStateConfiguration class.
///
/// By passing the type of the derived class into this base, it allows the callbacks generated by the state machine to pass the fully typed derived class.
/// Note that properties and methods have been explicitly implemented to hide the members from use other than via the IContext interface.
- /// Should you need persistence, or other such behaviour relating to the context class, implement another class implementing IContext.
+ /// Should you need persistence, or other such behaviour relating to the instance class, create a custom class implementing IActiveStateConfiguration.
///
- public abstract class DictionaryContextBase : IContext where TContext : IContext {
+ public abstract class StateMachineInstanceBase : IActiveStateConfiguration where TInstance : class, IActiveStateConfiguration {
// use a dictionary to store the last known state of a Region
- private readonly Dictionary, Vertex> last = new Dictionary, Vertex>();
+ private readonly Dictionary, State> active = new Dictionary, State> ();
///
- /// Indicates that the state machine context has been terminated.
+ /// Indicates that the state machine instance has been terminated.
///
/// A state machine is only deemed terminated if a transitions target is a Terminate PseudoState.
public Boolean IsTerminated { get; set; }
// sets and gets the current state of a specified Region
- Vertex IContext.this[ Region region ] {
+ State IActiveStateConfiguration.this[ Region region ] {
set {
- last[ region ] = value;
+ this.active[ region ] = value;
}
get {
- var value = default( Vertex );
+ var value = default (State);
- last.TryGetValue( region, out value );
+ this.active.TryGetValue (region, out value);
return value;
}
diff --git a/src/TODO (DMM-WIN7's conflicted copy 2015-03-23).md b/src/TODO (DMM-WIN7's conflicted copy 2015-03-23).md
new file mode 100644
index 0000000..a5dfd47
--- /dev/null
+++ b/src/TODO (DMM-WIN7's conflicted copy 2015-03-23).md
@@ -0,0 +1,6 @@
+Visitor pattern
+- implement bootstrapEnter as visitor
+
+Let bootstrapper create a materialised view of myTransitions
+
+Move fluent interface into Extensions
\ No newline at end of file
diff --git a/src/Transition.cs b/src/Transition.cs
index 4ffe551..6eb5ac9 100644
--- a/src/Transition.cs
+++ b/src/Transition.cs
@@ -4,117 +4,70 @@
* Licensed under MIT and GPL v3 licences
*/
using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
namespace Steelbreeze.Behavior.StateMachines {
///
/// A Transition describes a valid path from one Vertex in a state machine to another, and the trigger that will cause it to be followed.
///
- /// The type of the state machine instance.
+ /// The type of the state machine instance.
///
/// There are two types of transition, completion transitions and message based transitions.
/// Completion transitions are evaluated when a vertex has been entered and is deemed to be complete; this is the default for newly created transitions.
- /// Message based transitions have an additional guard condition that a message (event) and the current state machine context will be evaluated against; this is defined by the Transition.When method thereby turning a completion transition into a message based transition.
+ /// Message based transitions have an additional guard condition that a message (event) and the current state machine instance will be evaluated against; this is defined by the Transition.When method thereby turning a completion transition into a message based transition.
///
- public class Transition where TContext : IContext {
+ public class Transition where TInstance : class, IActiveStateConfiguration {
#region Static members
- private static Func IsElse = ( message, context ) => false;
-
- internal static Func[], Object, TContext, Transition> PseudoState( PseudoStateKind kind ) {
- switch( kind ) {
- case PseudoStateKind.Initial:
- case PseudoStateKind.DeepHistory:
- case PseudoStateKind.ShallowHistory:
- return Transition.Initial;
-
- case PseudoStateKind.Junction:
- return Transition.Junction;
-
- case PseudoStateKind.Choice:
- return Transition.Choice;
-
- case PseudoStateKind.Terminate:
- return Transition.Terminate;
-
- default: // NOTE: all PseudoStateKinds dealt with above so should not be an issue
- return null;
- }
- }
-
- internal static Transition State( Transition[] transitions, Object message, TContext context ) {
- Transition result = null;
-
- if( transitions != null ) {
- for( int i = 0, l = transitions.Length; i < l; ++i ) {
- if( transitions[ i ].Predicate( message, context ) ) {
- if( result != null )
- throw new InvalidOperationException( "Multiple outbound transitions evaluated true" );
-
- result = transitions[ i ];
- }
- }
- }
-
- return result;
- }
-
- private static Transition Initial( Transition[] transitions, Object message, TContext context ) {
- if( transitions.Length == 1 )
- return transitions[ 0 ];
- else
- throw new InvalidOperationException( "Initial transition must have a single outbound transition" );
- }
-
- private static Transition Junction( Transition[] transitions, Object message, TContext context ) {
- return transitions.SingleOrDefault( t => t.Predicate( message, context ) ) ?? transitions.Single( transition => transition.Predicate.Equals( Transition.IsElse ) );
- }
-
- private static readonly Random random = new Random();
-
- private static Transition Choice( Transition[] transitions, Object message, TContext context ) {
- var transition = default( Transition );
- var items = transitions.Where( t => t.Predicate( message, context ) );
- var count = items.Count();
-
- if( count == 1 )
- transition = items.First();
+ internal static Func IsElse = (message, instance) => false;
+ #endregion
+ ///
+ /// The source vertex of the transition.
+ ///
+ public readonly Vertex Source;
- else if( count > 1 )
- transition = items.ElementAt( random.Next( count ) );
+ ///
+ /// The target vertex of the transition.
+ ///
+ /// The target may be null for internal transitions.
+ public readonly Vertex Target;
- return transition ?? transitions.Single( t => t.Predicate.Equals( Transition.IsElse ) );
- }
+ ///
+ /// The predicate that must evaluate true before a transition may be traversed.
+ ///
+ public Func Predicate;
- internal static Transition Terminate( Transition[] transitions, Object message, TContext context ) {
- return null;
- }
- #endregion
- internal readonly Vertex Source;
- internal readonly Vertex Target;
- internal Func Predicate;
- internal Action Traverse;
+ ///
+ /// The complete bootstrapped behaviour performed when traversing a transition.
+ ///
+ internal Action Traverse;
- private event Action effect;
+ ///
+ /// The user defined behaviour for transition traversal.
+ ///
+ internal Action effect;
- internal Transition( Vertex source, Vertex target ) {
- Trace.Assert( source != null, "Transitions must have a source Vertex" );
+ ///
+ /// Creates a new instance of the Transition class
+ ///
+ /// The source vertex
+ /// The target vertex
+ public Transition (Vertex source, Vertex target = null) {
+ Trace.Assert (source != null, "Transitions must have a source Vertex");
this.Source = source;
this.Target = target;
- this.Completion();
+ this.Completion ();
}
///
- /// Adds a typed guard condition to a transition that can evaluate both the state machine context and the triggering message.
+ /// Adds a typed guard condition to a transition that can evaluate both the state machine instance and the triggering message.
///
/// The type of the message that can trigger the transition.
- /// The guard condition taking both the state machine context and the message that must evaluate true for the transition to be traversed.
+ /// The guard condition taking both the state machine instance and the message that must evaluate true for the transition to be traversed.
/// Returns the transition.
- public Transition When( Func guard ) where TMessage : class {
- this.Predicate = ( message, context ) => message is TMessage && guard( message as TMessage, context );
+ public Transition When (Func guard) where TMessage : class {
+ this.Predicate = (message, instance) => message is TMessage && guard (message as TMessage, instance);
return this;
}
@@ -125,8 +78,8 @@ public Transition When( Func gu
/// The type of the message that can trigger the transition.
/// A guard condition taking the message that must evaluate true for the transition to be traversed.
/// Returns the transition.
- public Transition When( Func guard ) where TMessage : class {
- this.Predicate = ( message, context ) => message is TMessage && guard( message as TMessage );
+ public Transition When (Func guard) where TMessage : class {
+ this.Predicate = (message, instance) => message is TMessage && guard (message as TMessage);
return this;
}
@@ -134,10 +87,10 @@ public Transition When( Func