Join

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

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.

You will always pair a join with a 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.

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.

Last updated