JSpasm State Machine Package

The JSpasm State Machine Package

See:
          Description

Packages
com.codexombie.jspasm  

 

The JSpasm State Machine Package

This is a short introduction to state machines and how to implement them in Java using the JSpasm package.

Event-driven programs

An event driven program is one in which the actions of the program are determined by events delivered to the program indicating that certain conditions have arisen. Usually the program runs in a loop; it waits for the next event then when one is delivered it processes it to decide what action to take. Events are usually delivered via a queue and if more events are created while the program is busy processing, the events are held on the queue until the program is ready to accept them.

Most GUI programs written for graphical operating systems are event-driven. Events are used to signal such things as pointer movements, mouse clicks, key presses and "ticks" from the system's internal clock. Event-driven software is found in many other applications from computer games to the firmware that runs in telephone exchanges.

State machines and models

A state machine is a system in which an entity can be in one of a finite (and usually quite small) number of states, and changes to another state (a transition) when an event is delivered to it. The state it changes to depends upon the starting state and the type of event that is delivered. Usually the state machine executes some code associated with the new state when a transition happens.

State machines can make the development of complicated event-driven programs much easier.

A state model is a representation of a related set of states, events and transitions that describes a process. State models can be represented as diagrams or as tables.

JSpasm features

JSpasm makes it simple to implement state models as Java classes and to attach them to a state engine that uses the models to control the actions of entity objects in response to events originating inside and outside an application. JSpasm allows one state engine to handle multiple state models in parallel, with multiple entities operating in parallel within each model. The JSpasm engine allows modes of multithreaded operation designed to enhance performance on SMP systems.

Designing state models

The general approach to designing a model is:

As an example we'll consider a very simple system: a push-button on/off switch. Following the steps above:

Implementing state models using JSpasm

State models can be implemented in two ways. You can either create an instance of the Model class and call the methods to define the states, events etc., or you can create a subclass of Model and add the definition code to the subclass's constructor. We will use the second approach.

(The complete source code for the following example is available for download at the JSpasm home page.)

Before we define our model we must first create an entity class to use with it. Our model doesn't require any real processing so our entity will be very simple:

public class PushButtonEntity extends AbsEntity
{
  public void offAction()
  {
    System.out.println("Switched OFF");
  }
  
  public void onAction()
  {
    System.out.println("Switched ON");
  }
}

Our model class looks like this:

public class PushButtonModel extends Model
{
  public PushButtonModel(String name) throws Exception
  {
    super(name);
    
    // Define the entity class
    setEntityClass(PushButtonEntity.class);
    
    // Define states
    addState("Off", "offAction", "");
    addState("On", "onAction", "");
    
    // Set the initial state for new entities
    setInitialState("Off");
    
    // Define events
    addEventSpec("push", "");
    
    // Define transitions
    addNormalTransition("Off", "push", "On");
    addNormalTransition("On", "push", "Off");
  }
}

See the API Javadocs for descriptions of what the method calls do. Note that the order of operations (i.e. define the entity class, states, initial state, events and transitions in that order) is the suggested order.

Running and stopping the engine

All that is needed to start the engine is the standard start() call, just like any other thread. The Engine class overrides Thread.start() adding extra code to ensure that control is not returned to the calling thread until the engine has initialized itself to the point where it is safe to create entities and generate events.

To shut the engine down, call shutdown(). This starts the shutdown sequence, in which the engine shuts down all internal threads then terminates. Using join() will ensure that the state machine is completely quiescent before proceeding.

Note that it can take some time for the engine to shut down; if the shutdown() is called at a time when state code that takes a long time to complete is being executed, the shutdown cannot complete until that code has finished.

Now we can write a section of code that sets up an engine and runs an entity in our model. It's very simple:

  // Create a model
  Model pushButtonModel = new PushButtonModel("MyModel");
  
  // Create an engine
  Engine engine = new Engine("My Engine");
  
  // Add the model
  engine.addModel(pushButtonModel);
  
  // Start the engine
  engine.start();
  
  // Now we can create an entity
  engine.createEntity("MyModel", "MyEntity", new Object[0]);
  
  // Push the button a few times
  engine.generateEvent("MyEntity", "push");
  engine.generateEvent("MyEntity", "push");
  engine.generateEvent("MyEntity", "push");
  engine.generateEvent("MyEntity", "push");
  
  // Wait a couple of seconds
  Thread.sleep(2000);
  
  // Now shut the engine down
  engine.shutdown();
  engine.join();

The output from this code should look like this:

Switched OFF
Switched ON
Switched OFF
Switched ON
Switched OFF

What's happening here?

Let's take a look at what's happening as the code runs. The first thing that happens is that we create an entity named "MyEntity" for the model we've named "MyModel". This creates a PushButtonEntity object, because that is the entity class associated with the PushButtonModel class. The new entity's state is set to "Off" (the initial state defined for the model) and the method associated with that state - offAction() - is called for the new entity. This causes the first line of output.

Now we generate a "push" event for delivery to MyEntity. According to the model's transition definitions, this event in the Off state causes a transition to the On state. Accordingly, the entity state is changed to On and the associated method, onAction(), is called. This causes the second line of the output.

Subsequent "push" events switch the entity alternately between Off and On states, causing a line of output with each transition.

Event and state arguments

In our example the state action methods require no arguments and so the events that cause transitions into those states do not need them either. In most models it's likely that you'd want events to carry additional data as arguments to be passed to the state methods. When defining states and events during building of a model, the arguments expected can be passed as arrays of Class objects or as comma/space-separated strings. For example consider a state action called exampleAction() that expects a String and an int as arguments:

  public void exampleAction(String s, int i)
  {
    ..

When defining the state we can supply the expected arguments as a Class array like this:
  Class[] args = { String.class, int.class };
  addState("example", "exampleAction", args);
However, we can use fully-qualified class names in a String, like this:
  addState("example", "exampleAction", "java.lang.String, int");
The string-based mechanism is probably more convenient in most cases. Note that primitives argument types can be specified in a Class array using byte.class, long.class, etc. In the string form just use the primitive type name - int, boolean etc.

For a no-argument method use a zero-length Class array or the empty string "".

Event arguments are provided as arrays of Objects. Primitive arguments (int, boolean, etc.) are inserted into the array by wrapping them using the wrapper classes (Integer for int, etc.). See the API Javadocs for details.

Internal events

Consider a model that has three states named A, C and D and three events named e1, e2 and e3:

However there's a wrinkle: C's state code needs to perform an extra step for the e2 event that is not needed for the e1 event. There is no way for the state code to know which event caused the transition (allowing the state code to be "event-smart" is considered to lead to bad programming practise and so JSpasm deliberately does not provide event information to state code). How do we get round this?

One way is to add an extra state, B, and set the model to transition into state B when the e2 event is delivered in state A. B's state code performs the extra processing step then generates an e1 event that will cause a transition to state C.

Unfortunately this is likely to cause even more problems. Let's say an e2 event is generated and quickly followed by an e3 event. Now B's state code generates the e1 event needed to transit to state C, but this is behind the e3 event on the event queue. As a result the e3 event is delivered to the entity while it is in state B, and our model doesn't allow for that - we'll get a "can't happen" exception from the engine.

We might be able to redesign the model to cope with events delivered out of sequence like this, but it would be extremely messy. To get around the problem, JSpasm allows internal events. These are high-priority events that are generated by an entity for delivery to itself. They are guaranteed to be delivered before normal-priority events.

In this case, B's code would generate the e1 event as an internal event, and then it doesn't matter whether or not the e3 event is on the queue - the e1 event will always be delivered first and so the problem doesn't happen.

Internal events are generated using the generateInternalEvent() method, which is a protected method of the AbsEntity class.

Transition types

Our example model uses only NORMAL transitions. These transitions work exactly the way we've described: the entity switches state and the end state code is executed.

There are three other transition types:

These additional transition types allow for extra flexibility and make it possible to design state models that are much simpler than would otherwise be possible.

Exception handling

The engine has provision for handling exceptions thrown by state code or internally by the engine during normal running (for example, when an event is delivered to an entity in a state that has no transition defined for it). By default, all engines share an instance of BasicExceptionHandler as the target for exception reports. This handler simply prints the exception stack trace on standard output.

If your application requires different handling of exceptions (reporting via a Log4J Logger, for example), you can create a handler class by implementing the IExceptionHandler interface and then passing an instance of your handler class to the engine's setExceptionHandler() method. Exceptions will then be passed to your handler instance when they occur.

You can also set the exception handler class by specifying a configuration file as a system property. This is described later.

Setting the engine's exception handler to null will disable reporting of exceptions.

Logging state changes for debugging

By default the engine does not report state change data, but being able to record state changes can be a very useful tool when debugging a state model. To enable reporting, you can create a handler class that implements the IStateChangeHandler interface then pass an instance of this to the engine's setStateChangeHandler() method. The engine will then call the handler's handleStateChange() method passing a StateChangeRecord object as an argument, for every state change that it performs.

The BasicStateChangeHandler class provides a basic implementation that reports state changes one per line on stdout. To enable its use:

  engine.setStateChangeHandler(new BasicStateChangeHandler());

You can also set the state change handler class by specifying a configuration file as a system property. This is described later.

Setting the state change handler to null disables reporting of state changes.

Thread model

There are three thread schemes available:

The thread scheme is set by calling the engine's setThreadScheme() method and, for thread-per-engine mode, setThreadCount() sets the number of processing threads. See the API description for details.

Note that any state method that performs a blocking operation (waiting for user input, for example) will stop the associated thread completely until it becomes unblocked.

Engine properties

Certain engine characteristics, as well as user-defined values for use by the application, can be set in a properties file and loaded by the Engine class automatically when it is loaded. To do this, specify the location of the file as the jspasm.configuration system property. If the file is in the classpath (for example, in a Jar file containing your application) it can be loaded by specifying the path:

  java -Djspasm.configuration=com/mydomain/mypackage/jspasm.properties ...

For files external to the application, the location can be identified similarly:
  java -Djspasm.configuration=file:configuration/jspasm.properties ...
Note that in this case the value is a URL and requires a protocol - use file: as shown above when the properties file is on the machine where the application is running.

The properties file may contain these keys:

The properties file can also be used to make application-specific settings available to user code. The Properties object loaded from the file is a static field publicly accessible as Engine.cfgProp.

Performance

Speed

The speed of the engine depends primarily on the speed of the CPU and to a lesser degree upon the installed JVM, the platform operating system and even the order that JAR files are specified in the classpath.

As a guide, a benchmark using no-argument events/states and null state action methods shows events being generated and delivered at a rate of approximately 450,000 per second when running on a 2.4GHz Pentium IV under Windows XP using the Sun JRE. The benchmark program is available for download on the JSpasm home page.

Memory

The Engine class places no limits on the number of models, entities and events that can exist at any time. As such, it is possible under extreme conditions to create so many entities and events that out-of-memory errors could occur. Developers should be aware of this when developing potentially large systems.

Suggested naming convention

From experience we have found that the following naming guidelines work quite well:

The package does not enforce any naming rules. State names and event names are in different namespaces, so it is valid to have names and events with the same names without problems.

Creating unique entity names

When creating entities it is often necessary to be able to generate entity names that are known to be unique. The java.rmi.server.UID and java.rmi.dgc.VMID classes can be used to generate unique names. See the documentation of those classes for details.

Other information

JSpasm was developed by Pete Ford and is ©Pete Ford & CodeXombie.com. JSpasm is released under the terms of the Lesser GNU Public License (LGPL).

The JSpasm package and the demonstration and benchmark programs mentioned in this document are available for download from the JSpasm homepage. Check the homepage for updates and news about JSpasm.