/**
* Factory for creating the state-machine's <em>raise()</em> function.
*
* Creates implementation for the state-engine's
* <pre>
* {
* name: STRING // engine name / description
* doc: STRING // the URL to the SCXML document that holds the engine's definition
* evalScript: BOOLEAN // true
* onraise: FUNCTION // default impl. for onraise: print debug output (depending on logging-level for DialogEngine / InputEngine)
* onload: FUNCTION // default impl. handling initialization of state-engine after SCXML definition was loaded
* raise: FUNCTION // default impl. for raise-function: use execution-queue for allowing raise-invocations from within SCXML scripts
* }
* </pre>
*
* @class mmir.env.statemachine.engine.exec
*
* @requires cordova.plugins.queuePlugin: needed for Android < 4.4 -> Cordova plugin for non-WebWorker-based execution-queue
*/
define(['mmirf/constants', 'mmirf/scionEngine', 'mmirf/util/extend'], function(constants, createScionEngine, extend) {
/**
* HELPER logging for state-changes
*
* @param {Engine} ctx
* the context for the state-machine, i.e. the DialogEngine or InputEngine instance
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var printDebugStates = function(ctx){
if(!ctx._log.isDebug()){
return;
}
ctx._log.debug(ctx.name, 'current state: ' + JSON.stringify(ctx.getStates()));
ctx._log.debug(ctx.name, 'active states: ' + JSON.stringify(ctx.getActiveStates()));
ctx._log.debug(ctx.name, 'active events: ',+ JSON.stringify(ctx.getActiveEvents()));
ctx._log.debug(ctx.name, 'active transitions: '+ JSON.stringify(ctx.getStates()) + ":"+ JSON.stringify(ctx.getActiveTransitions()));
};
/**
* Factory for base / default implementation for state-engine, returns
* <pre>
* {
* name: STRING // engine name / description
* doc: STRING // the URL to the SCXML document that holds the engine's definition
* evalScript: BOOLEAN // true
* onraise: FUNCTION // default impl. for onraise: print debug output (depending on logging-level for DialogEngine / InputEngine)
* onload: FUNCTION // default impl. handling initialization of state-engine after SCXML definition was loaded
* raise: FUNCTION // raise-function created by envFactory(_instance)
* }
* </pre>
*
* @param {Engine} _engine
* the instance of the SCION state-machine
*
* @param {ExecFactory} envFactory
* the factory for creating the Worker/Thread and the raise() function
*
* @returns the implementation with raise-function
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var _baseFactory = function(_instance, envFactory){ /** @class StateEngineDefaultImpl */ return {
/** @scope StateEngineDefaultImpl */
/** @memberOf StateEngineDefaultImpl */
name : envFactory.name? envFactory.name : 'base_engine',
doc : null,
onraise : function() {
if (this._log.isd()) {
printDebugStates(_instance);
};
},
evalScript : true,
onload : function(scion, deferred) {
//FIX (russa) for jQuery > 2.x: extend() uses isPlainObject(), which's impl. changed in 2.x
// -> isPlainObject() now requires a constructor that is different from the native Object.constructor
// in order to be able to detect, that an object is NOT a plain object
// ... but scion does not provide such a non-native constructor for its _scion property
// (which causes deep-copy in extend() to run into an infinite loop)
// QUICK-FIX: just attach a dummy constructor function, so that isPlainObject will correctly detect property
// _scion as not-a-plain-object (this should really be FIXed in the scion library itself...)
//
//TODO russa: check if we really need a deep copy here (maybe we should make a copy TO scion and replace _instance with the ext. scion obj. ...?)
scion['_scion'].constructor = function dummy(){};
extend(_instance, scion);//<- FIXME should this be a deep-copy? e.g. jQuery.exted(true, ...)
_instance.worker = envFactory.createWorker(_instance, _instance.gen);//_instance._genFuncFactory(_instance, _instance.gen);
//FIXME @russa: is this a general functionality? should this be removed?
if (!_instance.evalScript){
_instance.ignoreScript();
}
// delete _instance.gen;
delete _instance.evalScript;
_instance.start();
deferred.resolve(_instance);
},//END: onload
raise: envFactory.createRaise(_instance)
};
};
/**
* Factory for WebWorker-based implementation of raise function.
*
* Provide creator-functions:
* <code>createWorker(_engineInstance, genFunc) : WebWorker</code>
* <code>createRaise(_engineInstance) : Function</code>
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var _browserFactory = {
/** @scope StateEngineWebWorkerImpl */
/** @memberOf StateEngineWebWorkerImpl */
name: 'webworkerGen',
createWorker: function(_instance, gen) {
var scionQueueWorker = new Worker(
constants.getWorkerPath()+ 'ScionQueueWorker.js'
);
scionQueueWorker._engineInstance = _instance;
scionQueueWorker._engineGen = gen;
scionQueueWorker.onmessage = function(e) {
if (e.data.command == "toDo") {
var inst = this._engineInstance;
inst._log.debug('raising:' + e.data.toDo.event);
this._engineGen.call(inst, e.data.toDo.event, e.data.toDo.eventData);
// @chsc03: attention if something goes wrong along the transition,
// the worker is not ready for any incoming jobs
this.postMessage({
command : 'readyForJob'
});
inst.onraise();
}
};
return scionQueueWorker;
},
createRaise: function(_instance){
return function(event, eventData) {
if (eventData){
this._log.debug('new Job:' + event);
}
_instance.worker.postMessage({
command : 'newJob',
job : {
event : event,
eventData : eventData
}
});
};
}
};
/**
* Factory for CordovaPlugin-based implementation of raise function.
*
* Provide creator-functions:
* <code>createWorker(_engineInstance, genFunc) : WebWorker</code>
* <code>createRaise(_engineInstance) : Function</code>
*
*
* @requires cordova.plugins.queuePlugin (Cordova plugin for execution-queue)
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var _queuePluginFactory = {
/** @scope StateEngineQueuePluginImpl */
/** @memberOf StateEngineQueuePluginImpl */
name: 'queuepluginGen',
createWorker: (function initWorkerFactory() {
//"global" ID-list for all queues (i.e. ID-list for all engines)
var callBackList = [];
return function workerFactory(_instance, gen){
var id = callBackList.length;
var execQueue = window.cordova.plugins.queuePlugin;
function successCallBackHandler(args){
if (args.length===2){
// console.debug('QueuePlugin: success '+ JSON.stringify(args[0]) + ', '+JSON.stringify(args[1]));//DEBUG
callBackList[args[0]](args[1]);
}
}
function failureFallbackHandler(err){
_instance._log.error('failed to initialize SCION extension for ANDROID evn');
_instance.worker = (function(gen){
return {
raiseCordova: function fallback(event, eventData){
setTimeout(function(){
gen.call(_instance, event, eventData);
}, 10);
}
};
})();//END: fallback
}
callBackList.push(function(data){
var inst = _instance;
if(inst._log.isv()) inst._log.debug('raising:'+ data.event);
var generatedState = gen.call(inst, data.event, data.eventData);
if(inst._log.isv()) inst._log.debug('QueuePlugin: processed event '+id+' for '+ data.event+' -> new state: '+JSON.stringify(generatedState)+ ' at ' + inst.url);
execQueue.readyForJob(id, successCallBackHandler, failureFallbackHandler);
inst.onraise();
});
execQueue.newQueue(id, function(args){
if(_instance._log.isv()) _instance._log.debug('QueuePlugin: entry '+id+' created.' + ' at ' + _instance.url);
}, failureFallbackHandler
);
return {
_engineInstance: _instance,
raiseCordova: function (event, eventData){
if(this._engineInstance._log.isv()) this._engineInstance._log.debug('QueuePlugin: new Job: '+ id + ' at ' + this._engineInstance.url);
execQueue.newJob(id, {event: event, eventData: eventData}, successCallBackHandler,failureFallbackHandler);
}
};
};//END: workerFactory(_instance, gen)
})(),//END: initWorkerFactory()
createRaise: function(_instance){
return function(event, eventData) {
if (eventData) _instance._log.log('new Job:' + event);
_instance.worker.raiseCordova(event, eventData);
};
}
};
/**
* Factory for stub/dummy implementation of raise function.
*
* Provide creator-functions:
* <code>createWorker(_engineInstance, genFunc) : WebWorker</code>
* <code>createRaise(_engineInstance) : Function</code>
*
*
* @requires cordova.plugins.queuePlugin (Cordova plugin for execution-queue)
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var _stubFactory = {
/** @scope StateEngineStubImpl */
/** @memberOf StateEngineStubImpl */
name: 'stubGen',
createWorker: function(_instance, gen) {
return {
raiseStubImpl: function fallback(event, eventData){
setTimeout(function(){
gen(event, eventData);
}, 50);
}
};
},
createRaise: function(_instance){
return function(event, eventData) {
if (eventData) _instance._log.log('new Job:' + event);
_instance.worker.raiseStubImpl(event, eventData);
};
}
};
/**
* Get factory for raise-impl. depending on the runtime environment.
*
* Currently there are 3 impl. available
* * WebWorker
* * Android QueuePlugin
* * STUB IMPLEMENTATION
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
function getScionEnvFactory(){
var hasWebWorkers = typeof window.Worker !== 'undefined';
//TODO make this configurable? through ConfigurationManager?
if(hasWebWorkers){
return _browserFactory; //_browser;
}
else {
var isCordovaEnv = constants.isCordovaEnv();
//if queue-plugin is available:
if(isCordovaEnv && cordova.plugins && cordova.plugins.queuePlugin){
return _queuePluginFactory;//_cordova;
}
else {
//otherwise use fallback:
return _stubFactory;//_stub;
}
}
}
/**
* no-op function for dummy logger
* @memberOf mmir.env.statemachine.engine.exec#
*/
function noop(){};
/**
* always-false function for dummy logger
* @memberOf mmir.env.statemachine.engine.exec#
*/
function deny(){return false;};
// /**
// * print-warning function for dummy logger
// * @memberOf mmir.env.statemachine.engine.exec#
// */
// function pw(){console.warn.apply(console,arguments);};
/**
* print-error function for dummy logger
* @memberOf mmir.env.statemachine.engine.exec#
*/
function pe(){console.error.apply(console,arguments);};
/**
* dummy logger that does nothing:
* the engine-creator should replace this with a "real" implementation
* e.g. something like this (see also init() in dialogManager):
*
* engine = require('mmirf/engineConfig')('some-url', 'some-mode');
* engine._log = require('mmirf/logger').create('my-module-id');
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
var nolog = {
/** @scope mmir.env.statemachine.engine.exec.DummyLogger */
/** @memberOf DummyLogger */
d: noop,
debug: noop,
w: noop,//pw,
warn: noop,//pw,
e: pe,
error: pe,
log: noop,
isVerbose: deny,
isv: deny,
isDebug: deny,
isd: deny
};
/**
* Exported factory function for creating / adding impl. to SCION engine instance
*
* @memberOf mmir.env.statemachine.engine.exec#
*/
return function create(url, _mode) {
var baseFactory = _baseFactory;
var scionEnvConfig = getScionEnvFactory();
var _instance = {url: url,_log: nolog};
// var scionConfig = scionEnvConfig(_instance);
var scionConfig = baseFactory( _instance, scionEnvConfig);
scionConfig.doc = url;
_instance = createScionEngine(scionConfig, _instance);
return _instance;
};
});