/**
* loads the default grammar asynchronously using a WebWorker
*
* NOTE usage of the grammar is also async:
* calls on <code>interpret(text, callback)</code> are re-directed to the WebWorker
*
* @class
* @name AsyncGrammar
* @memberOf mmir.grammar
* @hideconstructor
*
*/
define(['mmirf/resources', 'mmirf/commonUtils', 'mmirf/semanticInterpreter', 'mmirf/logger', 'mmirf/asyncUtils', 'mmirf/util/deferred', 'require', 'module'], function(res, utils, semanticInterpreter, Logger, asyncUtils, deferred, require, module){
/** map for looking-up pending commands */
var _logger = Logger.create(module);
/** map for looking-up pending commands */
var _pendingCmds = new Map();
/** counter pending commands (~ command ID generator) */
var _cmdIdCounter = 1;
/** if "destroy" for WebWorker was request: if true, should terminate WebWeborker when there are no pending commands */
var _destroyRequested = false;
/** map for loaded async grammars */
var _loadedGrammars;
//webpack web worker wrapper:
var WpWorker = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD? require('../workers/asyncGrammarWorker.js') : null;
/** HELPER get resource ID for grammar ID */
var _toResId = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD?
function(langCode){ return 'mmirf/grammar/'+langCode; } :
function(langCode){ return langCode; };
/** web-worker instance */
var _asyncGrammarLoader;
/** web-worker instance */
var _initAsyncGrammarLoader = function(){
_loadedGrammars = {};
_asyncGrammarLoader = typeof WEBPACK_BUILD !== 'undefined' && WEBPACK_BUILD?
new WpWorker() :
new Worker(res.getWorkerPath()+'asyncGrammarWorker.js');
/** process messages from worker thread */
var onmessage = function(msg){
var data = msg.data;
var cmd = data.cmd;
if(cmd){
var id = data.id
if(cmd === 'stopwords'){
semanticInterpreter.setStopwords(id, data.stopwords);
//check/trigger init-listener
if(typeof _loadedGrammars[id] === 'object'){
_loadedGrammars[id].isStopwords = true;
_loadedGrammars[id].checkInit();
}
} else if(cmd === 'setgrammar'){
//replace the default impl. of the grammar execution:
// re-direct invocations to the worker thread and
// return the results via the callback
var execGrammar = function(text, parseOptions, callback){
if(!_asyncGrammarLoader){
var errMsg = 'WebWorker for async grammar execution is (already) destroyed!';
_logger.error(errMsg);
callback({error: errMsg});
return;
}
var cmdid = ''+ _cmdIdCounter++;
this.executeGrammar._pendingCmds.set(cmdid, callback);
var langid = this.executeGrammar._langCode;
_asyncGrammarLoader.postMessage({cmd: 'parse', id: langid, cmdId: cmdid, text: text, options: parseOptions});
};
execGrammar._pendingCmds = _pendingCmds;
execGrammar._langCode = id;
var options = data.options;
if(options.execMode != 'async'){
options.execMode = 'async';
}
semanticInterpreter.addGrammar(id, execGrammar, options);
//check/trigger init-listener
if(typeof _loadedGrammars[id] === 'object'){
_loadedGrammars[id].isGrammar = true;
_loadedGrammars[id].checkInit();
}
} else if(cmd === 'parseresult'){
var cmdid = data.cmdId;
if(!_pendingCmds.has(cmdid)){
_logger.error('no callback registered for cmd-ID '+cmdid+', ignoring result '+JSON.stringify(cmd.result));
} else {
_pendingCmds.get(cmdid).call(semanticInterpreter, data.result);
_pendingCmds.delete(cmdid);
}
} else if(cmd === 'error'){
_logger.error('encountered error: '+JSON.stringify(data));
} else {
_logger.error('unknown response from loader: '+JSON.stringify(msg.data));
}
} else {
_logger.error('unknown response from loader: '+JSON.stringify(msg.data));
}
if(_destroyRequested && _pendingCmds.size === 0){
_destroy(true)
}
};
asyncUtils.onMessage(_asyncGrammarLoader, onmessage);
}
/**
* destroy / terminate the WebWorker for async grammar execution
*
* NOTE: can be re-initialized using {@link mmir.grammar.AsyncGrammar#init}
*
* @private
* @function
* @param {Boolean} [force] OPTIONAL if true, will force termination of WebWorker,
* otherwise waits for pending commands to finish.
*
* @memberOf mmir.grammar.AsyncGrammar
*/
var _destroy = function(force){
if(_asyncGrammarLoader){
//if not forced, check if there are pending commands
if(!force && _pendingCmds.size > 0){
_destroyRequested = true;
return;////////////// EARLY EXIT /////////////////
}
_asyncGrammarLoader.terminate();
_asyncGrammarLoader = null;
_destroyRequested = false;
}
}
/** path to mmir-lib base directory */
var _mmirLibPath = res.getMmirBasePath();
return {
/**
* Initialize a grammar to be loaded & executed asynchronously
*
* @public
* @function
* @param {String} langCode the grammar's ID
* @param {Function} listener the callback that is triggered when the grammar has been initialized:
* <pre>listener(initializationResult)</pre>
* @param {String} [phrase] a phrase that should be immediately interpreted, after grammar has been loaded
* (for large grammars, this may reduce delays for subsequent calls, by fully initializing the grammar)
* @param {String} [grammarCode] for injecting JavaScript grammar code into the WebWorker: instead of loading the compiled grammar
* (for large grammars, this may reduce delays for subsequent calls, by fully initializing the grammar)
* for <code>langCode</code>, will <code>eval()</code> the <code>grammarCode</code> within the WebWorker
* The <code>grammarCode</code> corrsponds to field {@link mmir.grammar.GrammarConverter.js_grammar_definition},
* after grammar was compiled.<br/>
* NOTE this argument is positional! If no <code>phrase</code> should be use, it needs to be specified as
* <code>undefined</code>, e.g. <pre>asyncGrammar.init('myGrammar', onInitCallback, void(0), compiledGrammarCode)</pre>
*
* @returns {Boolean} <code>true</code> if initialization has started,
* if <code>false</code> initialization could not be started, due
* to errors or missing/invalid arguments
*
* @requires WebWorker
*
* @memberOf mmir.grammar.AsyncGrammar#
*/
init: function(langCode, listener, phrase, grammarCode){
if(!_asyncGrammarLoader){
_initAsyncGrammarLoader();
}
//use default language, if none is specified
if(!langCode){
langCode = semanticInterpreter.getCurrentGrammar();
}
if(!langCode){
_logger.error('Inavlid grammar ID: "'+langCode+'"');
return false;//////////////////// EARLY EXIT////////////////////////
}
//if grammar is already loaded & available:
if(_loadedGrammars[langCode] === true){
semanticInterpreter.interpret(phrase, langCode, listener);
return true;//////////////////// EARLY EXIT ////////////////////////
}
//if grammar is loading, but not available yet: register listener as complete-callback
if(_loadedGrammars[langCode] && _loadedGrammars[langCode].initDef && _loadedGrammars[langCode].initDef.then){
_loadedGrammars[langCode].initDef.then(listener, listener);
return true;//////////////////// EARLY EXIT ////////////////////////
}
utils.init().then(function onSuccess(){
var compiledGrammarPath;
if(!grammarCode){
compiledGrammarPath = utils.getCompiledGrammarPath(res.getGeneratedGrammarsPath(), _toResId(langCode));
if(!compiledGrammarPath){
_logger.error('No compiled grammar available for ID: "'+langCode+'"');
return;//////////////////// EARLY EXIT////////////////////////
}
}
var grammarInit = {
id: langCode,
initDef: deferred(),
isGrammar: false,
isStopwords: false,
checkInit: function(){
if(this.isStopwords && this.isGrammar){
_loadedGrammars[this.id] = true;
this.initDef.resolve();
this.initDef = null;
}
}
};
//register invocation of init-phrase as soon as (async-loaded) grammar becomes available
var onComplete = function(){
if(typeof phrase !== 'undefined'){
semanticInterpreter.interpret(phrase, langCode, listener);
} else {
listener({});
}
};
grammarInit.initDef.then(onComplete, onComplete);
_loadedGrammars[langCode] = grammarInit;
if(grammarCode){
_asyncGrammarLoader.postMessage({cmd: 'eval', libUrl: _mmirLibPath, id: langCode, code: grammarCode});
} else {
_asyncGrammarLoader.postMessage({cmd: 'load', libUrl: _mmirLibPath, id: langCode, url: compiledGrammarPath});
}
},
function onError(){
//FIXME impl. real error handling
_logger.error('cannot determine URL for compiled grammar with ID "'+langCode+'": commonUtils is not initialized.');
});
return true;
},
/**
* check, if grammar has been (or currently is in the process of beeing)
* intialized for async execution
*
* @public
* @function
* @param {String} grammarId the grammar ID to check
* @returns {Boolean} true, if grammar has been initialized for async-execution
*
* @memberOf mmir.grammar.AsyncGrammar#
* @see #init
*/
isInit: function(grammarId){
return !!_loadedGrammars[grammarId];
},
/**
* @public
* @function
* @copydoc mmir.grammar.AsyncGrammar._destroy
* @memberOf mmir.grammar.AsyncGrammar#
*/
destroy: _destroy,
/**
* check, if async-grammar worker has been destroyed
*
* @public
* @function
* @returns {Boolean} true, if async-grammar worker has been destroyed, otherwise false
*
* @memberOf mmir.grammar.AsyncGrammar#
* @see #destroy
* @see #init
*/
isDestroyed: function(){
return !_asyncGrammarLoader;
}
};
});