From Model to SMC

This section shows a quasi-UML state machine snipet and the equivalent SMC code. I use the word "quasi" because SMC is not directly derived from UML or Harel state machines. That means that there are capabilities in UML that are not in SMC and vice versa. See the SMC FAQ for why this is.

Instantiating a Finite State Machine

Care must be taken when instantiating an SMC-generated finite state machine. The application class passes a its reference to the FSM context and this reference is used when FSM actions are performed. It is safe to instantiation an FSM within a constructor but unsafe to enter the start state while in a constructor because the start state entry actions will call your application object before its constructor has completed.

private final AppClassContext _fsm; public AppClass() { // Initialize you application class. // Instantiate your finite state machine. // Note: it is safe to pass this to the the FSM // constructor because the FSM constructor only // stores this in a data member. _fsm = new AppClassContext(this); } // Enter the FSM start state after instantiating this // application object. public void startWorking() { _fsm.enterStartState(); return; }

Prior to SMC v. 6, the FSM constructor incorrectly used the this pointer which meant instantiating the FSM within the application constructor could lead to incorrect behavior. SMC v. 6 corrects this problem and it is now safe to instantiate the FSM within your application constructor.

The enterStartState method should be called only once after instantiating the finite state machine and prior to issuing any transition. This method is unprotected and does not prevent its being called multiple times. If enterStartState is called after the first transition, then incorrect behavior may occur.

A Simple Transition

Simple Transition
// State Idle { // Trans Next State Actions Run Running {} }

A state and transition names must have the form "[A-Za-z_][A-Za-z0-9_]*".

A Jump Transition

Jump Transition
// State Idle { // Trans Next State Actions Run jump(Running) {} }

The Jump transition is equivalent to the Simple Transition and is provided since this transition is used in an Augmented Transition Network.

In a future SMC release, the Jump transition will become the only way for make an end state outside the current map. With the syntax : jump(AnotherMap::NextState)

An External Loopback Transition

External Loopback Transition
// State Idle { // Trans Next State Actions Timeout Idle {} }

An external loopback does leave the current state and comes back to it. This means that the state's exit and entry actions are executed. This is in contrast to an internal loopback transition.

An Internal Loopback Transition

Internal Loopback Transition
// State Idle { // Trans Next State Actions Timeout nil {} }

Using "nil" as the next state causes the transition to remain in the current state and not leave it. This means that the state's exit and entry actions are not executed. This is in contrast to an external loopback transition.

A Transition with Actions

Transition Action
// State Idle { // Trans Run // Next State Running // Actions { StopTimer("Idle"); DoWork(); } }
  1. A transition's actions must be enclosed in a "{ }" pair.
  2. The action's form is "[A-Za-z][A-Za-z0-9_-]*(<argument list>)". The argument list must be either empty or consist of comma-separated literals. Examples of literals are: integers (positive or negative, decimal, octal or hexadecimal), floating point, strings enclosed in double quotes, constants and transition arguments.
  3. Actions must be member functions in the %class class and accessible by the state machine. Usually that means public member functions in C++ or package in Java.

Action arguments include:

If you want to call a method in the context class, then use the ctxt variable. For example, if the context class contains a method getName() and you want to call it inside an action's argument list, then write ctxt.getName().

Go here for sample code using the ctxt variable.

Note: Only use ctxt inside argument lists and transition guards.

Transition Guards

Transition Guard
// State Idle { // Trans Run // Guard condition [ctxt.isProcessorAvailable() == true && ctxt.getConnection().isOpen() == true] // Next State Running // Actions { StopTimer("Idle"); DoWork(); } Run nil {RejectRequest();} }

The guard must contain a condition that is valid target language source code - that is, it would be a valid "if" statement. Your guard may contain &&s, ||s, comparison operators (==, <, etc.) and nested expressions. SMC copies your guard condition verbatim into the generated output.

Note: If your are calling a context class method, then you must prefix the method with ctxt - SMC will not append ctxt for you.

If the guard condition evaluates to true, then the transition is taken. If the guard condition evaluates to false, then one of the following occurs (ordered by precedence):

  1. If the state has another guarded transition with the same name and arguments, that transition's guard is checked.
  2. Failing that, If the state has another unguarded transition with the same name and argument list, that transition is taken.
  3. If none of the above, then the default transition logic is followed.

A state may have multiple transitions with the same name and argument list as long as they all have unique guards. When a state does have multiple transitions with the same name, care must be taken when ordering them. The state machine compiler will check the transitions in the same top-to-bottom order that you use except for the unguarded version. That will always be taken only if all the guarded versions fail. Guard ordering is only important if the guards are not mutually exclusive, i.e., it is possible for multiple guards to evaluate to true for the same event.

Allowable argument types for a transition guard are the same as for a transition action.

Transtion Arguments

Transition Argument
// State Idle { // Transition Run(msg: const Message&) // Guard condition [ctxt.isProcessorAvailable() == true && msg.isValid() == true] // Next State Running // Actions { StopTimer("Idle"); DoWork(msg); } Run(msg: const Message&) // Next State Actions nil {RejectRequest(msg);} }

Note: When using transition guards and transition arguments, multiple instances of the same transition must have the same argument list. Just as with C++ and Java methods, the transitions Run(msg: const Message&) and Run() are not the same transition. Failure to use the identical argument list when defining the same transition with multiple guards will result in incorrect code being generated.

Tcl "arguments":

While Tcl is a type-less language, Tcl does distinguish between call-by-value and call-by-reference. By default SMC will generate call-by-value Tcl code if the transition argument has no specified type. But you may use the artificial types "value" or "reference".

If your Tcl-targeted FSM has a transition:

DoWork(task: value) Working { workOn(task); }

then the generated Tcl is:

public method DoWork {task} { workOn $this $task; }

If your Tcl-targeted FSM has a transition:

DoWork(task: reference) Working { workOn(task); }

then the generated Tcl is:

public method DoWork {task} { workOn $this task; }

The method workOn must upvar the task parameter:

public method workOn {taskName} { upvar $taskName task; ... }

Lua/Python/Ruby "arguments":

While Lua/Python/Ruby is a dynamically typed language and does not use types for function parameter definitions, you could provide a optional data type for transition arguments. This "data type" is ignored when generating the target Lua/Python/Ruby code. I suggest using meaningful type names.

DoWork(task: TaskObj, runtime: Ticks) Working { ... }

Groovy/PHP "arguments":

While Groovy/PHP gives the choice between static and dynamic typing, you could provide a optional data type for transition arguments. In this case, the type is used when generating the target Groovy/PHP code.

The PHP variable syntax is like Perl (named with '$').

Perl "arguments":

While Perl is a dynamically typed language and does not use types for function parameter definitions, you could provide a optional data type for transition arguments. This "data type" is ignored when generating the target Perl code. I suggest using meaningful type names.

Only Perl scalar values (ie, named with '$') are allowed.

DoWork($task: TaskObj, $runtime: Ticks) Working { ... }

Entry and Exit Actions

Entry, Exit Actions
// State Idle Entry {StartTimer("Idle", 1); CheckQueue();} Exit {StopTimer("Idle");} { // Transitions }

When a transition leaves a state, it executes the state's exit actions before any of the transition actions. When a transition enters a state, it executes the state's entry actions. A transition executes actions in this order:

  1. "From" state's exit actions.
  2. Set the current state to null.
  3. The transition actions in the same order as defined in the .sm file.
  4. Set the current state to the "to" state.
  5. "To" state's entry actions.
As of version 6.0.0, SMC generates a enterStartState method which executes the start state's entry acitons. It is now up to the application to call the start method after instantiating the finite state machine context. If it is not appropriate to execute the entry actions upon start up, then do not call enterStartState. You are not required to call this method to set the finite state machine start state. That is done when the FSM is instantiated. This method is used only to execute the start state's entry actions.

If you do call this method, be sure to do it outside of the context class' constructor. This is because entry actions call context class methods. If you call enterStateState from within you context class' constructor, the context instance will be referenced before it has completed initializing which is a bad thing to do.

enterStartState does not protect against being called multiple times. It should be called at most once and prior to issuing any transitions. Failure to follow this requirement may result in inappropriate finite state machine behavior.

Whether a state's Entry and Exit actions are executed depends on the type of transition taken. The following table shows which transitions execute the "from" state's Exit actions and which transitions execute the "to" state's Entry actions.

Transition Type Execute "From" State's
Exit Actions?
Execute "To" State's
Entry Actions?
Simple Transition Yes. Yes.
External Loopback Transition Yes. Yes.
Internal Loopback Transition No. No.
Push Transition No. Yes.
Pop Transition Yes. No.

WARNING! Entry and exit actions are not supported for the Default state which is not an actual state. See more in the "Default Transitions" section.

From this point on, SMC diverges from UML. SMC uses the idea of multiple machines and pushing and popping states as way of breaking complicated behavior up into simpler parts. UML acheives much the same by grouping states into superstates. They may be equivalent in ability but I find the idea of pushing to a new state easier to understand because it is similar to the subroutine call.

Push Transition

Push Transition
Running { Blocked push(WaitMap::Blocked) {GetResource();} }

Note: The end state does not have to be in another map - it could be in the same %map construct. Conversely, a plain transition's end state may be in another map. But chances are that you will set up maps so that you will push to another map's state and simple transitions will stay within the same map. You use multiple maps for the same reason you create multiple subroutines: to separate out functionality into easy-to-understand pieces.

With SMC v. 1.3.2, the push syntax was modified yet is backward compatible with the initial syntax. The new syntax is

Running { Blocked BlockPop/push(WaitMap::Blocked) {GetResource();} }

This causes the state machine to:

  1. Transition to the BlockPop state.
  2. Execute the BlockPop entry actions.
  3. Push to the WaitMap::Blocked state.
  4. Execute the WaitMap::Blocked entry actions.

When WaitMap issues a pop transition, control will return to BlockPop and the pop transition is issued from there.

Use this new syntax when a state has two different transitions which push to the same state but need to handle the pop transition differently. For example:

Idle { NewTask NewTask/push(DoTask) {} RestartTask OldTask/push(DoTask) {} } NewTask { TaskDone Idle {} // Try running the task one more time. TaskFailed OldTask/push(DoTask) {} } OldTask { TaskDone Idle {} TaskFailed Idle {logFailure();} }

Pop Transition

Pop Transition
Waiting { Granted pop(OK) {cleanUp();} Denied pop(FAILED) {cleanUp();} }

The pop transition differs from the simple and push transition in that:

In the above example, if the resource request is granted, the state machine returns to the corresponding state that did the push and then takes that state's OK transition. If the request is denied, the same thing happens except the FAILED transition is taken. The code for the corresponding push transition is:

Running { Blocked push(WaitMap::Blocked) {GetResource();} // Handle the return "transitions" from WaitMap. OK nil {} FAILED Idle {Abend(INSUFFICIENT_RESOURCES);} }

As of SMC v. 1.2.0, additional arguments may be added after the pop transition's transition argument. These additional arguments are like any others passed to an action and will be passed into the named transition. Following the above example, given the pop transition pop(FAILED, errorCode, reason), then the FAILED should be coded as:

FAILED(errorCode: ErrorCode, reason: string) Idle { Abend(errorCode, reason); }

Default Transitions

What happens if a state receives a transition that is not defined in that state? SMC has two separate mechanisms for handling that situation.

The first is the "Default" state. Every %map may have a special state named "Default" (the uppercase D is significant). Like all other states, the Default state contains transitions.

Default { // Valid run request but transition occurred in an invalid // state. Send a reject reply to valid messages. Run(msg: const Message&) [ctxt.isProcessorAvailable() == true && msg.isValid() == true] nil { RejectRequest(msg); } // Ignore invalid messages are ignored when received in // an invalid state. Run(msg: const Message&) nil {} Shutdown ShuttingDown { StartShutdown(); } }

Default state transitions may have guards and arguments features as non-default transitions. This means the Default state may contain multiple guarded and one unguarded definition for the same transition.

The second mechanism is the "Default" transition. This is placed inside a state and is used to back up all transitions.

Connecting { // We are now connected to the far-end. Now we can logon. Connected Connected { logon(); } // Any other transition at this point is an error. // Stop the connecting process and retry later. Default RetryConnection { stopConnecting(); } }

Because any transition can fall through to the Default transition, Default transitions:

Transition Precedence

Transition definitions have the following precedence:

  1. Guarded transition
  2. Unguarded transition
  3. The Default state's guarded definition.
  4. The Default state's unguarded definition.
  5. The current state's guarded Default transition.
  6. The current state's unguarded Default transition.
  7. The Default state's guarded Default transition.
  8. The Default state's unguarded Default transition.
Since SMC does not force you to specify a Default state or a Default transition, it is possible that there is no transition defined. If SMC falls through this list, it will throw a "Transition Undefined" exception.