Skip to content
This repository has been archived by the owner on Sep 25, 2018. It is now read-only.

Serialization and Deserialization of "snapshots"

jbeard4 edited this page Mar 1, 2013 · 1 revision

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.

Public SCION API

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.