Be Persistant!

This section describes how to persist an SMC-generated finite state machine and restore it at a later date. In the following examples I persist the FSM to a flat file but they can be modified to work with other storage types. The focus is only capturing the FSM's current state and state stack. There is no other data to persist in the SMC-generated code.

The examples use the class AppClass which has an associated finite state machine stored in its data member _fsm.

Note: the following sample code requires the .sm file be compiled with the -serial option.


C++ Transition Queue


If you are not using push/pop transitions, then the use the following code to persist the current state:

int
AppClass::serialize(
const char *
filename)
const
{
int
fd;
int
stateId(_fsm.getState().getId());
int
retcode(-1); fd =
open
(filename, (O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if (fd >= 0) { retcode =
write
(fd, &stateId,
sizeof
(
int
)); (
void
)
close
(fd); fd = -1; }
return
(retcode); }

Deserializing is the mirror:

int
AppClass::deserialize(
const char *
filename)
const
{
int
fd;
int
stateId;
int
retcode(-1);
// This code assumes _fsm is already instantiated.
fd =
open
(filename, O_RDONLY); if (fd >= 0) { retcode =
read
(fd, &stateId,
sizeof
(
int
)); if (retcode >= 0) { _fsm.setState(_fsm.valueOf(stateId)); } (
void
)
close
(fd); fd = -1; }
return
(retcode); }

If you are using push/pop transitions, then the serialization will be require your to persist the state stack in reverse order (bottom to top) followed by the current state.

Warning! Reading in the state stack results in emptying the stack and corrupting the FSM. This should not be a problem because you are persisting the FSM for use later when you will restore the state stack.

int
AppClass::serialize(
const char *
filename)
const
{
int
fd;
int
retcode(-1); fd =
open
(filename, (O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if (fd >= 0) {
int
size(_fsm.getStateStackDepth() + 1);
int
bufferSize(size *
sizeof
(
int
));
int
buffer[size + 1];
int
i;
// Copy the states into the buffer in reverse order: // from the state stack bottom to the top and the current // state last. The first element is the number of states.
buffer[0] = size; buffer[size] = (_fsm.getState()).getId(); for (i = (size - 1); i > 0; --i) { _fsm.popState(); buffer[i] = (_fsm.getState()).getId(); } retcode =
write
(fd, buffer, (bufferSize +
sizeof
(
int
))); (
void
)
close
(fd); fd = -1; }
return
(retcode); }

When reading in the persisted FSM, first read the state count and then read in the states:

int
AppClass::deserialize(
const
char *
filename) {
int
fd;
int
size;
int
retcode(-1);
// The FSM's current state is probably set to the start // state. Clear it out because it is not correct.
_fsm.clearState();
// Open the file for reading and then read in the number // of persisted states.
fd =
open
(filename, O_RDONLY); if (fd >= 0 &&
read
(fd, &size,
sizeof
(
int
)) >= 0) {
int
bufferSize(size *
sizeof
(
int
));
int
buffer[size]; if (
read
(fd, buffer, bufferSize) >= 0) {
int
i;
// Note: Do not call setState for the final current // state because pushState actually sets the current // state while pushing the next state on the stack.
for (i = 0; i < size; ++i) { _fsm.pushState(_fsm.valueOf(buffer[i])); } } }
// Make sure the file is closed before leaving.
if (fd >= 0) { (
void
)
close
(fd); fd = -1; }
return
(retcode); }

Java Transition Queue


SMC makes full use of Java's object serialization. Assuming that AppClass declares implements java.io.Serializable and that the _fsm member data is not marked as transient, then by serializing the AppClass instance will also serialize the FSM.

SMC considers the code it generates to be subservient to the application code. For this reason the SMC code does not serialize its references to the FSM context owner or property listeners. The application code after deserializing the FSM must call the setOwner method to re-establish the application/FSM link. If the application listens for FSM state transitions addStateChangeListener must also be called to put the listeners in place.

public final class
AppClass
implements
java.io.Serializable
{
private
AppClassContext
_fsm;
public
AppClass() { _fsm =
new
AppClassContext
(); }
// Restore the FSM context's reference to this object. // This is necessary because AppClass and AppClassContext // reference each other. When AppClass is serialized, its // AppClassContext instance is serialized. But when // AppClassContext is serialized, its AppClass instance is // not serialized which breaks the circual reference. When // AppClassContext is deserialized, its AppClass reference // is null. Hence the need to implement readObject // and reset AppClassContext's AppClass reference. // The state change property listeners list is empty for // the same reason.
private
void
readObject(
java.io.ObjectInputStream
istream)
throws
java.io.IOException
,
ClassNotFoundException
{
// Do the default read first which sets _fsm to null.
istream.defaultReadObject();
// Now set the FSM's owner.
_fsm.setOwner(
this
);
// State change listeners must also be added back.
_fsm.addStateChangeListener(_stateListener);
return
; }
public static
void
main(
String[]
args) { Serialize(); Deserialize(); }
public static
void
Serialize() {
AppClass
appInstance =
new
AppClass
();
ObjectOutputStream
ostream =
new
ObjectOutputStream
(
new
FileOutputStream
(
"./fsm_serial.bin"
));
try
{ ostream.writeObject(appInstance); }
catch
(
java.io.IOException
ioex) {
// Handle serialization exception.
}
finally
{ ostream.close(); } } ... }

Recreating the persisted AppClass instance is equally simple:

public static
void
Deserialize() {
AppClass
appInstance =
null
;
ObjectInputStream
istream =
new
ObjectInputStream
(
new
FileInputStream
(
"./fsm_serial.bin"
));
try
{ appInstance = (
AppClass
) istream.readObject(); }
catch
(
java.io.IOException
ioex) {
// Handle deserialization exception.
}
finally
{ istream.close(); } }

Tcl Transition Queues


Note: the following sample code requires the .sm file is compiled with the -serial option.

Tcl persistance is similar to C++. If you are not using push/pop transitions, then the use the following code to persist the current state:

public method
serialize {fileName} {
if
[
catch
{
open
$fileName w 0644} fileId] {
set
retcode
error
;
set
retval
"Failed to open ${filename} for writing"
; }
else
{
puts
$fileId [[$_fsm getState] getId];
close
$fileId;
set
retcode
ok
;
set
retval
""
; }
return
-code ${retcode} ${retval}; }

The following deserializes the current state:

public method
deserialize {fileName} {
if
[
catch
{
open
$fileName r} fileId] {
set
retcode
error
;
set
retval
"Failed to open ${filename} for reading"
; }
else
{
gets
$fileId stateId; $_fsm setState [$_fsm valueOf $stateId];
close
$fileId;
set
retcode
ok
;
set
retval
""
; }
return
-code ${retcode} ${retval}; }

If you are using push/pop transitions, then the serialization will be require your to persist the state stack in reverse order (bottom to top) followed by the current state.

Warning! Reading in the state stack results in emptying the stack and corrupting the FSM. This should not be a problem because you are persisting the FSM for use later when you will restore the state stack.

public method
serialize {fileName} {
if
[
catch
{
open
$fileName w 0644} fileId] {
set
retcode
error
;
set
retval
"${fileName} open failed"
; }
else
{
set
state [$_fsm getState];
set
states {};
lappend
states [$state getId];
while
{[
catch
{$_fsm popState} retcode] == 0} {
set
state [$_fsm getState];
set
states [
linsert
$states 0 [$state getId]]; }
set
size [
llength
$states];
puts
$fileId $size;
foreach
stateId $states {
puts
$fileId $stateId; }
close
$fileId;
set
retcode ok;
set
retval
""
; }
return
-code ${retcode} ${retval}; }

When reading in the persisted FSM, first read the state count and then read in the states:

public method
deserialize {fileName} {
if
[
catch
{
open
$fileName r} fileId] {
set
retcode
error
;
set
retval
"${fileName} open failed"
; }
else
{
# Clear out the default start state.
$_fsm clearState;
gets
$fileId size;
for
{
set
i 0} {$i < $size} {
incr
i} {
gets
$fileId stateId;
set
state [$_fsm valueOf $stateId]; $_fsm pushState $state; }
close
$fileId;
set
retcode
ok
;
set
retval
""
; }
return
-code ${retcode} ${retval}; }

VB.net


SMC makes full use of .net's object serialization. Assuming that AppClass has the <Serializable()> attribute and that the _fsm member data is not marked as <NonSerializable()>, then serializing the AppClass instance will also serialize the FSM:

SMC considers the code it generates to be subservient to the application code. For this reason the SMC code does not serialize its references to the FSM context owner or property listeners. The application code after deserializing the FSM must call the Owner property setter to re-establish the application/FSM link. If the application listens for FSM state transitions, then event handlers must also be put back in place.

Imports
System
Imports
System.IO
Imports
System.Runtime.Serialization
Imports
System.Runtime.Serialization.Formatters.Binary
<Serializable()>
Public Class
AppClass
Implements
IDeserializationCallback
Private
_fsm
As
AppClassContext
Public Sub New
() _fsm =
New
AppClassContext(
Me
)
End Sub
' Restore the FSM context's reference to this object. ' This is necessary because AppClass and AppClassContext ' reference each other. When AppClass is serialized, its ' AppClassContext instance is serialized. But when ' AppClassContext is serialized, its AppClass instance is ' not serialized which breaks the circual reference. When ' AppClassContext is deserialized, its AppClass reference ' is Nothing. Hence the need to implement IDeserialization ' and reset AppClassContext's AppClass reference. ' Event handlers must also be put back in place for the same ' reason.
Private Sub
OnDeserialization(
ByVal
send
As
Object
) _
Implements
IDeserializationCallback.OnDeserialization
_fsm.Owner =
Me
AddHandler _fsm.StateChange, handler
End Sub
Shared Sub
Main() Serialize() Deserialize()
End Sub
Shared Sub
Serialize()
Dim
appInstance
As New
AppClass()
Dim
stream
As
Stream
= _
File
.Open(
"fsm_serial.bin"
,
FileMode.Create
)
Dim
formatter
As New
BinaryFormatter
()
Try
formatter.Serialize(stream, appInstance)
Catch
serialex
As
SerializationException
' Handle serialization failure.
Finally
stream.Close()
End Try
End Sub
...
End Class

Recreating the persisted AppClass instance is equally simple:

Shared Sub
Deserialize()
Dim
appInstance
As
AppClass =
Nothing
Dim
stream
As
Stream
= _
File
.Open(
"fsm_serial.bin"
,
FileMode.Open
)
Dim
formatter
As New
BinaryFormatter()
Try
appInstance = _
CType
(formatter.Deserialize(stream), AppClass)
Catch
serialex
As
SerializationException
' Handle deserialization failure.
Finally
stream.Close()
End Try
End Sub

C#


SMC makes full use of .net's object serialization. Assuming that AppClass has the [Serializable] attribute and that the _fsm member data is not marked as [NonSerialized], then serializing the AppClass instance will also serialize the FSM:

SMC considers the code it generates to be subservient to the application code. For this reason the SMC code does not serialize its references to the FSM context owner or property listeners. The application code after deserializing the FSM must call the Owner property setter to re-establish the application/FSM link. If the application listens for FSM state transitions, then event handlers must also be put back in place.

using
System
;
using
System.IO
;
using
System.Runtime.Serialization
;
using
System.Runtime.Serialization.Formatters.Binary
;
[Serializable]
public class
AppClass :
IDeserializationCallback
{
private
AppClassContext _fsm;
public
AppClass() { _fsm =
new
AppClassContext(
this
); }
// Restore the FSM context's reference to this object. // This is necessary because AppClass and AppClassContext // reference each other. When AppClass is serialized, its // AppClassContext instance is serialized. But when // AppClassContext is serialized, its AppClass instance is // not serialized which breaks the circual reference. When // AppClassContext is deserialized, its AppClass reference // is null. Hence the need to implement IDeserialization // and reset AppClassContext's AppClass reference. // Event handlers must also be put back in place for the same // reason.
void
IDeserializationCallback.OnDeserialization(
Object
sender) { _fsm.Owner =
this
; _fsm.StateChange += handler; }
static
void
Main(
string
[] args) { Serialize(); Deserialize(); }
static
void
Serialize() { AppClass appInstance =
new
AppClass();
FileStream
fstream =
new
FileStream
(
"fsm_serial.dat"
, FileMode.Create);
BinaryFormatter
formatter =
new
BinaryFormatter
();
try
{ formatter.Serialize(fstream, appInstance); }
catch
(
SerializationException
serialex) {
// Handle exception.
}
finally
{ fstream.Close(); } } ... }

Recreating the persisted AppClass instance is equally simple:

static
void
Deserialize() { AppClass appInstance =
null
;
FileStream
fstream =
new
FileStream
(
"fsm_serial.dat"
, FileMode.Open);
BinaryFormatter
formatter =
new
BinaryFormatter
();
try
{ appInstance = (AppClass) formatter.Deserialize(fstream); }
catch
(
SerializationException
serialex) {
// Handle exception.
}
finally
{ fstream.Close(); } }