Skip to content

Commit

Permalink
Merge pull request #9 from steelbreeze/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
David Mesquita-Morris committed Mar 28, 2015
2 parents ee86e10 + 1dcb4d8 commit 19bfc24
Show file tree
Hide file tree
Showing 29 changed files with 1,301 additions and 988 deletions.
6 changes: 1 addition & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 [email protected]

## 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.

Expand Down
21 changes: 21 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -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
93 changes: 44 additions & 49 deletions examples/Player.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,85 +6,80 @@
using System;
using System.Xml.Linq;

namespace Steelbreeze.Behavior.StateMachines.Examples
{
namespace Steelbreeze.Behavior.StateMachines.Examples {
/// <summary>
/// A controller for a simple cassette player
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public class Player
{
static void Main() {
// create the state machine model
var model = new StateMachine<XContext>( "player" );
var initial = new PseudoState<XContext>( "initial", model, PseudoStateKind.Initial );
var operational = new State<XContext>( "operational", model );
var choice = new PseudoState<XContext>( "choice", model, PseudoStateKind.Choice );
var final = new FinalState<XContext>( "final", model );
public class Player {
public static StateMachine<StateMachineInstance> Model;

var history = new PseudoState<XContext>( "history", operational, PseudoStateKind.DeepHistory );
var stopped = new State<XContext>( "stopped", operational );
var active = new State<XContext>( "active", operational ).Entry( EngageHead ).Exit( DisengageHead );
static Player () {
// create the state machine model
Model = new StateMachine<StateMachineInstance> ("model");

var running = new State<XContext>( "running", active ).Entry( StartMotor ).Exit( StopMotor );
var paused = new State<XContext>( "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<String>( command => command == "play" );
active.To( stopped ).When<String>( command => command == "stop" );
running.To( paused ).When<String>( command => command == "pause" );
paused.To( running ).When<String>( command => command == "play" );
operational.To( final ).When<String>( command => command == "off" );
operational.To( choice ).When<String>( 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<String>( command => command == "current" ).Effect( state => Console.WriteLine( state.XElement ) );
initial.To (operational).Effect (DisengageHead, StartMotor);
history.To (stopped);
stopped.To (running).When<String> (command => command == "play");
active.To (stopped).When<String> (command => command == "stop");
running.To (paused).When<String> (command => command == "pause");
paused.To (running).When<String> (command => command == "play");
operational.To (final).When<String> (command => command == "off");
operational.To (choice).When<String> (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");
}
}
}
202 changes: 202 additions & 0 deletions src/Bootstrap.cs
Original file line number Diff line number Diff line change
@@ -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 {
/// <summary>
/// Bootstraps a state machine model.
/// </summary>
/// <typeparam name="TInstance">The type of the state machine instnce.</typeparam>
/// <remarks>Bootstrapping a state machine model pre-determines all operations and evaluations required when traversing a transition; the results are then cached within the transition.</remarks>
internal class Bootstrap<TInstance> : Visitor<TInstance, Boolean> where TInstance : class, IActiveStateConfiguration<TInstance> {
private class Actions {
internal Action<Object, TInstance, Boolean> Leave;
internal Action<Object, TInstance, Boolean> BeginEnter;
internal Action<Object, TInstance, Boolean> EndEnter;
internal Action<Object, TInstance, Boolean> Enter;
}

/// <summary>
/// Cache of the behaviour required within the state machine model.
/// </summary>
private Dictionary<Element<TInstance>, Actions> behaviour;

/// <summary>
/// Returns the behaviour for a given element within the state machine model.
/// </summary>
/// <param name="element">The element to return the behaviour for.</param>
/// <returns>The state machine behaviour for a given model element.</returns>
private Actions Behaviour (Element<TInstance> element) {
Actions result = null;

if (!behaviour.TryGetValue (element, out result))
behaviour.Add (element, result = new Actions ());

return result;
}

public override void VisitElement (Element<TInstance> 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<TInstance> 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<TInstance> 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<TInstance> 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<TInstance> 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<TInstance> 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<TInstance> 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<TInstance> stateMachine, bool param) {
behaviour = new Dictionary<Element<TInstance>, Actions> ();

base.VisitStateMachine (stateMachine, param);

stateMachine.initialise = Behaviour (stateMachine).Enter;
}

public override void VisitTransition (Transition<TInstance> 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<TInstance> == 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<TInstance>) {
var state = element as State<TInstance>;

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;
}
}
}
}
14 changes: 0 additions & 14 deletions src/DictionaryContext.cs

This file was deleted.

Loading

0 comments on commit 19bfc24

Please sign in to comment.