1 2 3 define(['constants'], 4 /** 5 * Module that provides a generator for parser-compiler WebWorker instances: 6 * 7 * initializes the WebWorker instance and provides functions for communicating with 8 * the WebWorker. 9 * 10 * @class 11 * @constant 12 * @public 13 * @name AsyncGenerator 14 * @memberOf mmir.env.grammar 15 * 16 * @requires WebWorker 17 * 18 * @example 19 * 20 * //initializes a WebWorker at MMIR's worker path, i.e. at constants.getWorkerPath() + "file.name": 21 * var asyncCompiler = asyncGen.createWorker("jisonCompiler.js"); 22 * 23 * //create job ID (must be unique for each job): 24 * var taskId = '1'; 25 * 26 * //register callback for the job ID: 27 * asyncCompiler.addCallback(taskId, function(evtData){ 28 * 29 * if(evtData.error){ 30 * //... do something 31 * } else { 32 * var hasError = evtData.hasError; 33 * var grammarParser = evtData.def; 34 * 35 * //... do something 36 * } 37 * 38 * }); 39 * 40 * //start a compile job on the worker: 41 * asyncCompiler.postMessage({ 42 * cmd: 'parse', 43 * id: taskId, 44 * config: options, //compile options (specific to compiler engine) 45 * text: grammarDefinition //parser definition (using the syntax of the specific compiler engine) 46 * }); 47 * 48 */ 49 function(constants){ 50 51 var COMPILER_WORKER_POSTFIX = 'Compiler.js'; 52 53 /** 54 * Factory for creating WebWorkers that compile grammar-parser 55 * 56 * @class AsyncCompiler 57 */ 58 return { 59 /** @scope AsyncCompiler.prototype */ 60 61 /** 62 * Create a WebWorker for compiling grammars. 63 * 64 * The returned WebWorker provides 2 additional convenience functions 65 * for communicating with the WebWorker thread: 66 * 67 * <code>addCallback(taskId : String, callback : Function)</code> 68 * where <em>taskId</em> is a unique String identifying a single compile-job 69 * and <em>callback</em> handles messages that come back from the WebWorker thread: 70 * 71 * The <code>callback(eventData)</code> will invoke the callback with the message's data 72 * The property <code>eventData.done</code> has a special function: 73 * if this property is present with a TRUTHY value, then callback will be removed, i.e. 74 * not listening anymore for the specified taskId. 75 * 76 * After adding a <em>callback</em>, messages to the WebWorker thread can be send with 77 * the <code>postMessage({id: taskId, cmd: 'parse', ...})</code> 78 * 79 * @param {String} parserEngineId 80 * the name of the file that holds the WebWorker implementation. 81 * The file must be located in MMIR's {@link Constants#getWorkerPath} 82 * directory. 83 * 84 * @returns {CompileWebWorker} 85 * a WebWorker that provides some convenience functions for 86 * communicating with the WebWorker/thread. 87 * 88 * @memberOf AsyncCompiler# 89 */ 90 createWorker: function(parserEngineId){ 91 92 93 /** @memberOf CompileWebWorker# */ 94 var workerPath = constants.getWorkerPath() + parserEngineId + COMPILER_WORKER_POSTFIX; 95 96 /** 97 * web-worker for compile jobs 98 * 99 * @class CompileWebWorker 100 * @memberOf AsyncCompiler.createWorker 101 */ 102 var asyncCompiler = new Worker(workerPath); 103 104 /** 105 * dictionary for currently active compilations 106 * 107 * <pre> 108 * [id] -> callback-function() 109 * </pre> 110 * 111 * @memberOf CompileWebWorker# 112 */ 113 asyncCompiler._activeTaskIds = {}; 114 115 /** 116 * listener for init-message 117 * 118 * DEFAULT: prints a message to the console 119 * 120 * Init Message: 121 * * success: 122 * <code>{init:true}</code> 123 * * failed: 124 * <code>{init:false,error:"message"}</code> 125 * 126 * @memberOf CompileWebWorker# 127 */ 128 asyncCompiler._oninit = function(evtData){ 129 if(evtData.init){ 130 console.log('initialized: '+JSON.stringify(evtData)); 131 } else { 132 console.error('failed to initialize: '+JSON.stringify(evtData)); 133 } 134 }; 135 136 /** 137 * HELPER generate & setup oninit signal for sync + async modules. 138 * 139 * Side Effects: 140 * generates and sets #_oninit 141 * 142 * @param {ParserGen} syncGen 143 * the sync parser generator 144 * @param {Deferred} asyncDef 145 * the deferred that should be resolved when async generator is initialized 146 * @param {requirejs} require 147 * the require instance that is used for loading the MMIR framework 148 * 149 * @returns {AsyncInitMesssage} 150 * the message object that should be sent to the async generator's WebWorker 151 * 152 * @memberOf CompileWebWorker# 153 */ 154 asyncCompiler.prepareOnInit = function(syncGen, asyncDef, require){ 155 156 //setup async init-signaling: 157 var isSyncGenInit = false; 158 var isAsyncGenInit = false; 159 160 //HELPER signal as "completely initialized" when sync & async modules are initialized: 161 var checkInit = function(){ 162 if(isSyncGenInit && isAsyncGenInit){ 163 asyncDef.resolve(); 164 } 165 }; 166 167 var initSyncGenDef = syncGen.init();//<- get init-signal for sync-generator 168 initSyncGenDef.always(function(){ 169 isSyncGenInit = true; 170 checkInit(); 171 }); 172 173 this._oninit = function(initEvt){ 174 if(initEvt.init === false){ 175 console.error('Failed to initialize '+JSON.stringify(initEvt)); 176 } else { 177 isAsyncGenInit = true; 178 } 179 checkInit(); 180 }; 181 return {cmd:'init', engineUrl: require.toUrl(syncGen.engineId)}; 182 183 }; 184 185 /** 186 * Error handler for errors signaled by the webworker 187 * 188 * @memberOf CompileWebWorker# 189 */ 190 asyncCompiler._onerror = function(errorMsg, error){ 191 console.error(errorMsg, error); 192 }; 193 194 /** 195 * HELPER: register a callback (usage: see jisonGen.compileGrammar() below) 196 * 197 * @memberOf CompileWebWorker# 198 */ 199 asyncCompiler.addCallback = function(id, cb){ 200 this._activeTaskIds[id] = cb; 201 }; 202 203 /** 204 * handler for messages/event from web-worker: 205 * 206 * @memberOf CompileWebWorker# 207 */ 208 asyncCompiler.onmessage = function(evt){ 209 210 var id = evt.data.id; 211 if(id){ 212 213 if(this._activeTaskIds[id]){ 214 215 this._activeTaskIds[id](evt.data); 216 217 } else { 218 console.warn('no callback registered for ID "'+id+'" -> ' + JSON.stringify(evt.data)); 219 } 220 221 //this was the final message for this ID -> remove callback-function from "active tasks" dictionary: 222 if(evt.data.done){ 223 this._activeTaskIds[id] = void(0); 224 } 225 } 226 else if(evt.data.error){ 227 228 this._onerror('Error in '+workerPath+': '+evt.data.error, evt.data); 229 } 230 else if(typeof evt.data.init === 'boolean'){ 231 232 asyncCompiler._oninit(evt.data); 233 } 234 else { 235 236 this._onerror('ERROR unknown message -> ' + JSON.stringify(evt.data), evt.data); 237 } 238 239 }; 240 241 return asyncCompiler; 242 } 243 }; 244 245 }); 246