-
Notifications
You must be signed in to change notification settings - Fork 29
Serialization and Deserialization of "snapshots"
This document describes a proposed new feature for SCION: the ability to extract a JSON-serializable object representing the state of a running state machine instance, such that it can be stored in a database, and deserialized into a running state machine instance at a later time. All of the changes on this page entail modification of a single module, SCXML.js. Note that the code in this document should be regarded as helpful pseudocode suggestions, and not as working code, as it has not been tested.
Three things capture the current snapshot of a running SCION interpreter:
- basic configuration (the set of basic states the state machine is in)
- history state values (the states the state machine was in last time it was in the parent of a history state)
- the datamodel
Note that this assumes that the method to serialize a scion.SCXML instance is not called when the interpreter is executing a big-step (e.g. after scion.SCXML.prototype.gen
is called, and before the call to gen
returns). If the serialization method is called during the execution of a big-step, then the inner event queue must also be saved. I do not expect this to be a common requirement however, and therefore I believe it would be better to only support serialization when the interpreter is not executing a big-step.
This feature should be implemented as an public method called scion.SCXML.prototype.getSnapshot()
. This should return a data structure which is a JSON-serializable tuple of [basicConfiguration,historyStateValues,datamodel]
.
Deserialization should be accomplished by passing the tuple in as an optional argument to scion.SCXML.prototype.start()
. The way that start
will de-serialize these serialized data structures will be described below.
#Configuration
Internally, the basic configuration is stored as this._configuration
. this._configuration
contains state references which are not serializable, so a custom function would need to be written for serlializing/deserializing this data structure. Something like:
function serializeConfiguration(){
return this._configuration.map(function(s){return s.id;});
}
function deserializeConfiguration(serializedConfiguration){
return serializedConfiguration.map(function(id){return this._model.states[serializedHistoryValue[id]];},this);
}
#History values
This is stored in this._historyValue
, which maps history state id strings to state references. These state references are not serializable, so a custom function would need to be written for serlializing/deserializing this data structure. Something like:
function serializeHistoryValue(){
var o = {};
for( s in this._historyValue ){
o[s] = this._historyValue[s].id;
}
return o;
}
function deserializeHistoryValue(serializedHistoryValue){
var o = {};
for( s in serializedHistoryValue ){
o[s] = this._model.states[serializedHistoryValue[s]];
}
return o;
}
#Datamodel
The datamodel is stored in this._datamodel
. This is a data structure of the form:
{
dataId : {
get : function(){},
set : function(value){}
},
...
}
The reason why the datamodel is of this form, and not a simple object, is because of SCION's approach to code generation. In order to allow variables declared in the datamodel to be accessed as local variables in ECMAScript expressions in SCXML code, SCION uses lightweight code generation to programmatically construct a scope in which datamodel variables are declared as local variables within the scope. This looks something like following:
(function(){
var dataModelVariable1, datamodelVariable2, datamodelVariableN;
//SCXML ECMAScript expressions would be wrapped in functions and inserted here
})()
this._datamodel
exposes these local variables via objects containing functions called get
and set
.
this._datamodel
is not immediately JSON-serializable, because it contains functions. What is required, then, is to iterate through this._datamodel
. Something like:
function serializeDatamodel(){
var o = {};
for(var dataId in this._datamodel){
o[dataId] = this._datamodel[dataId].get();
}
return o;
}
Code to deserialize this serialized datamodel would then look like the following:
function deserializeDatamodel(serializedDatamodel){
for(var dataId in serializedDatamodel){
this._datamodel[dataId].set(serializedDatamodel[dataId]);
}
}
Note that the datamodel can contain arbitrary JavaScript objects, which may not be JSON-serializable, so it may be useful to introduce a new XML attribute such as "isSerializable" to indicate whether a datamodel variable should be serialized. This is a design issue which bares further exploration.
#Modifications to scion.SCXML.prototype.start
Modifications to scion.SCXML.prototype.start
would look something like the following:
//note the new optional argument "snapshot"
start : function(snapshot) {
//perform big step without events to take all default transitions and reach stable initial state
if (printTrace) pm.platform.log("performing initial big step");
this._configuration.add(this.model.root.initial);
//figure out which require to use when evaluating action code, in the following order:
//the one specified when instantiating the interpreter
//the require of the module importing SCION
//the require of the main module
//this module's require
var actionCodeRequire =
this.opts.require ||
(module.parent &&
module.parent.parent &&
module.parent.parent.require &&
module.parent.parent.require.bind(module.parent.parent)) ||
(require.main &&
require.main.require &&
require.main.require.bind(require.main)) ||
require;
//set up scope for action code embedded in the document
var tmp = this.model.actionFactory(
this.opts.log,
this._cancel.bind(this),
this._send.bind(this),
this.opts.origin,
this.isIn.bind(this),
actionCodeRequire,
pm.platform.parseDocumentFromString);
this._actions = tmp.actions;
this._datamodel = tmp.datamodel;
//-----------------> the following code is new
if(snapshot){
this._configuration = this._deserializeConfiguration(snapshot[0]); //set the initial configuration
this._historyValue = this._deserializeHistoryValue(snapshot[1]); //restore history values
this._deserializeDatamodel(snapshot[2]); //this will update this._datamodel, which is already assigned
}
//<----------------- end of new code
this._performBigStep();
return this.getConfiguration();
}
#Future Work
It might be useful to also save a serializable form of the "model" object which is SCION's intermediate representation. This way it would be impossible to start a state machine with a snapshot corresponding to a different model than the one the state machine was instantiated with. This would be possible, as SCION first converts the SCXML docuent to a JSON-serializable IR before converting it to a "model" object. However, this would be complicated to implement, as the methods to convert an SCXML document to the JSON-serializable IR, and to convert the JSON-serializable IR to the "model" object, are not currently exposed in a suitable way to easily allow them to be used for on-the-fly serialization/deserialization. Therefore, I would suggest storing a serialized copy of the SCXML file along with the snapshot, as a seperate record to be used when deserializing the snapshot.