Source: env/grammar/asyncGenerator.js

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