TaskProcessor.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import when from '../ThirdParty/when.js';
  2. import buildModuleUrl from './buildModuleUrl.js';
  3. import defaultValue from './defaultValue.js';
  4. import defined from './defined.js';
  5. import destroyObject from './destroyObject.js';
  6. import DeveloperError from './DeveloperError.js';
  7. import Event from './Event.js';
  8. import FeatureDetection from './FeatureDetection.js';
  9. import isCrossOriginUrl from './isCrossOriginUrl.js';
  10. import Resource from './Resource.js';
  11. import RuntimeError from './RuntimeError.js';
  12. function canTransferArrayBuffer() {
  13. if (!defined(TaskProcessor._canTransferArrayBuffer)) {
  14. var worker = new Worker(getWorkerUrl('Workers/transferTypedArrayTest.js'));
  15. worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);
  16. var value = 99;
  17. var array = new Int8Array([value]);
  18. try {
  19. // postMessage might fail with a DataCloneError
  20. // if transferring array buffers is not supported.
  21. worker.postMessage({
  22. array : array
  23. }, [array.buffer]);
  24. } catch (e) {
  25. TaskProcessor._canTransferArrayBuffer = false;
  26. return TaskProcessor._canTransferArrayBuffer;
  27. }
  28. var deferred = when.defer();
  29. worker.onmessage = function(event) {
  30. var array = event.data.array;
  31. // some versions of Firefox silently fail to transfer typed arrays.
  32. // https://bugzilla.mozilla.org/show_bug.cgi?id=841904
  33. // Check to make sure the value round-trips successfully.
  34. var result = defined(array) && array[0] === value;
  35. deferred.resolve(result);
  36. worker.terminate();
  37. TaskProcessor._canTransferArrayBuffer = result;
  38. };
  39. TaskProcessor._canTransferArrayBuffer = deferred.promise;
  40. }
  41. return TaskProcessor._canTransferArrayBuffer;
  42. }
  43. var taskCompletedEvent = new Event();
  44. function completeTask(processor, data) {
  45. --processor._activeTasks;
  46. var id = data.id;
  47. if (!defined(id)) {
  48. // This is not one of ours.
  49. return;
  50. }
  51. var deferreds = processor._deferreds;
  52. var deferred = deferreds[id];
  53. if (defined(data.error)) {
  54. var error = data.error;
  55. if (error.name === 'RuntimeError') {
  56. error = new RuntimeError(data.error.message);
  57. error.stack = data.error.stack;
  58. } else if (error.name === 'DeveloperError') {
  59. error = new DeveloperError(data.error.message);
  60. error.stack = data.error.stack;
  61. }
  62. taskCompletedEvent.raiseEvent(error);
  63. deferred.reject(error);
  64. } else {
  65. taskCompletedEvent.raiseEvent();
  66. deferred.resolve(data.result);
  67. }
  68. delete deferreds[id];
  69. }
  70. function getWorkerUrl(moduleID) {
  71. var url = buildModuleUrl(moduleID);
  72. if (isCrossOriginUrl(url)) {
  73. //to load cross-origin, create a shim worker from a blob URL
  74. var script = 'importScripts("' + url + '");';
  75. var blob;
  76. try {
  77. blob = new Blob([script], {
  78. type : 'application/javascript'
  79. });
  80. } catch (e) {
  81. var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
  82. var blobBuilder = new BlobBuilder();
  83. blobBuilder.append(script);
  84. blob = blobBuilder.getBlob('application/javascript');
  85. }
  86. var URL = window.URL || window.webkitURL;
  87. url = URL.createObjectURL(blob);
  88. }
  89. return url;
  90. }
  91. var bootstrapperUrlResult;
  92. function getBootstrapperUrl() {
  93. if (!defined(bootstrapperUrlResult)) {
  94. bootstrapperUrlResult = getWorkerUrl('Workers/cesiumWorkerBootstrapper.js');
  95. }
  96. return bootstrapperUrlResult;
  97. }
  98. function createWorker(processor) {
  99. var worker = new Worker(getBootstrapperUrl());
  100. worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage);
  101. var bootstrapMessage = {
  102. loaderConfig: {
  103. paths: {
  104. 'Workers': buildModuleUrl('Workers')
  105. },
  106. baseUrl: buildModuleUrl.getCesiumBaseUrl().url
  107. },
  108. workerModule: TaskProcessor._workerModulePrefix + processor._workerName
  109. };
  110. worker.postMessage(bootstrapMessage);
  111. worker.onmessage = function(event) {
  112. completeTask(processor, event.data);
  113. };
  114. return worker;
  115. }
  116. function getWebAssemblyLoaderConfig(processor, wasmOptions) {
  117. var config = {
  118. modulePath : undefined,
  119. wasmBinaryFile : undefined,
  120. wasmBinary : undefined
  121. };
  122. // Web assembly not supported, use fallback js module if provided
  123. if (!FeatureDetection.supportsWebAssembly()) {
  124. if (!defined(wasmOptions.fallbackModulePath)) {
  125. throw new RuntimeError('This browser does not support Web Assembly, and no backup module was provided for ' + processor._workerName);
  126. }
  127. config.modulePath = buildModuleUrl(wasmOptions.fallbackModulePath);
  128. return when.resolve(config);
  129. }
  130. config.modulePath = buildModuleUrl(wasmOptions.modulePath);
  131. config.wasmBinaryFile = buildModuleUrl(wasmOptions.wasmBinaryFile);
  132. return Resource.fetchArrayBuffer({
  133. url: config.wasmBinaryFile
  134. }).then(function (arrayBuffer) {
  135. config.wasmBinary = arrayBuffer;
  136. return config;
  137. });
  138. }
  139. /**
  140. * A wrapper around a web worker that allows scheduling tasks for a given worker,
  141. * returning results asynchronously via a promise.
  142. *
  143. * The Worker is not constructed until a task is scheduled.
  144. *
  145. * @alias TaskProcessor
  146. * @constructor
  147. *
  148. * @param {String} workerName The name of the worker. This is expected to be a script
  149. * in the Workers folder.
  150. * @param {Number} [maximumActiveTasks=5] The maximum number of active tasks. Once exceeded,
  151. * scheduleTask will not queue any more tasks, allowing
  152. * work to be rescheduled in future frames.
  153. */
  154. function TaskProcessor(workerName, maximumActiveTasks) {
  155. this._workerName = workerName;
  156. this._maximumActiveTasks = defaultValue(maximumActiveTasks, 5);
  157. this._activeTasks = 0;
  158. this._deferreds = {};
  159. this._nextID = 0;
  160. }
  161. var emptyTransferableObjectArray = [];
  162. /**
  163. * Schedule a task to be processed by the web worker asynchronously. If there are currently more
  164. * tasks active than the maximum set by the constructor, will immediately return undefined.
  165. * Otherwise, returns a promise that will resolve to the result posted back by the worker when
  166. * finished.
  167. *
  168. * @param {Object} parameters Any input data that will be posted to the worker.
  169. * @param {Object[]} [transferableObjects] An array of objects contained in parameters that should be
  170. * transferred to the worker instead of copied.
  171. * @returns {Promise.<Object>|undefined} Either a promise that will resolve to the result when available, or undefined
  172. * if there are too many active tasks,
  173. *
  174. * @example
  175. * var taskProcessor = new Cesium.TaskProcessor('myWorkerName');
  176. * var promise = taskProcessor.scheduleTask({
  177. * someParameter : true,
  178. * another : 'hello'
  179. * });
  180. * if (!Cesium.defined(promise)) {
  181. * // too many active tasks - try again later
  182. * } else {
  183. * Cesium.when(promise, function(result) {
  184. * // use the result of the task
  185. * });
  186. * }
  187. */
  188. TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) {
  189. if (!defined(this._worker)) {
  190. this._worker = createWorker(this);
  191. }
  192. if (this._activeTasks >= this._maximumActiveTasks) {
  193. return undefined;
  194. }
  195. ++this._activeTasks;
  196. var processor = this;
  197. return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) {
  198. if (!defined(transferableObjects)) {
  199. transferableObjects = emptyTransferableObjectArray;
  200. } else if (!canTransferArrayBuffer) {
  201. transferableObjects.length = 0;
  202. }
  203. var id = processor._nextID++;
  204. var deferred = when.defer();
  205. processor._deferreds[id] = deferred;
  206. processor._worker.postMessage({
  207. id : id,
  208. parameters : parameters,
  209. canTransferArrayBuffer : canTransferArrayBuffer
  210. }, transferableObjects);
  211. return deferred.promise;
  212. });
  213. };
  214. /**
  215. * Posts a message to a web worker with configuration to initialize loading
  216. * and compiling a web assembly module asychronously, as well as an optional
  217. * fallback JavaScript module to use if Web Assembly is not supported.
  218. *
  219. * @param {Object} [webAssemblyOptions] An object with the following properties:
  220. * @param {String} [webAssemblyOptions.modulePath] The path of the web assembly JavaScript wrapper module.
  221. * @param {String} [webAssemblyOptions.wasmBinaryFile] The path of the web assembly binary file.
  222. * @param {String} [webAssemblyOptions.fallbackModulePath] The path of the fallback JavaScript module to use if web assembly is not supported.
  223. * @returns {Promise.<Object>} A promise that resolves to the result when the web worker has loaded and compiled the web assembly module and is ready to process tasks.
  224. */
  225. TaskProcessor.prototype.initWebAssemblyModule = function (webAssemblyOptions) {
  226. if (!defined(this._worker)) {
  227. this._worker = createWorker(this);
  228. }
  229. var deferred = when.defer();
  230. var processor = this;
  231. var worker = this._worker;
  232. getWebAssemblyLoaderConfig(this, webAssemblyOptions).then(function(wasmConfig) {
  233. return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) {
  234. var transferableObjects;
  235. var binary = wasmConfig.wasmBinary;
  236. if (defined(binary) && canTransferArrayBuffer) {
  237. transferableObjects = [binary];
  238. }
  239. worker.onmessage = function(event) {
  240. worker.onmessage = function(event) {
  241. completeTask(processor, event.data);
  242. };
  243. deferred.resolve(event.data);
  244. };
  245. worker.postMessage({ webAssemblyConfig : wasmConfig }, transferableObjects);
  246. });
  247. });
  248. return deferred;
  249. };
  250. /**
  251. * Returns true if this object was destroyed; otherwise, false.
  252. * <br /><br />
  253. * If this object was destroyed, it should not be used; calling any function other than
  254. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  255. *
  256. * @returns {Boolean} True if this object was destroyed; otherwise, false.
  257. *
  258. * @see TaskProcessor#destroy
  259. */
  260. TaskProcessor.prototype.isDestroyed = function() {
  261. return false;
  262. };
  263. /**
  264. * Destroys this object. This will immediately terminate the Worker.
  265. * <br /><br />
  266. * Once an object is destroyed, it should not be used; calling any function other than
  267. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  268. */
  269. TaskProcessor.prototype.destroy = function() {
  270. if (defined(this._worker)) {
  271. this._worker.terminate();
  272. }
  273. return destroyObject(this);
  274. };
  275. /**
  276. * An event that's raised when a task is completed successfully. Event handlers are passed
  277. * the error object is a task fails.
  278. *
  279. * @type {Event}
  280. *
  281. * @private
  282. */
  283. TaskProcessor.taskCompletedEvent = taskCompletedEvent;
  284. // exposed for testing purposes
  285. TaskProcessor._defaultWorkerModulePrefix = 'Workers/';
  286. TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix;
  287. TaskProcessor._canTransferArrayBuffer = undefined;
  288. export default TaskProcessor;