1 /* 2 * Copyright (C) 2012-2013 DFKI GmbH 3 * Deutsches Forschungszentrum fuer Kuenstliche Intelligenz 4 * German Research Center for Artificial Intelligence 5 * http://www.dfki.de 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a 8 * copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be included 16 * in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 /** 28 * Factory for creating the state-machine's <em>raise()</em> function. 29 * 30 * Creates implementation for the state-engine's 31 * <pre> 32 * { 33 * name: STRING // engine name / description 34 * doc: STRING // the URL to the SCXML document that holds the engine's definition 35 * evalScript: BOOLEAN // true 36 * onraise: FUNCTION // default impl. for onraise: print debug output (depending on logging-level for DialogEngine / InputEngine) 37 * onload: FUNCTION // default impl. handling initialization of state-engine after SCXML definition was loaded 38 * raise: FUNCTION // default impl. for raise-function: use execution-queue for allowing raise-invocations from within SCXML scripts 39 * } 40 * </pre> 41 * 42 * @class mmir.env.statemachine.engine.exec 43 * 44 * @requires jQuery.extend 45 * @requires cordova.plugins.queuePlugin: needed for Android < 4.4 -> Cordova plugin for non-WebWorker-based execution-queue 46 */ 47 define(['constants', 'scionEngine', 'jquery'], function(constants, createScionEngine, $) { 48 49 /** 50 * HELPER logging for state-changes 51 * 52 * @param {Engine} ctx 53 * the context for the state-machine, i.e. the DialogEngine or InputEngine instance 54 * 55 * @memberOf mmir.env.statemachine.engine.exec# 56 */ 57 var printDebugStates = function(ctx){ 58 if(!ctx._log.isDebug()){ 59 return; 60 } 61 ctx._log.debug(ctx.name, 'current state: ' + JSON.stringify(ctx.getStates())); 62 ctx._log.debug(ctx.name, 'active states: ' + JSON.stringify(ctx.getActiveStates())); 63 ctx._log.debug(ctx.name, 'active events: ',+ JSON.stringify(ctx.getActiveEvents())); 64 ctx._log.debug(ctx.name, 'active transitions: '+ JSON.stringify(ctx.getStates()) + ":"+ JSON.stringify(ctx.getActiveTransitions())); 65 }; 66 67 /** 68 * Factory for default implementation for state-engine, returns 69 * <pre> 70 * { 71 * name: STRING // engine name / description 72 * doc: STRING // the URL to the SCXML document that holds the engine's definition 73 * evalScript: BOOLEAN // true 74 * onraise: FUNCTION // default impl. for onraise: print debug output (depending on logging-level for DialogEngine / InputEngine) 75 * onload: FUNCTION // default impl. handling initialization of state-engine after SCXML definition was loaded 76 * raise: FUNCTION // raise-function created by envFactory(_instance) 77 * } 78 * </pre> 79 * 80 * @param {Engine} _engine 81 * the instance of the SCION state-machine 82 * 83 * @param {ExecFactory} envFactory 84 * the factory for creating the Worker/Thread and the raise() function 85 * 86 * @returns the implementation with raise-function 87 * 88 * @memberOf mmir.env.statemachine.engine.exec# 89 */ 90 var _defaultFactory = function(_instance, envFactory){ /** @class StateEngineDefaultImpl */ return { 91 /** @scope StateEngineDefaultImpl */ 92 93 /** @memberOf StateEngineDefaultImpl */ 94 name : envFactory.name? envFactory.name : 'default_engine', 95 96 doc : null, 97 98 onraise : function() { 99 100 if (this._log.isd()) { 101 printDebugStates(_instance); 102 }; 103 104 }, 105 106 evalScript : true, 107 108 onload : function(scion, deferred) { 109 110 //FIX (russa) for jQuery > 2.x: extend() uses isPlainObject(), which's impl. changed in 2.x 111 // -> isPlainObject() now requires a constructor that is different from the native Object.constructor 112 // in order to be able to detect, that an object is NOT a plain object 113 // ... but scion does not provide such a non-native constructor for its _scion property 114 // (which causes deep-copy in extend() to run into an infinite loop) 115 // QUICK-FIX: just attach a dummy constructor function, so that isPlainObject will correctly detect property 116 // _scion as not-a-plain-object (this should really be FIXed in the scion library itself...) 117 // 118 //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. ...?) 119 scion['_scion'].constructor = function dummy(){}; 120 $.extend(true, _instance, scion); 121 122 _instance.worker = envFactory.createWorker(_instance, _instance.gen);//_instance._genFuncFactory(_instance, _instance.gen); 123 124 //FIXME @russa: is this a general functionality? should this be removed? 125 if (!_instance.evalScript){ 126 _instance.ignoreScript(); 127 } 128 129 // delete _instance.gen; 130 delete _instance.evalScript; 131 132 _instance.start(); 133 134 deferred.resolve(_instance); 135 136 },//END: onload 137 138 raise: envFactory.createRaise(_instance) 139 }; 140 }; 141 142 /** 143 * Factory for WebWorker-based implementation of raise function. 144 * 145 * Provide creator-functions: 146 * <code>createWorker(_engineInstance, genFunc) : WebWorker</code> 147 * <code>createRaise(_engineInstance) : Function</code> 148 * 149 * @memberOf mmir.env.statemachine.engine.exec# 150 */ 151 var _browserFactory = { 152 /** @scope StateEngineWebWorkerImpl */ 153 154 /** @memberOf StateEngineWebWorkerImpl */ 155 name: 'webworkerGen', 156 createWorker: function(_instance, gen) { 157 158 var scionQueueWorker = new Worker( 159 constants.getWorkerPath()+ 'ScionQueueWorker.js' 160 ); 161 scionQueueWorker._engineInstance = _instance; 162 scionQueueWorker._engineGen = gen; 163 164 scionQueueWorker.onmessage = function(e) { 165 166 if (e.data.command == "toDo") { 167 var inst = this._engineInstance; 168 inst._log.debug('raising:' + e.data.toDo.event); 169 170 this._engineGen.call(inst, e.data.toDo.event, e.data.toDo.eventData); 171 172 // @chsc03: attention if something goes wrong along the transition, 173 // the worker is not ready for any incoming jobs 174 this.postMessage({ 175 command : 'readyForJob' 176 }); 177 178 inst.onraise(); 179 } 180 }; 181 182 return scionQueueWorker; 183 184 }, 185 createRaise: function(_instance){ 186 return function(event, eventData) { 187 188 if (eventData){ 189 this._log.debug('new Job:' + event); 190 } 191 192 _instance.worker.postMessage({ 193 command : 'newJob', 194 job : { 195 event : event, 196 eventData : eventData 197 } 198 }); 199 }; 200 } 201 202 }; 203 204 /** 205 * Factory for Android-based implementation of raise function. 206 * 207 * Provide creator-functions: 208 * <code>createWorker(_engineInstance, genFunc) : WebWorker</code> 209 * <code>createRaise(_engineInstance) : Function</code> 210 * 211 * 212 * @requires cordova.plugins.queuePlugin (Cordova plugin for execution-queue) 213 * 214 * @memberOf mmir.env.statemachine.engine.exec# 215 */ 216 var _androidFactory = { 217 /** @scope StateEngineQueuePluginImpl */ 218 219 /** @memberOf StateEngineQueuePluginImpl */ 220 name: 'queuepluginGen', 221 createWorker: (function initWorkerFactory() { 222 223 //"global" ID-list for all queues (i.e. ID-list for all engines) 224 var callBackList = []; 225 226 return function workerFactory(_instance, gen){ 227 228 var id = callBackList.length; 229 var execQueue = window.cordova.plugins.queuePlugin; 230 231 function successCallBackHandler(args){ 232 if (args.length=2){ 233 // console.debug('QueuePlugin: success '+ JSON.stringify(args[0]) + ', '+JSON.stringify(args[1]));//DEBUG 234 callBackList[args[0]](args[1]); 235 } 236 } 237 238 function failureFallbackHandler(err){ 239 240 _instance._log.error('failed to initialize SCION extension for ANDROID evn'); 241 _instance.worker = (function(gen){ 242 return { 243 raiseCordova: function fallback(event, eventData){ 244 setTimeout(function(){ 245 gen.call(_instance, event, eventData); 246 }, 10); 247 } 248 }; 249 })();//END: fallback 250 } 251 252 callBackList.push(function(data){ 253 var inst = _instance; 254 if(inst._log.isv()) inst._log.debug('raising:'+ data.event); 255 var generatedState = gen.call(inst, data.event, data.eventData); 256 if(inst._log.isv()) inst._log.debug('QueuePlugin: processed event '+id+' for '+ data.event+' -> new state: '+JSON.stringify(generatedState)+ ' at ' + inst.url); 257 execQueue.readyForJob(id, successCallBackHandler, failureFallbackHandler); 258 259 inst.onraise(); 260 }); 261 execQueue.newQueue(id, function(args){ 262 if(_instance._log.isv()) _instance._log.debug('QueuePlugin: entry '+id+' created.' + ' at ' + _instance.url); 263 }, failureFallbackHandler 264 ); 265 266 return { 267 _engineInstance: _instance, 268 raiseCordova: function (event, eventData){ 269 if(this._engineInstance._log.isv()) this._engineInstance._log.debug('QueuePlugin: new Job: '+ id + ' at ' + this._engineInstance.url); 270 execQueue.newJob(id, {event: event, eventData: eventData}, successCallBackHandler,failureFallbackHandler); 271 } 272 }; 273 274 };//END: workerFactory(_instance, gen) 275 276 })(),//END: initWorkerFactory() 277 278 createRaise: function(_instance){ 279 return function(event, eventData) { 280 281 if (eventData) _instance._log.log('new Job:' + event); 282 283 _instance.worker.raiseCordova(event, eventData); 284 }; 285 } 286 287 }; 288 289 /** 290 * Factory for stub/dummy implementation of raise function. 291 * 292 * Provide creator-functions: 293 * <code>createWorker(_engineInstance, genFunc) : WebWorker</code> 294 * <code>createRaise(_engineInstance) : Function</code> 295 * 296 * 297 * @requires cordova.plugins.queuePlugin (Cordova plugin for execution-queue) 298 * 299 * @memberOf mmir.env.statemachine.engine.exec# 300 */ 301 var _stubFactory = { 302 /** @scope StateEngineStubImpl */ 303 304 /** @memberOf StateEngineStubImpl */ 305 name: 'stubGen', 306 createWorker: function(_instance, gen) { 307 308 return { 309 raiseStubImpl: function fallback(event, eventData){ 310 setTimeout(function(){ 311 gen(event, eventData); 312 }, 50); 313 } 314 }; 315 316 }, 317 createRaise: function(_instance){ 318 return function(event, eventData) { 319 320 if (eventData) _instance._log.log('new Job:' + event); 321 322 _instance.worker.raiseStubImpl(event, eventData); 323 }; 324 } 325 }; 326 327 /** 328 * Get factory for raise-impl. depending on the runtime environment. 329 * 330 * Currently there are 3 impl. available 331 * * WebWorker 332 * * Android QueuePlugin 333 * * STUB IMPLEMENTATION 334 * 335 * @memberOf mmir.env.statemachine.engine.exec# 336 */ 337 function getScionEnvFactory(){ 338 339 var hasWebWorkers = typeof window.Worker !== 'undefined'; 340 341 //TODO make this configurable? through ConfigurationManager? 342 if(hasWebWorkers && constants.isBrowserEnv()){ 343 return _browserFactory; //_browser; 344 } 345 else { 346 var isCordovaEnv = !constants.isBrowserEnv(); 347 if(isCordovaEnv){ 348 return _androidFactory;//_cordova; 349 } 350 else { 351 return _stubFactory;//_stub; 352 } 353 } 354 355 } 356 357 /** 358 * no-op function for dummy logger 359 * @memberOf mmir.env.statemachine.engine.exec# 360 */ 361 function noop(){}; 362 /** 363 * always-false function for dummy logger 364 * @memberOf mmir.env.statemachine.engine.exec# 365 */ 366 function deny(){return false;}; 367 // /** 368 // * print-warning function for dummy logger 369 // * @memberOf mmir.env.statemachine.engine.exec# 370 // */ 371 // function pw(){console.warn.apply(console,arguments);}; 372 /** 373 * print-error function for dummy logger 374 * @memberOf mmir.env.statemachine.engine.exec# 375 */ 376 function pe(){console.error.apply(console,arguments);}; 377 /** 378 * dummy logger that does nothing: 379 * the engine-creator should replace this with a "real" implementation 380 * e.g. something like this (see also init() in dialogManager): 381 * 382 * engine = require('engineConfig')('some-url', 'some-mode'); 383 * engine._log = require('logger').create('my-module-id'); 384 * 385 * @memberOf mmir.env.statemachine.engine.exec# 386 */ 387 var nolog = { 388 /** @scope mmir.env.statemachine.engine.exec.DummyLogger */ 389 /** @memberOf DummyLogger */ 390 d: noop, 391 debug: noop, 392 w: noop,//pw, 393 warn: noop,//pw, 394 e: pe, 395 error: pe, 396 log: noop, 397 isVerbose: deny, 398 isv: deny, 399 isDebug: deny, 400 isd: deny 401 }; 402 403 /** 404 * Exported factory function for creating / adding impl. to SCION engine instance 405 * 406 * @memberOf mmir.env.statemachine.engine.exec# 407 */ 408 return function create(url, _mode) { 409 410 var baseFactory = _defaultFactory; 411 var scionEnvConfig = getScionEnvFactory(); 412 413 var _instance = {url: url,_log: nolog}; 414 // var scionConfig = scionEnvConfig(_instance); 415 var scionConfig = baseFactory( _instance, scionEnvConfig); 416 417 scionConfig.doc = url; 418 _instance = createScionEngine(scionConfig, _instance); 419 420 return _instance; 421 }; 422 423 }); 424