UNPKG

mmir-lib

Version:

MMIR (Mobile Multimodal Interaction and Relay) library

290 lines (251 loc) 8.28 kB
define(['mmirf/resources', 'mmirf/logger', 'mmirf/asyncUtils', 'require', 'module'], /** * Module that provides a generator for parser-compiler WebWorker instances: * * initializes the WebWorker instance and provides functions for communicating with * the WebWorker. * * @class * @constant * @public * @name AsyncGenerator * @memberOf mmir.env.grammar * @hideconstructor * * @requires WebWorker * * @example * * //initializes a WebWorker at MMIR's worker path, i.e. at resources.getWorkerPath() + "file.name": * var asyncCompiler = asyncGen.createWorker("jisonCompiler.js"); * * //create job ID (must be unique for each job): * var taskId = '1'; * * //register callback for the job ID: * asyncCompiler.addCallback(taskId, function(evtData){ * * if(evtData.error){ * //... do something * } else { * var hasError = evtData.hasError; * var grammarParser = evtData.def; * * //... do something * } * * }); * * //start a compile job on the worker: * asyncCompiler.postMessage({ * cmd: 'parse', * id: taskId, * config: options, //compile options (specific to compiler engine) * text: grammarDefinition //parser definition (using the syntax of the specific compiler engine) * }); * */ function(resources, Logger, asyncUtils, require, module){ var COMPILER_WORKER_POSTFIX = 'Compiler.js'; var MODULE_ID_PREFIX = 'mmirf/'; /** * Factory for creating WebWorkers that compile grammar-parser * * @class AsyncCompiler * @memberOf mmir.env.grammar * @hideconstructor */ return { /** * Create a WebWorker for compiling grammars. * * The returned WebWorker provides 2 additional convenience functions * for communicating with the WebWorker thread: * * <code>addCallback(taskId : String, callback : Function)</code> * where <em>taskId</em> is a unique String identifying a single compile-job * and <em>callback</em> handles messages that come back from the WebWorker thread: * * The <code>callback(eventData)</code> will invoke the callback with the message's data * The property <code>eventData.done</code> has a special function: * if this property is present with a TRUTHY value, then callback will be removed, i.e. * not listening anymore for the specified taskId. * * After adding a <em>callback</em>, messages to the WebWorker thread can be send with * the <code>postMessage({id: taskId, cmd: 'parse', ...})</code> * * @param {String} parserEngineId * the name of the file that holds the WebWorker implementation. * The file must be located in MMIR's {@link Resources#getWorkerPath} * directory. * * @returns {mmir.env.grammar.CompileWebWorker} * a WebWorker that provides some convenience functions for * communicating with the WebWorker/thread. * * @memberOf mmir.env.grammar.AsyncCompiler# */ createWorker: function(parserEngineId){ /** @memberOf mmir.env.grammar.CompileWebWorker# */ var workerPath = resources.getWorkerPath() + parserEngineId + COMPILER_WORKER_POSTFIX; /** @memberOf mmir.env.grammar.CompileWebWorker# */ var mmirLibPath = resources.getMmirBasePath(); //webpack web worker wrapper: var WpWorker = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? require('../../workers/' + parserEngineId + COMPILER_WORKER_POSTFIX) : null; /** * web-worker for compile jobs * * @class CompileWebWorker * @memberOf mmir.env.grammar */ var asyncCompiler = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? new WpWorker() : new Worker(workerPath); var _modConf = module.config(module); /** @memberOf mmir.env.grammar.CompileWebWorker# */ asyncCompiler._logger = Logger.create(parserEngineId+'AsyncWorker', _modConf? _modConf.logLevel : void(0)); /** * dictionary for currently active compilations * * <pre> * [id] -> callback-function() * </pre> * * @memberOf mmir.env.grammar.CompileWebWorker# * @member _activeTaskIds */ asyncCompiler._activeTaskIds = {}; /** * listener for init-message * * DEFAULT: prints a message to the console * * Init Message: * * success: * <code>{init:true}</code> * * failed: * <code>{init:false,error:"message"}</code> * * @memberOf mmir.env.grammar.CompileWebWorker# * @function _oninit */ asyncCompiler._oninit = function(evtData){ if(evtData.init){ if(this._logger.isDebug()) this._logger.debug('initialized: '+JSON.stringify(evtData)); } else { this._logger.error('failed to initialize: '+JSON.stringify(evtData)); } }; /** * HELPER generate & setup oninit signal for sync + async modules. * * Side Effects: * generates and sets #_oninit * * @param {ParserGen} syncGen * the sync parser generator * @param {Deferred} asyncDef * the deferred that should be resolved when async generator is initialized * * @returns {AsyncInitMesssage} * the message object that should be sent to the async generator's WebWorker * * @memberOf mmir.env.grammar.CompileWebWorker# * @function prepareOnInit */ asyncCompiler.prepareOnInit = function(syncGen, asyncDef){ //setup async init-signaling: var isSyncGenInit = false; var isAsyncGenInit = false; //HELPER signal as "completely initialized" when sync & async modules are initialized: var checkInit = function(){ if(isSyncGenInit && isAsyncGenInit){ asyncDef.resolve(); } }; var initSyncGenDef = syncGen.init();//<- get init-signal for sync-generator var onComplete = function(){ isSyncGenInit = true; checkInit(); }; initSyncGenDef.then(onComplete, onComplete); this._oninit = function(initEvt){ if(initEvt.init === false){ this._logger.error('Failed to initialize '+JSON.stringify(initEvt)); } else { isAsyncGenInit = true; } checkInit(); }; var engineUrl = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? null : require.toUrl(MODULE_ID_PREFIX + syncGen.engineId) return {cmd:'init', engineUrl: engineUrl, libUrl: mmirLibPath}; }; /** * Error handler for errors signaled by the webworker * * @memberOf mmir.env.grammar.CompileWebWorker# * @function _onerror */ asyncCompiler._onerror = function(errorMsg, error){ this._logger.error(errorMsg, error); }; /** * HELPER: register a callback (usage: see e.g. jisonGen.compileGrammar()) * * @memberOf mmir.env.grammar.CompileWebWorker# * @function addCallback */ asyncCompiler.addCallback = function(id, cb){ this._activeTaskIds[id] = cb; }; /** * handler for messages/event from web-worker: * * @memberOf mmir.env.grammar.CompileWebWorker# * @function onmessage */ var onmessage = function(evt){ var id = evt.data.id; if(id){ var handler = this._activeTaskIds[id]; //this was the final message for this ID -> remove callback-function from "active tasks" dictionary: if(evt.data.done){ delete this._activeTaskIds[id]; } if(handler){ handler.call(this, evt.data); } else { this._logger.warn('no callback registered for ID "'+id+'" -> ' + JSON.stringify(evt.data)); } } else if(evt.data.error){ this._onerror('Error in '+workerPath+': '+evt.data.error, evt.data); } else if(typeof evt.data.init === 'boolean'){ asyncCompiler._oninit(evt.data); } else { this._onerror('ERROR unknown message -> ' + JSON.stringify(evt.data), evt.data); } if(this._onidle && !this.hasPendingCallback()){ this._onidle(); } }; asyncUtils.onMessage(asyncCompiler, onmessage); /** * check if the worker has "pending callbacks" * * @memberOf mmir.env.grammar.CompileWebWorker# * @function hasPendingCallback */ asyncCompiler.hasPendingCallback = function(){ for(var n in asyncCompiler._activeTaskIds){ if(asyncCompiler._activeTaskIds[n]){ return true; } } }; return asyncCompiler; } }; });