# Overview

## Finite State Machines

FSM2 is a partial implementation of the UML2 State Machine.

State Machines allow you to model the behaviour of a system by specifying the sequence of states that a system can be in, as well as the events that cause the system to move between states.

![](/files/-MODgOUJBL0diYEAyViA)

In the above FSM for Water we can see water has three states and we have modelled four events that causes Water to transition from one state to another.

FSM2 uses a builder model to create a state machine with support for both static and dynamic transitions.

## Sponsored by OnePub

Help support FSM2 by supporting [OnePub](https://onepub.dev/drive/e69aafe8-c7f4-4965-bfe1-1ad7dbe7b23f), the private Dart repository.&#x20;

OnePub allows you to privately share Dart packages across your Team and with your customers.

Try it for free and publish your first private package in seconds.

| ![](/files/xscFghR6xU2ahbqQlSAX) | <p>Publish a private package in five commands:</p><p><mark style="color:green;"><code>dart pub global activate onepub</code></mark></p><p><mark style="color:green;"><code>onepub login</code></mark></p><p><mark style="color:green;"><code>cd \<my package></code></mark></p><p><mark style="color:green;"><code>onepub pub private</code></mark> </p><p><mark style="color:green;"><code>dart pub publish</code></mark></p> |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |

To make your life easier FSM2 is able to generate a graphical visualisation of your FSM using the [Graphwiz](http://www.graphviz.org/) dot notation. This makes designing and debugging your state machine much easier.

Let's have a look at the code required to generate the above model:

```dart
var machine = StateMachine.create((g) => g
          ..initialState<Solid>()
          ..state<Solid>((b) => b
            ..on<OnMelted, Liquid>(sideEffect: (e) => print('its melting')
            ..onEnter((s, e) => print('we are now solid')
            ..onExit((s, e) => print('bye bye solid existance'))
          ..state<Liquid>((b) => b
            ..on<OnFroze, Solid>(sideEffect: (e) => print('its getting cold'))
            ..on<OnVaporized, Gas>(sideEffect: (e) => print('its getting very hot'))
          ..state<Gas>((b) => b
            ..on<OnCondensed, Liquid>(sideEffect: (e) => print('move a little closer will you'))
        );
```

Your state machine will have an initial state of `Solid` and to transition to an new state you send the state machine an event.

```bash
machine.applyEvent(OnMelted);
```

Once you have applied all events (which are processed asynchronously) you need to call:

````
```dart
await machine.complete;
```
````

The call will only return once all events have been processed. Until `complete` returns the StateMachine is considered to be in an indeterminate state.

To create a visualization of the state machine:

```bash
machine.export('test.gv');
```

To view the FSM

```bash
apt install xdot
dotx test.gv
```

##

## Credits

Credit goes where credit is due.

FSM2 started out life as a fork of FSM by Kirill Bubochkin <https://github.com/ookami-kb/fsm> which in turn was inspired by [Tinder StateMachine library](https://github.com/Tinder/StateMachine).

All of the good bits are his, all of the mistakes are mine :)


# States

A state represents a stage in an FSM's life cycle.

An FSM will have an initial state just like you. We can describe your life with an FSM.

Twinkle -> Gestation -> Baby -> Teenager -> Adult ->  Dead

Each of the above stages in your life can be modeled as a State in an FSM.

Let's have a look at that.

```
void _createMachine() {
  machine = StateMachine.create((g) => g
    ..initialState<Twinkle>()
    ..state<Twinkle>((b) {})
    ..state<Gestation>((b) {})
    ..state<Baby>((b) {})
    ..state<Teenager>((b) {})
    ..state<Adult>((b) {})
    ..state<Dead>((b) {}));
}
```

We have now definititively identified all of the stages of your life including your 'initialState' of 'Twinkle'.

We can also model the events that cause you to move from one stage of your life to the next.

```
  machine = StateMachine.create((g) => g
    ..initialState<Twinkle>()
    ..state<Twinkle>((b) => b
      ..on<Conception, Gestation>())
    ..state<Gestation>((b) => b
      ..on<Born, Baby>())
    ..state<Baby>((b) => b
      ..on<Puberty, Teenager>())
    ..state<Teenager>((b) => b
      ..on<GetDrunk, Adult>())
    ..state<Adult>((b) => b
      ..on<Death, Dead>())
    ..state<Dead>((b) {}));
```

We have now added events that cause the transitions from one stage of your life to the next.

You start out as a 'Twinkle' in the eye of your father.  Then there is a rather messy 'Conception' event, where you are conceived which takes you into the 'Gestation' state.

So lets conceive you.

```
statemachine.applyEvent(Conception());
```

The transition call sends an event to the state machine, as we start out in the 'Twinkle' state the 'Conception' event is matched resulting in the transition to the 'Gestation' state.

## Initial State

You may define the initial state for the top level state and each [nested](/states/nested-states) substate using the 'initialState' method.

If you don't define an 'initialState' then the first state in the substate will be used as the initial state.


# Nested States

The UML2 specification allows for the concept of a nested state.

Nested states exist to reduce exponential 'state' and 'transition' explosions that can occur with classic FSMs.

Let's look at a code example and the associated diagram.

```dart
 var machine = StateMachine.create((g) => g
    ..initialState<S>()
    ..state<Alive>((b) => b
      ..onEnter((s, e) => print('onEnter $s as a result of $e'))
      ..onExit((s, e) => print('onExit $s as a result of $e'))
      ..on<OnBirthday, Young>(condition: (s, e) => human.age < 18, sideEffect: () => human.age++)
      ..on<OnBirthday, MiddleAged>(condition: (s, e) => human.age < 50, sideEffect: () => human.age++)
      ..on<OnBirthday, Old>(condition: (s, e) => human.age < 80, sideEffect: () => human.age++)
      ..on<OnDeath, Dead>()
      ..state<Young>((b) => b)
      ..state<MiddleAged>((b) => b)
      ..state<Old>((b) => b))
    ..state<Dead>((b) => b
      ..on<OnGood, Budist>(condition: (s, e) => s == Dead)
      ..on<OnUgly, SalvationArmy>(condition: (s, e) => s == InHell)
      ..on<OnBad, Christian>(condition: (s, e) => s == InHeaven)
      ..state<InHeaven>((b) => b..state<Budist>((b) => b))
      ..state<InHell>((b) => b..state<Christian>((b) => b..state<Catholic>((b) => b)..state<SalvationArmy>((b) => b))))
    ..onTransition((td) => watcher.log('${td.eventType}')));

```

In the above code block the indentations show the level of nesting.  You can see that the state 'Young' is nested within the state 'Alive'.

![](/files/-MMTem6gf0Ydp0hnxawG)

Each box represents a State and its nested states. In the above diagram we have two top level states 'Alive' and 'Dead'.

You can see how the 'Young' state is nested within the 'Alive' state box which reflects the above code.

There is no limit to the depth of nesting.

## Leaf States

States that have no child states of their own are referred to as Leaf States.&#x20;

{% hint style="info" %}
When looking at a nested state diagram, the top state(s) are the root state(s) and any states that are at the end of a branch are the leaf states. So in the above diagram Alive and Dead a root states and Buddhist and Old are leaf states.
{% endhint %}

*Due to the limits of the dot diagraming tooling (or my ability to use it) the grey ovals are duplicates of the box. The State  'Alive' is also represented by a box and the grey oval 'Alive'. This is done so that transitions to the parent state can be easily represented on the diagram.*

## Abstract States

When you create a nested set of states any states that have children states becomes 'abstract' states.

{% hint style="warning" %}
You cannot transition to an abstract state!
{% endhint %}

In the above example each of 'the Alive', 'Dead', 'InHeavan', 'InHell' and 'Christian' states have children states and are therefore abstract states.

It is only valid to create a transition to a Leaf state.&#x20;

## A Nested FSM can be in multiple states!

When an FSM is in a Nested State we say that it is also in ALL ancestor states.

A classic FSM can only ever be in a single state, Nested States make life more interesting >:) \*2

As an example; if the above FSM transitions to the 'Catholic' state we say that the FSM is in the 'Catholic' state,  'InHell' state and the 'Dead' state simultaneously.

If you call StateMachine.isInState\<Dead>() or StateMachine.isInState\<InHell>() both will return true.

## Cascading Events

If a parent state has a transition then we say that the child state also has that transition.

Let's look at a toaster oven that supports toasting and baking and that turns off the heater when the door is open.\*1

```dart
StateMachine _createMachine() {
  return StateMachine.create((g) => g
    ..initialState<DoorOpen>()
    ..state<DoorOpen>((b) {})
    ..state<Toasting>((b) => b
            // one
           ..on<OpenDoor, DoorOpen>())
    ..state<Baking>((b) => b
            // and duplicate
           ..on<OpenDoor, DoorOpen>())
  )); 
}
```

In the above example the transition ..on\<OpenDoor, DoorOpen> is duplicated for both the Toasting and Baking state.

If we create a super state `Heating` we can then make it the parent of `Toasting` and `Baking` .&#x20;

We can now  attached the  .on\<OpenDoor, DoorOpen> transition to the `Heating` state.

Both Toasting and Baking now inherit the .on\<OpenDoor, DoorOpen> transition from the Heating state.

This is the result:

```dart
StateMachine _createMachine() {
  return StateMachine.create((g) => g
    ..initialState<DoorOpen>()
    ..state<DoorOpen>((b) {})
    // new super state
    ..state<Heating>((b) => b
      // transition shared by heating, toasting and baking.
      ..on<OpenDoor, DoorOpen>()
      ..state<Toasting>((b) {})
      ..state<Baking>((b) {}))); 
}
```

This may not seem much of a saving but when you have an FSM with a significant no. of States and events then Nested States significantly reduce the complexity of an FSM.

\*1 Example sourced from: <https://www.embedded.com/a-crash-course-in-uml-state-machines-part-2/>

\*2 Evil grin


# Concurrent Region

Like [Nested States](/states/nested-states), concurrent regions are designed to control the combinatorial explosions that can occur in an FSM.

{% hint style="info" %}
UML2 refers to Concurrent Regions as 'orthogonal regions' meaning independant regions. Concurrent Region seems more descriptive hence our naming choice.
{% endhint %}

Concurrent regions allow you to have two or more concurrently active states within your state machine.

FSM2 uses the 'coregion' builder to create a concurrent region.

```dart
  machine = StateMachine.create((g) => g
    ..initialState<MaintainAir>()
    ..state<MaintainAir>((b) => b
      ..state<MonitorAir>((b) => b
        ..onFork<OnBadAir>((b) => b
          ..target<HandleFan>()
          ..target<HandleLamp>()
          ..target<WaitForGoodAir>(),
            condition: (s, e) => e.quality < 10))
      ..coregion<CleanAir>((b) => b
        ..state<HandleFan>((b) => b
          ..onEnter((s, e) async => turnFanOn())
          ..onExit((s, e) async => turnFanOff())
          ..onJoin<OnFanRunning, MonitorAir>(condition: ((e) => e.speed > 5))
          ..state<FanOff>((b) => b)
          ..state<FanOn>((b) => b
            ..onEnter((s, e) async => machine.applyEvent(OnFanRunning()))))
        ..state<HandleLamp>((b) => b
          ..onEnter((s, e) async =>  turnLightOn(machine))
          ..onExit((s, e) async =>  turnLightOff(machine))
          ..onJoin<OnLampOn, MonitorAir>()
          ..state<LampOff>((b) => b)
          ..state<LampOn>((b) => b
            ..onEnter((s, e) async => machine.applyEvent(OnLampOn()))))
        ..state<WaitForGoodAir>((b) => b..onJoin<OnGoodAir, MonitorAir>())))
    ..onTransition((s, e, st) {}));
```

In the above FSM the  'CleanAir' is  denoted as a concurrent region via the 'coregion' builder.

Each of 'CleanAir's immediate child states  'HandleFan' and 'HandleLamp' are concurrent states. The FSM can be in both of these states at the same time.

The concurrency of this example makes sense in the real world if you consider that the state of the fan (on/off) is completely independent of the state of the lamp.

If we look at the 'HandleFan' state we see that it has to child states 'FanOff' and 'FanOn'. These are normal nested states and whilst in the 'HandleFan'  state we can expect to move between the 'FanOff' and 'FanOn' states.

If you have only used a simple FSM then you may think of an FSM as only being in a single state. With UML2 and FSM2 concurrent regions and nested states create additional states that your FSM can be in simultaneously.

* Each immediate children of a concurrent region are considered completely independant. There is no limit on the no. of concurrent regions that an FSM may have nor the no. of immediate children.
* Concurrent regions can be nested within other states and you can nest states within a concurrent region.
* You enter a concurrent region using the [onFork](/states/concurrent-states/fork) pseudo state which FSM2 models as a transition with multiple target states. This would normally be each of the immediate children.&#x20;
* You exit a concurrent region using either the [onJoin](/states/concurrent-states/join) pseudo state or a simple '[on](/transitions/static-transitions)' transition.
* When you enter a co-region then you enter EVERY substate.
* If you use an 'on' transition or an 'onFork' that doesn't explicitly target every sub state then for all other substates their 'initialState' is entered.
* If a transition causes any state to transition to a start external to the co-region then all of the co-region's substates are exited.
* Once in a concurrent region, transitions between child states can occur independently of each other.

if you have [concurrent regions](/states/concurrent-states) in you FSM and a concurrent region is active when an event is applied, only one of the active states needs to be able to handle the event.&#x20;


# Fork

A fork is similar to an `on` transition in that it takes an Event and transitions the FSM to a new State.&#x20;

The difference is that a fork transitions the FSM into a concurrent region ([costate](/states/concurrent-states)). The `targets` of onFork are the new states the FSM will be in once the transition completes.

All the targets of a fork must enter states within the same concurrent region.

A fork is normally paired with a [join](/states/concurrent-states/join).  The fork causes the FSM to enter a  concurrent region, the join exits the concurrent region returning the FSM to a single state.

```
  return StateMachine.create((g) => g
    ..initialState<CheckingAir>()
    ..state<CheckingAir>((b) => b
      ..onFork<OnCheckAir>((b) => b
        ..target<HandleFan>()
        ..target<HandleLamp>(), condition: (s, e) => e.quality < 10)
      ..state<CleaningAir>((b) => b
        ..costate<HandleEquipment>((b) => b
          ..onJoin<WaitForGoodAir>((b) => b
            ..on<OnAirFlowIncreased>()
            ..on<OnLampOn>())
          ..state<HandleFan>((b) {})
          ..state<HandleLamp>((b) {}))
        ..state<WaitForGoodAir>((b) {}))));
```

All of the target states MUST be the descendant of a single [costate](/states/concurrent-states).

In the UML2 nomenclature a 'Fork' is a pseudo state.  Which is a state that the FSM never actually stays in but rather instantaneously transitions through.

Even thought Fork is a pseudo state, FSM2 has model it as a transition as it fits more neatly into the builder pattern that we have used.

The `onFork` method takes an Event which is the event that triggers the fork.&#x20;

```
..state<CheckingAir>((b) => b
  ..onFork<OnCheckAir>((b) => b
     ..target<HandleFan>()
     ..target<HandleLamp>()
```

In the above example, if the FSM is in the 'CheckingAir' state and an 'OnCheckAir' event occurs then the FSM enters the 'HandleFan' AND the 'HandleLamp' states which are both children of the 'HandleEquipment' concurrent region.


# Join

A join is similar to an `on` transition in that it takes an Event and transitions the FSM to a new State.&#x20;

The difference is that a join transitions the FSM from being in concurrent region into a single state and may requires multiple events to occur before the transition occurs.&#x20;

You will always pair a join with a [fork](/states/concurrent-states/fork). The fork causes the FSM to enter a concurrent region and a join causes it to exit the concurrent region.

When a join causes the FSM to exit a concurrent region it exits ALL states of the concurrent region (even states that lack an onJoin clause.

To add a join you use the 'onJoin' builder within a state that has a 'coregion' as an ancestor.

All onJoin within a single coregion MUST target the same state and the state must not be a child of the current 'coregion'.

A join is essentially an AND gate in that all events noted in onJoin statements (with in the coregion) must be triggered before the join will transition the FSM out of the coregion.

You might look at a join as shopping list. Each time an event fires the join ticks it off an event in its list. When the last event on the list is ticked off, the transition occurs.

OnJoin takes a State which is the new state the FSM will be in once the transition completes.&#x20;

All onJoin State targets MUST target the same state (we my relax this rule in a later implementation).

```
return StateMachine.create((g) => g
    ..initialState<CheckingAir>()
    ..state<CheckingAir>((b) => b
      ..onFork<OnCheckAir>((b) => b
        ..target<HandleFan>()
        ..target<HandleLamp>(), condition: (s, e) => e.quality < 10)
      ..state<CleaningAir>((b) => b
        ..coregion<HandleEquipment>((b) => b
          ..state<HandleFan>((b) =>
            ..onJoin<OnRunning, CheckingAir>())
         ..state<HandleLamp>((b) =>
            ..onJoin<OnLampOn, CheckingAir>())
        ..state<WaitForGoodAir>((b) {}))));
```

```
StateMachine.create((g) => g
    ..initialState<MaintainAir>()
    ..state<MaintainAir>((b) => b
      ..state<MonitorAir>((b) => b
        ..onFork<OnBadAir>(
            (b) => b
              ..target<HandleFan>()
              ..target<WaitForGoodAir>(),
            condition: (s, e) => e.quality < 10))
      ..coregion<CleanAir>((b) => b
        ..state<HandleFan>((b) => b
          ..onJoin<OnFanRunning, MonitorAir>())
        ..state<WaitForGoodAir>((b) => b
          ..onJoin<OnGoodAir, MonitorAir>())))
    );
```

## Rules

* A join must only be defined within a coregion
* Any number of nested states may exist between the join and its owning coregion.
* If coregions are nested then the join is 'owned' by the nearest ancestor coregion.
* All joins in a coregion MUST have the same target state.
* For a join to cause a state transition, all events for the joins in the owning coregion must have been received.
* Each time the FSM enters a coregion the set of received events will be reset and all join events must be received again before the join will trigger.
* When a join receives an event but does not trigger, the FSM will continue to evaluate additional transitions until it finds one that triggers.
* A coregion's child state does may not have an 'onJoin'. The state will be exited when the coregion exits.&#x20;


# Pseudo States

In UML2 whe have a number of what we call pseudo states.

Examples of pseudo states are:

* [fork](/states/concurrent-states/fork)
* [join](/states/concurrent-states/join)

The FSM can never be said to be in a Pseudo states.&#x20;

The StateMachine always transitions through pseudo states instantaneously.

&#x20;We do not emmitt pseudo states into the StateOfMind [stream](/tracking-state/stream-of-states) nor transitions.

Instead you will see an transition from the original state into the target state.

FSM2 models the above pseudo states as transitions as that is where the fit naturally into the FSM2 builder pattern.


# Events

For example when receiving an 'UserLoggedIn' event it is likely that you need to fetch some additional data. To facilitate this you can pass the user id in the event.Events are objects passed into the state machine with the intent of causing the FSM to transition to a new state.

You will often model real world events 'Turn Light on' but equally you will create many events that reflect internal events within your software or even somewhat abstract events that simply help your state machine to arrive in the correct state.

Some events won't result in a transition to a new state as the likes of a [guard condition](/transitions/guard-conditions) or a [Join](/states/concurrent-states/join) may not be satisfied.

To send an event to the state machine you use:

```dart
statemachine.applyEvent(SomeEvent());
```

{% hint style="danger" %}
The call to 'applyEvent' is asynchronous however you MUST NEVER 'await' a call to 'applyEvent' as you risk deadlocking your state machine.
{% endhint %}

## Event Data

You will often find it useful to pass information along with your event. You might have defined a [sideEffect](/transitions/sideeffects) that uses data from your event.

```dart
class UserLoggedIn extends Event
{
 int userId;
 UserLoggedIn(this.userId);
}

stateMachine.applyEvent(UserLoggedIn(userid));
```

Within your statemachine you can then define an sideEffect that takes that user id and fetches the user's credit score.

```dart
..state<NoSession>((b) => b
  ..on<UserLoggedIn, HasSession>(sideEffect: (e) => fetchCreditScore(e.userId)) 
```

## Terminology

| Phrase            | Meaning                                                                                                                                                                                                                      |
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Applying an Event | An Event is 'applied' to the StateMachine which may cause a state transition.                                                                                                                                                |
| Receive an Event  | An event may be received but not cause a transition to trigger. This may be because its an invalid event for the current state, guard conditions prevent the transition being triggered or a join condition hasn't been met. |
| Trigger an Event  | An event has been received and a valid transition found. The guard condition has evaluated to true and the transition will be executed. The state machine may not however change state.                                      |
| State Transition  | An event was triggered resulting in a change of State.                                                                                                                                                                       |


# Transitions

Transitions are where all of the excitement happens in an FSM.

Transitions occur when an [event](/events) is sent to the FSM. The purposes of a transition is to move the FSM from the existing state(s) into a new state(s).

{% hint style="info" %}
We refer to the set of active states as a StateOfMind. UML2 refer to this as a State Configuration but that is such a boring non-descriptive term we had to use something else.
{% endhint %}

Some transitions will result in the FSM not changing states. The transition attempt could be blocked by a [guard condition](/transitions/guard-conditions) or the transition simple ends up with the FSM ending in the same state it started with.

You trigger a transition by calling `statemachine.applyEvent` passing an Event.

The FSM will look at what state(s) it is in. The FSM will then look for a transition handler for the event in each of the current states.

There are a number of transition handlers:

* [on](/transitions/static-transitions)
* [onFork](/states/concurrent-states/fork)
* [onJoin](/states/concurrent-states/join)

If a matching transition is found its [guard condition](/transitions/guard-conditions) is checked. If the guard condition evaluates to false then the FSM searches for the next matching transition.

If the guard condition evaluates to true then the transition is triggered.

States can define multiple transitions for the same event with different guard conditions.

If no transition is triggered the FSM will then search the state's ancestors (from the current state(s) upwards) for a matching transition and apply the same rules.

If the FSM has active [Concurrent Regions](/states/concurrent-states) then the above process will be applied to the active state in each concurrent region and as such multiple state transitions may occur as the result of a single Event.

## Ordering of Transitions

Ordering of transitions is critical to the correct operation of the FSM.

FSM2 queues all transitions into the state machine and won't allow the transition to be applied to the FSM until all previously queued transitions are completed.

Transitions are always processed in the order the are submitted (FIFO).

## Nested Transitions

In some cases you may need to trigger a transition whilst in a callback from the FSM (e.g. onEnter, onExit or sideEffect in turn trigger a new transition). This is fine, the FSM will simply queue the transition and it will be applied once all previously queued transitions have completed.&#x20;


# Trigger a Transition

Once you have a FSM you need to trigger a transition to a new state.

This is done with the `StateMachine().applyEvent` method.

```dart
var machine = StateMachine.create((g) => g
          ..initialState<Solid>()
          ..state<Solid>((b) => b
            ..on<OnMelted, Liquid>(
                sideEffect: (e) => watcher.log(onMeltedMessage))
            ..onEnter((s, e) => watcher?.onEnter(s))
            ..onExit((s, e) => watcher?.onExit(s)))
          ..state<Liquid>((b) => b
            ..onEnter((s, e) => watcher?.onEnter(s))
            ..onExit((s, e) => watcher?.onExit(s))
            ..on<OnFroze, Solid>(
                sideEffect: (e) => watcher.log(onFrozenMessage))
            ..on<OnVaporized, Gas>(
                sideEffect: (e) => watcher.log(onVaporizedMessage)))
          ..state<Gas>((b) => b..on<OnCondensed, Liquid>(
              sideEffect: (e) => watcher.log(onCondensedMessage)))
        );
machine.applyEvent(OnFroze());
```

The `applyEvent` method takes an Event and may cause the FSM to transition to a new state.

{% hint style="info" %}
If you have concurrent regions or nested states then a single event may result in multiple transitions.
{% endhint %}

You should be aware that some calls to \`applyEvent\` will not result in a change in State. This can happen due to [Guard Conditions ](/transitions/guard-conditions)suppressing the transition.

Calls to `StateMachine.applyEvent` are asynchronous.&#x20;

{% hint style="info" %}
Your state machine will not have finished transitioning when applyEvent returns! Use an onEnter, onExit, sideEffect or stream to get notifications of when the statemachine has entered a new state.
{% endhint %}

Calls to `StateMachine.applyEvent` can be made at any time and from any code.&#x20;

The StateMachine will queue any events and only apply the event once any active or pre existing queued  events have completed.

## Invalid Transitions

If you call applyEvent with an event you must be in a state that has an handler for that event.

```dart
final machine = StateMachine.create((g) => g
    ..initialState<Solid>()
    ..state<Solid>((b) => b
      ..on<OnMelted, Liquid>(sideEffect: (e) async => print('Melted'))
    ..state<Liquid>((b) => b
      ..on<OnFroze, Solid>(sideEffect: (e) async => print('Frozen'))
      ..on<OnVaporized, Gas>(sideEffect: (e) async => print('Vaporized')))
    ..state<Gas>((b) => b
      ..on<OnCondensed, Liquid>(sideEffect: (e) async => print('Condensed'))));

  machine.applyEvent(OnFroze());

```

The above call to 'machine.applyEvent' will fail as the initialState is 'Solid' and the 'Solid' state doesn't have a handler for the 'OnFroze' event.

{% hint style="info" %}
Note: FSM2 supports inherited transition handlers so an event can be processed if an active State OR one of its parents has a handler for the state.
{% endhint %}

If the statemachine is can't handle the event then a 'InvalidTransitionException' is thrown.

The exception is thrown only when in debug mode. If you pass 'production: true' to the statemachine 'create' method then the exception will be suppressed and instead it will be logged. This is done to make the FSM less brittle in production. &#x20;

## Concurrent Regions

If you have [concurrent regions](/states/concurrent-states) in you FSM and a concurrent region is active when an event is applied, only one of the active states needs to be able to handle the event. If at least on of the actives states handles the event then an 'InvalidTransitionException' will not be thrown.


# Simple Transitions

Simple transitions are declared using the 'on' builder.

```dart
import 'package:fsm2/fsm2.dart';

void main() {
  final machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnMelted, Liquid>())
 );
```

The above examples creates a Finite State Machine (machine) which declares its initial state as being `Solid` and then declares a single transition Solid -> Liquid when the event `OnMelted` is applied to the FSM.

We can now write a unit test that validates our state machine when \`machine.applyEvent(OnMelted())\` is called.

```dart
import 'package:fsm2/fsm2.dart';
import 'package:test/test.dart';

test('test transition', () async {
  
  /// build the state machine.
  final machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnMelted, Liquid>())
      
  /// check its initial state
  expect(machine.isInState<Solid>(), equals(true));

  /// trigger the state change
  await machine.applyEvent(OnMelted());

  /// check we arrived in the expected state.
  expect(machine.isInState<Liquid>(), equals(true));
});
```

### Many transitions

A State can have any number of `on` transitions including multiple transitions for the same Event providing a guard conditions is used.

See the section on [Guard Conditions](/transitions/guard-conditions) for details on defining conditional transitions.


# Guard conditions

FSM2 supports guard conditions on transitions which only allow an event to trigger a transition if some condition is met.

Guard conditions allow you to declare (via the `on` builder) the same Event multiple times from a single State. When registering multiple transitions with the same Event type only a single event (the default transition) may have an empty guard condition and it MUST be the last event added to the state.

If a State has multiple transitions for the same event, then the transitions will be evaluated in the order they are declared. The first transition whose guard condition returns true will be triggered. No further guard conditions will be evaluated. A transition without a Guard Conditions (the default transition) always evaluates to true.

```dart
import 'package:fsm2/fsm2.dart';

void main() {
  var temperature = 0;
  final machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnHeat, Liquid>(condition: temperature + e.deltaDegrees > 0)
      ..on<OnHeat, Boiling>(condition: temperature + e.deltaDegrees > 100)
      ..on<OnHeat, Solid>()
 ));
```

You can see from the above example that the `OnHeat` Event contains a field `deltaDegrees`. It is often useful to pass arguments to your events which can then be applied to the State.&#x20;

To pass a value into an event: (your event must take a named argument deltaDegrees).

```dart
machine.applyEvent(OnHeat(deltaDegrees: 25));
```

When `machine.applyEvent` is called, and the FSM is in the `Solid` state, then the FSM will evaluate each `onHeat` transition in the order that they are declared.

The first transition whose condition evaluates to true will be applied. No further transitions will be considered.

### Default Transition

The above example contains three `onHeat` transitions, the last one does not have a guard condition. This is considered the default transition. If all other transitions fail their condition test, then the default transition will be triggered.&#x20;

You do not have to have a default transition. If you don't have a default transition and all conditions fail then no transition is triggered and the State of the machine does not change.


# onEnter

Each state may define an `onEnter` lambda. The `onEnter` lambda is called every time the FSM enters the state regardless of how the FSM arrived in that state. The onEnter method saves you from having to duplicate common code for every transition into the state.

```dart

    StateMachine.create((g) => g
          ..initialState<Solid>()
          ..state<Solid>((b) => b
            ..onEnter((s, e) => print('we are now solid'))
            ..on<OnMelted, Liquid>()
        );
```

By convention the `onEnter` builder should be the first one after the state builder.

It is safe to call `stateMachine.applyEvent` call whilst in onEnter. The transition will be queued and processed once the current transition has been completed.

## Nested States

When you have a [Nested State](/states/nested-states) the onEnter action becomes a little more complex. With a Nested State we say that when you enter a child state you also enter all ancestor states of that child.

As such when entering a new child state we must call onEnter for each ancestor that is being entered. We start with the ancestor nearest the root of the state tree and call its onEnter method, then the next one down and so on until we reach the child state and call its onEnter.

When moving between any two states with a common ancestor onExit and onEntry methods are called for every ancestor up to but excluding the common ancestor (as we are neither entering nor leaving its state).

## Concurrent Region

Concurrent Regions are somewhat less complex than Nested States as each concurrent region has an independent set of states so changing state in one region does not affect the state of the other region. Concurrent States can of course be within Nested States and include Nested states so the normal Nested rules apply.

With a concurrent region, the 'onEnter' method will be called for each state that we transition into and then the above noted nesting rules are applied.

When calling onEnter for each state in a concurrent region, we do not define the order that the onEnter methods will be called.

&#x20;


# onExit

Each state may define an `onExit` lambda. The `onExit` lambda is called every time the FSM leaves the state regardless of what transition caused the FSM to leave the state. The `onExit` method saves you from having to duplicate common code in for every transition into the state.

```dart

    StateMachine.create((g) => g
          ..initialState<Solid>()
          ..state<Solid>((b) => b
            ..onExit((s, e) => print('We may be melting'))
            ..on<OnMelted, Liquid>()
        );
```

By convention the `onExit` builder should be the first one after the state builder unless there is an `onEnter` builder in which case it should appear after the `onEnter` builder.

It is safe to call `stateMachine.applyEvent` whilst in onExit. The transition will be queued and processed once the current transition has been completed.

## Nested States

When you have a [Nested State](/states/nested-states) the onExit action becomes a little more complex. With a Nested State we say that when you enter a state you also enter all ancestor states of that state. In the same way when you leave a state you may leave all of its ancestor states as well as any active child states.

As such we leaving a state we must call onExit for each ancestor upto, but not including, the common ancestor of the old and new state and call onExit for any active child states. (An active state is one that is in the current 'StateOfMind').

As with Class Inheritance we start with the child call its onExit method and the work our way up to the common ancestor.

## Concurrent States

Concurrent States are less complex than Nested States as each concurrent region has an independent set of states so changing state in one region does not affect the state of the other region. Concurrent States can of course be within Nested States and include Nested states so the normal Nested rules apply.

If the state transitions to a state 'above' the concurrent region (closer to the state tree root) then we may need to call the onExit method of all active states in the concurrent regions. If there are two or more active states in the concurrent regions then we do not define the order in which their onExit methods will be called.


# SideEffects

FSM2 allows you to trigger individual side effects for each transition that occurs.

&#x20;The side effect is a lambda which will be called when the transition is triggered.&#x20;

Side effects make it easy to carry out a task when a transition occurs.  For example if the Event is 'OnApplyHeat' you could use a side effect to recalculate the temperature of a the water as the result of being heated.

A transition that fails to pass its [guard condition](/transitions/guard-conditions) will not be triggered and its side effect will not be called.

The sideEffect is called AFTER the original states `onExit` method is called but before the new states `onEnter` method is called.

When the sideEffect is called the State Machine is still considered to be in the original state.

It is safe to call `stateMachine.applyEvent` call whilst in sideEffect. The event will be queued and processed once the current transition has been completed.

```dart
void main() {
  final machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnHeat, Liquid>(sideEffect: (e) => print('I melted')))
 ));
```

In this example the side effect is only called when the `onHeat` event is sent when the FSM is in the `Solid` state.

```dart
machine.applyEvent(OnHeat());
> I melted
```

The side effect will be called every time the transition occurs.

## Nested and Concurrent Regions

A single transition may result in multiple state changes when Nested or Concurrent Regions are used.

In these cases the sideEffect is still only called once&#x20;


# Transition Inheritance

A core objective of UML2 is to reduce duplication of code.

As such UML2 supports the concept of 'inherited' transitions.

When you define a transition in a parent state each of the child states 'inherit' that transition.

```
 var machine = StateMachine.create((g) => g
    ..initialState<S>()
    ..state<Alive>((b) => b
      ..on<OnDeath, Dead>()
      ..state<Young>((b) => b)
      ..state<Old>((b) => b))
    ..state<Dead>((b) => b)
```

In the above example we have four states:

* Alive
* Young
* Old
* Dead

It doesn't matter if you are Young or Old you can always die.

FMS2 allows us to place the '..on\<OnDeath, Dead>' transition in the parent 'Alive' state. We now say that both the Young and Old states inherit the OnDeath transition. This correctly models the fact that you can die at any moment (so stop programming right now and go out and enjoy the day!).

## Handling of inherited transitions

So what occurs when stateMachine.applyEvent(OnDeath()) is called?

When using nested states or concurrent regions you state machine can be in multiple states simultaneously.

In the above example if you are Young then you are also Alive, so we can say that you are in both the 'Alive' and the 'Young' state.

Our [StateOfMind](/tracking-state/stateofmind) object would have a single StatePath 'Alive -> Young'. If you had active [concurrent regions](/states/concurrent-states) then the StateOfMind would have a StatePath for each active concurrent state.

When searching for a transition handler for the given event we start at leaf state (Young) and search up the tree until we find a matching handler. In this case we find one on the 'Alive' state.&#x20;

A single transition event will be emitted to all transition listeners which will indicate that an event was handled by the 'Young' state even though the transition handler was in the 'Alive' state.

A single event can result in multiple transition events if you are in a concurrent region or an 'onFork' transition is triggered.

A single StateOfMind will be added to the stream  (StateMachine.stream) regardless of the number of transitions that occur when processing the event.


# Where the action happens

When creating your FSM you need to be thinking about how state transitions will affect your application and how to apply those state transitions.

Essentially you need to wire the FSM into your application.

FSM2 has a number key tools you should look to when wiring a FSM into your app.

* [onEnter](/transitions/onenter)
* [onExit](/transitions/onexit)
* [sideEffects](/transitions/sideeffects)
* [stream](/tracking-state/stream-of-states)

Using the above tools allows your application to 'react' to state changes.

By placing handlers at any of the above connection points you can use state changes to trigger virtually anything.

Some examples might be:

* ui updates
* database updates
* fetch data
* trigger a route change
* send a text message


# Tracking State


# StateOfMind

Chances are that if you have previously used an FSM, it probably only supported the concept of being in a single state at any given moment.

UML2 and FSM2 support the concept of nested states and concurrent states. Whenever you use [nested states](/states/nested-states) or [Concurrent Regions](/states/concurrent-states)  your FSM2 state machine may at times be in multiple concurrent states.

Because we have multiple concurrent states, you can't just ask FSM2 what the current state is. Instead, you have to ask what the current set of states are.&#x20;

In UML2 they refer to the current set of states as the State Configuration, in FSM2 we refer to it as the 'StateOfMind'. A StateOfMind is an object that reflects the current set of states of the state machine.

The primary access path to the StateOfMind is via:

* StateMachine.stream

The StateMachine.stream allows you to respond to state changes by combining it with the likes of StreamBuilder. You then use StateOfMind.isInState to check for a particular state.

You should also look to using the onEnter, onExit, and sideEffect methods on states and transitions as these are the most effective ways of hooking into the state machine.

## State Paths

The StateOfMind doesn't just hold a set of States, rather it holds a set of state paths.

UML2 describes the State Configuration (the equivalent of our StateOfMind) as a tree of states starting at the root state. There is a branch on that tree for each active state.

FSM2 takes a slightly different approach to optimise access.

For each active state, we keep a list of ancestor states starting at the active state back to the root state.

An FSM2 state machine can have multiple roots (under the hood we implement a VirtualRoot which all of these roots use as a parent).

If you look at the following simplified example you see we have two root nodes 'Alive' and 'Dead' each of which have nested child states.

In turn, we can see that the 'Dead' state has two child states, 'InHeaven' and 'inHell', and that InHeavan has a subsequent child state 'Buddhist'.  So we could say that this FSM is a three-level tree with Buddhist as a 3rd level node in the tree.  The deepest nodes on each branch (such as Buddhist) are referred to as **Leaf Nodes**.

```
var machine = StateMachine.create((g) => g
    ..state<Alive>((b) => b
      ..state<Young>((b) {})
      ..state<MiddleAged>((b) {})
      ..state<Old>((b) => b))
    ..state<Dead>((b) => b
      ..state<InHeaven>((b) => b
        ..state<Buddhist>((b) {}))
      ..state<InHell>((b) => b
        ..state<Christian>((b) => b
        ..state<Catholic>((b) {})
        ..state<SalvationArmy>((b) {}))))
    ));
```

If Buddhist is the current active state, then the StateOfMind would contain one state path:

Buddhist -> InHeaven -> Dead

Each of the above states, Buddhist, InHeaven, and Dead are now all considered active.


# Stream of States

You can subscribe to a stream of [StateOfMind](/tracking-state/stateofmind) objects from the `StateMachine`.

Each time the FSM transitions to a new state the stream will emit a StateOfMind which contains details of the new state(s) the FSM is in.

Remember that with Nested States or Concurrent Regions an FSM can be in multiple states at the same time.

The \`StateOfMind\` will contain a complete set of all States that reflect the StateMachine's current state.

Using a stream with a StreamBuilder:

```dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

/// This is the main application widget.
class MyApp extends StatelessWidget {
  static const String _title = 'FSM2 Stream Builder sample';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: _title,
      home: MyStatefulWidget(),
    );
  }
}

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  StateMachine machine;
  MyStatefulWidget({Key key}) : super(key: key)
  {
   machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnMelted, Liquid>(
            sideEffect: (e) => print('Melted'),
          )))
   ));

}

  @override
  _MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {

  Widget build(BuildContext context) {
    return DefaultTextStyle(
      style: Theme.of(context).textTheme.headline2,
      textAlign: TextAlign.center,
      child: Container(
        alignment: FractionalOffset.center,
        color: Colors.white,
        child: StreamBuilder<StateOfMind>(
          stream: machine.stream,
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
            List<Widget> children;
            if (snapshot.hasError) {
              children = <Widget>[
                Icon(
                  Icons.error_outline,
                  color: Colors.red,
                  size: 60,
                ),
                Padding(
                  padding: const EdgeInsets.only(top: 16),
                  child: Text('Error: ${snapshot.error}'),
                )
              ];
            } else {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                case ConnectionState.waiting:
                  children = <Widget>[
                    Icon(
                      Icons.info,
                      color: Colors.blue,
                      size: 60,
                    ),
                    const Padding(
                      padding: EdgeInsets.only(top: 16),
                      child: Text('I lost my mind'),
                    )
                  ];
                  break;
               
                case ConnectionState.active:
                case ConnectionState.done:
                  children = <Widget>[
                    Icon(
                      Icons.info,
                      color: Colors.blue,
                      size: 60,
                    ),
                    Padding(
                      padding: const EdgeInsets.only(top: 16),
                      child: showState(snapshot.data),
                    )
                  ];
                  break;
              }
            }

            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: children,
            );
          },
        ),
      ),
    );
  }
  
  Widget showState(StateOfMind som)
  {
    var children = <Text>[];
    for (var stateType in som.currentStates)
    {
      children.add(Text(stateType));
    }
    return Column(children: children);
  }
}

```


# Check the current state

There are a number of methods for checking the current state of the StateMachine.

* StateMachine.isInState
* StateOfMind.isInState
* StateMachine.stateOfMind
* Subscribing to StateMachine.stream.

Generally, you should not have to interact with any of these methods too often, rather you should rely on sideEffects and the onEnter and onExit methods to make 'things' happen although the [SateOfMind](/tracking-state/stateofmind) stream is useful as it can be wired to a Flutter StreamBuilder.

The preferred methods are:

* When you enter a new state [onEnter](/transitions/onenter) is called so you can have that update your UI.
* When you leave a state [onExit](/transitions/onexit) is called so you can for example close a window.
* When you transition to a new state you can have a [sideEffect](/transitions/sideeffects) update the UI or database.

## isInState

Using isInState allows you to check the current state of the machine at any time.

Remember that due to Nested States and Concurrent States an FSM can actually be in multiple states at the one time.

When using isInState you should usually target the state lowest in the tree (where the top of the tree is the root - it's an upside-down tree).

You can also call isInState multiple times to determine what other states the FSM is in.

If you need to know multiple states then `stateOfMind` is a better option.

## stateOfMind

Note: This is a work in progress and currently only returns the state that was directly transitioned into (referred to internally as the currentState but that's not quite a true reflection).

A call to `stateOfMind` returns a `StateOfMind` object that contains all of the nested ancestor states as well as any concurrent states in a tree structure.

## StateMachine.stream

The stream can be used with a Flutter StreamBuilder to process each state update as it occurs.


# Recommendations

## Many state machines

We strongly recommend that you do NOT try to implement one humongous state machine for your whole app. Large state machine are hard to build, debug and maintain.

Rather we recommend that you have as  many state machines as you find useful.

To help you think about this lets discuss the different types of state your application has:

* Global State
* Session State
* Screen State
* Widget State

## Global State

Global state is normally a reflection of your persistent storage such as your application's database.

You don't need to model your global state in a state machine as you can simply query your database to discover its current state. You may need to cache your database but that's an optimisation issue and not relevant to what state is about.

Sometimes you may want to create a FSM for the state of a entity or a group of related entities.

For example a customer may go through different states such as; trial, customer, inactive, overdue, suspended, terminated. In this case it may be useful to have an FSM that is used to transition a customer between these states.

## Session State

Session State tends to revolve around a user interaction with the system. A user logs in and we create a Session. It can be useful to create an FSM to track the sessions current state and to perform transitions from one state to another (logged in -> logged out).&#x20;

You can then subscribed to an FSM2 stream to reflect these changes in other parts of your app is simply query the sessions state via a call to StateMachine.isInState.

## Screen State

Screens, Pages, Routes, Views, or whatever you want to call them tend to present the user with a related data set (e.g. a CRUD screen for maintaining users).&#x20;

As the user navigates around the Screen you may need to track what state the user is in; Listing, Editing etc.

I wouldn't normally use an FSM for these simple screens as using setState and Providers are usually sufficient to track the state of the screen.&#x20;

There are however plenty of exceptions. I'm currently working on a registration wizard and the no. of possible states is crazy. Having an FSM to track where the user is in the process really makes it easier to code and easier to ensure that the process is correct via static analysis and using the FSM2 [visualizer](/visualise-your-fsm).

## Widget State

Screen state is often enough to track a widget's state by using a combination of setState and a Provider.

But again, like Screen State, you may often have a widget that is particularly complex, and having an FSM to track its transitions can be useful.

## Whole of Application State

This is what I'm not a fan of, a single state machine that attempts to describe the state of an entire application.&#x20;

I suspect that the reason we have some many alternate state providers is that they are attempting to do too much. &#x20;

Keep things simple, use a state machine where it makes sense.

Don't use a state machine just because everyone else is.

###

###

###

##

###

###


# Debugging your FSM

FSM2 provides a number of tools to help debug your FSM.

Firstly we strongly recommend that you only use the static transitions (i.e. don't use onDynamic). As yet we have found no use cases where onDynamic is needed and it makes debugging your FSM much harder.

## Static Analysis

The first thing you should ALWAYS check is that your FSM is consistent.

You can do this by calling [analysis](/debugging-your-fsm/static-analysis)().

```
statemachine.analysis();
```

You should create a unit test that tests your statemachine.

```dart
import 'package:fsm2/fsm2.dart';
import 'package:test/test.dart';

void main() {
  test('export', () async {
    final machine = _createMachine();
    expect(await machine.analyse(), equals(true));
  });
}

StateMachine _createMachine() {
  return StateMachine.create((g) => g
    ..initialState<Heating>()
    ..state<DoorOpen>((b) {})
    ..state<Heating>((b) => b
      ..on<OpenDoor, DoorOpen>()
      ..state<Toasting>((b) {})
      ..state<Baking>((b) {})));

}
class DoorOpen extends State {}
class Toasting extends State {}
class Baking extends State {}
class Heating extends State {}
class LightOn extends State {}
class OpenDoor extends Event {}
class OnTurnOff extends Event {}
```

## Visualise your FSM

If you have followed our recommendation to not use 'onDynamic' then you can create a visualisation of your FSM.

```dart
void main() {
    final machine = _createMachine();
    await machine.export('test/test.gv');
  }
```

See details on [visualisation](/visualise-your-fsm) for details on viewing the .gv file.

## Simplify your FSM

We are not big fans of humongous state machines. Rather we recommend that you create a statemachine for each part of your code.

You might have an FSM for each screen or particular db updates but avoid the temptation to model your entire app as a single statemachine. This will never end well.

## Log Transitions

The statemachine allows you to hook every transition by calling 'onTransition'.

```dart
  StateMachine.create((g) => g
      ..initialState<Solid>()
      ..state<Solid>((b) => b
        ..on<OnMelted, Liquid>(sideEffect: (e) => watcher.log(onMeltedMessage))
        ..onEnter((s, e) => watcher?.onEnter(s))
        ..onExit((s, e) => watcher?.onExit(s)))
      ..onTransition((fromState, event, toState) => print('${fromState} ${event} ${toState} ')));
```

The above code prints the eventType, fromState, and toState for every transition.

{% hint style="info" %}
A single event can result in multiple transitions if your state machine is in a concurrent region.
{% endhint %}

## Unit Testing

We provide a number of helper functions that make it easier to unit test your statemachine.

* StateMachine.waitUntilQuiescent
* StateMachine.isInState
* StateMachine.stateOfMind

```dart

  test('test fsm', () async {
      final machine = _createMachine<Solid>(watcher);
      machine.applyEvent(OnMelted());
      // wait for the event to have been applied
      await machine.waitUntilQuiescent;
      expect(machine.isInState<Liquid>(), equals(true));
  });
```


# Static Analysis

FSM2 is able to perform a static analysis on your state machine. The static analysis tool is able to detect a number of issues with your FSM including:

* States which cannot be reached
* Duplicate States
* Events which target an non-registered state.

To perform a static analysis I usually set up a unit tests. This allows me to manually run the static analysis with a single click on the unit test.

```dart
  test('export', () async {
    final machine = _createMachine();
    await machine.analyse();
  });
```

##


# Visualise your FSM

It can be extremely useful to visualise you FSM and with that in mind FSM2 is able to export your FSM to the smcat format and then convert that to an svg file (image).

<https://github.com/sverweij/state-machine-cat>

Start by exporting your fsm2 file to an smcat file.

```dart
final machine = StateMachine.create((g) => g
    ..initialState(Solid())
    ..state<Solid>((b) => b
      ..on<OnMelted>((s, e) => b.transitionTo(
            Liquid(),
            sideEffect: (e) => print('Melted'),
          )))
   ));
await machine.export('/path/to/file.smcat');
```

#### Install

Next we need to install the visualisation tools.

```bash
pub global activate fsm2
fsm2 --install
```

#### Generate

Now you can generate svg files to show your fsm.

```bash
fsm2 /path/to/file.smcat
```

fms2 will now generate an svg file which you can view using any suitable svg viewer.

### Display

You now have two options to view the generated svg files.

#### Firefox

If you have firefox installed the fsm2 can automatically display the svg using firefox:

```bash
fsm2 --show /path/to/file.smcat
```

#### FSM2 Viewer

We have now created a flutter desktop application call fsm2\_viewer.

{% embed url="<https://github.com/bsutton/fsm2_viewer>" %}

![Desktop application for viewing all the svg pages of your FSM.](/files/-MOiog8zuauj0G8urq90)

#### Page Breaks

When you FSM gets large it can be had to analyse the entire statemachine one page.

FSM2 allows you to put page breaks into rendered image.

You can add a page break at any state of coregion.

```dart
final machine = StateMachine.create((g) => g
    ..initialState<S>()
    ..state<Alive>((b) => b
      ..initialState<Young>()
      ..onEnter((s, e) async => watcher.onEnter(s, e))
      ..onExit((s, e) async => watcher.onExit(s, e))
      ..on<OnBirthday, Young>(
          condition: (e) => human.age < 18, sideEffect: (e) async => human.age++)
      ..on<OnBirthday, MiddleAged>(
          condition: (e) => human.age < 50, sideEffect: (e) async => human.age++)
      ..on<OnBirthday, Old>(
          condition: (e) => human.age < 80, sideEffect: (e) async => human.age++)
      ..on<OnDeath, Purgatory>()
      ..state<Young>((b) => b..onExit((s, e) async => watcher.onExit(s, e)))
      ..state<MiddleAged>(
          (b) => b..onEnter((s, e) async => watcher.onEnter(s, e)))
      ..state<Old>((b) => b))
    ..state<Dead>((b) => b
      ..pageBreak
      ..onEnter((s, e) async => watcher.onEnter(s, e))

      /// ..initialState<InHeaven>()
      ..state<Purgatory>((b) => b
        ..onEnter((s, e) async => watcher.onEnter(s, e))
        ..on<OnJudged, Buddhist>(
            condition: (e) => e.judgement == Judgement.good,
            conditionLabel: 'good')
        ..on<OnJudged, Catholic>(
            condition: (e) => e.judgement == Judgement.bad,
            conditionLabel: 'bad')
        ..on<OnJudged, SalvationArmy>(
            condition: (e) => e.judgement == Judgement.ugly,
            conditionLabel: 'ugly'))
      ..state<InHeaven>((b) => b..state<Buddhist>((b) => b))
      ..state<InHell>((b) => b
        ..state<Christian>(
            (b) => b..state<SalvationArmy>((b) {})..state<Catholic>((b) => b))))
    ..onTransition((from, event, to) => watcher.log('${event.runtimeType}')));
```

In the above example the 'Dead' will appear on two pages. It will appear on page 1 as a simple state and it will appear on  page 1 as the parent state of any nested children. The blue border indicates a parent state whose contents is displayed on the next page.

![Page 1](/files/-MODgmGTY7RZzXn5A9-l)

![Page 2](/files/-MODgpOjOmrK_I8zlqNE)

#### Watch and refresh

Whilst working on you fsm it can be handy to see the latest version of the FSM.

You can use the fsm2 watch option to regenerate and display the latest svg file:

```bash
fsm2 -s -w /path/to/file.smcat
```

Now each time you regenerate the smcat file fsm2 will launch firefox with the latest version.

#### Script it

You can use [dcli](https://pub.dev/packages/dcli) to re-generate and display your smcat file on demand.

{% hint style="info" %}
Dcli is a library and tooling for creating cli apps using dart.
{% endhint %}

```bash
pub global activate dcli
cd /your/projectroot/tool
dcli create gen_smcat.dart
```

Dcli will create a sample script and a pubspec.yaml in the tool directory. Edit the pubspec.yaml in the tool directory to include your own project:

```bash
name: gen_smcat
version: 0.0.1
description: A script generated by dcli.
environment: 
  sdk: '>=2.9.0 <3.0.0'
dependencies: 
  args: ^1.0.0
  dcli: ^0.32.0
  path: ^1.0.0
  fsm2: ^0.10.0
  <your project name>:
    path: ..

dev_dependencies: 
  pedantic: ^1.0.0
```

Replace the contents of gen\_smcat.dart with:

```bash
#! /usr/bin/env dcli

import 'dart:io';
import 'package:dcli/dcli.dart';
/// path to script that generates fsm 
import '../cleaning_air_fsm.dart';
 
void main(List<String> args) {

  // replace 'createMachine' with a call to your method
  // that creates your fsm.
  
  var machine = createMachine();
  machine.export('cleaning_air.smcat');

}

```

To generate and visualise your FSM.

Open two terminal windows

Use the first terminal window to regenerate the smcat file each time you change your fsm.

```bash
tool/gen_smcat.dart
```

Use the second terminal to have fsm2 watch for changes and automatically display them.

```bash
fsm2 --show tool/cleaning_air.smcat
```


# Water Example

### Example

A simple example showing the life cycle of H2O.

```dart
import 'package:fsm2/fsm2.dart';

void main() async {
  final machine = StateMachine.create((g) => g
    ..initialState<Solid>()
    ..state<Solid>((b) => b..on<OnMelted, Liquid>(sideEffect: () => print('Melted')))
    ..state<Liquid>((b) => b
      ..onEnter((s, e) => print('Entering ${s.runtimeType} State'))
      ..onExit((s, e) => print('Exiting ${s.runtimeType} State'))
      ..on<OnFroze, Solid>(sideEffect: (e) => print('Frozen'))
      ..on<OnVaporized, Gas>(sideEffect: (e) => print('Vaporized')))
    ..state<Gas>((b) => b..on<OnCondensed, Liquid>(
        sideEffect: (e) => print('Condensed')))
    ..onTransition((t) => print(
        'Recieved Event ${t.eventType} in State ${t.fromState.runtimeType} transitioning to State ${t.toState.runtimeType}')));

  /// static analysis of the state machine.
  /// only do this during testing.
  await machine.analyse();
  
  /// export the statemachine to a dot file so we can visualise it.
  await machine.export('test/test.gv');

  print(await machine.isInState<Solid>()); // TRUE

  machine.applyEvent(OnMelted());
  print(await machine.isInState<Liquid>()); // TRUE

  machine.applyEvent(OnFroze());
  print(await machine.isInState<Solid>()); // TRUE
}

class Solid extends State {}

class Liquid extends State {}

class Gas extends State {}

class OnMelted extends Event {}

class OnFroze extends Event {}

class OnVaporized extends Event {}

class OnCondensed extends Event {}

```


# Contributing

## Overview

## Overview <a href="#overview" id="overview"></a>

So how can you get involved?

## Objective <a href="#objective" id="objective"></a>

The objective of the `FSM2` project is to provide a simple yet powerful Finite State Machine implementation.

## Documentation <a href="#documentation" id="documentation"></a>

Any project is only as good as its documentation.

A key objective of `FSM2` is to provide quality documentation which makes it easy for both experienced and novices users to use the `FSM2` package.

## Community <a href="#community" id="community"></a>

Development is to be done as a collaborative approach to problem solving.

New ideas should be started by raising an issue to give the community a chance to discuss the pros and cons of the proposed change.

The community should be supportive of all contributors and users and negative language will not be tolerated.

### Coding Standards <a href="#coding-standards" id="coding-standards"></a>

The dart code base will adhere the 'effective dart' lint rules.

Code MUST be free of errors and warnings before it will be accepted.

All code must be formatting using the standard dartfmt tool before it is submitted.

Code MUST be well commented.

All public API's must include examples code in the comments.

Careful consideration is to be given to what methods/class are exposed as part of the public api. The objective is to expose the smallest API possible.

Abbreviations in variable, method and class names should be avoided.

The code should attempt to adhere to conventions that are recognisable to the broader Flutter community and any recommended by the Google Flutter team.

## Minimise included packages <a href="#minimise-included-packages" id="minimise-included-packages"></a>

Dart suffers from dll hell (where different package version from different dependencies conflict). To avoid being a source of dll hell we should aim to include the minimal set of package dependencies possible. We will have to balance this requirement with effort and long term maintenance issues.

ReCase is a good example. In reviewing the code base I found we had only used 3-4 lines of code from the package. So I copied those lines into our own class. Minimal maintenance will be required and we have eliminated usage of a popular package.

## Unit Tests <a href="#unit-tests" id="unit-tests"></a>

Code changes will need to be submitted with Unit Test that provides reasonable coverage of the new feature.


# Features and bugs

### Features and bugs

Please file feature requests and bugs at the [issue tracker](https://github.com/bsutton/fsm2/issues).


# References

A list of references I used in creating FSM2

<https://lpuguidecom.files.wordpress.com/2016/10/state_machine_diagrams-final.pdf>

{% embed url="<https://docs.spring.io/spring-statemachine/docs/current/reference>" %}


