import when from '../ThirdParty/when.js'; import buildModuleUrl from './buildModuleUrl.js'; import defaultValue from './defaultValue.js'; import defined from './defined.js'; import destroyObject from './destroyObject.js'; import DeveloperError from './DeveloperError.js'; import Event from './Event.js'; import FeatureDetection from './FeatureDetection.js'; import isCrossOriginUrl from './isCrossOriginUrl.js'; import Resource from './Resource.js'; import RuntimeError from './RuntimeError.js'; function canTransferArrayBuffer() { if (!defined(TaskProcessor._canTransferArrayBuffer)) { var worker = new Worker(getWorkerUrl('Workers/transferTypedArrayTest.js')); worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage); var value = 99; var array = new Int8Array([value]); try { // postMessage might fail with a DataCloneError // if transferring array buffers is not supported. worker.postMessage({ array : array }, [array.buffer]); } catch (e) { TaskProcessor._canTransferArrayBuffer = false; return TaskProcessor._canTransferArrayBuffer; } var deferred = when.defer(); worker.onmessage = function(event) { var array = event.data.array; // some versions of Firefox silently fail to transfer typed arrays. // https://bugzilla.mozilla.org/show_bug.cgi?id=841904 // Check to make sure the value round-trips successfully. var result = defined(array) && array[0] === value; deferred.resolve(result); worker.terminate(); TaskProcessor._canTransferArrayBuffer = result; }; TaskProcessor._canTransferArrayBuffer = deferred.promise; } return TaskProcessor._canTransferArrayBuffer; } var taskCompletedEvent = new Event(); function completeTask(processor, data) { --processor._activeTasks; var id = data.id; if (!defined(id)) { // This is not one of ours. return; } var deferreds = processor._deferreds; var deferred = deferreds[id]; if (defined(data.error)) { var error = data.error; if (error.name === 'RuntimeError') { error = new RuntimeError(data.error.message); error.stack = data.error.stack; } else if (error.name === 'DeveloperError') { error = new DeveloperError(data.error.message); error.stack = data.error.stack; } taskCompletedEvent.raiseEvent(error); deferred.reject(error); } else { taskCompletedEvent.raiseEvent(); deferred.resolve(data.result); } delete deferreds[id]; } function getWorkerUrl(moduleID) { var url = buildModuleUrl(moduleID); if (isCrossOriginUrl(url)) { //to load cross-origin, create a shim worker from a blob URL var script = 'importScripts("' + url + '");'; var blob; try { blob = new Blob([script], { type : 'application/javascript' }); } catch (e) { var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; var blobBuilder = new BlobBuilder(); blobBuilder.append(script); blob = blobBuilder.getBlob('application/javascript'); } var URL = window.URL || window.webkitURL; url = URL.createObjectURL(blob); } return url; } var bootstrapperUrlResult; function getBootstrapperUrl() { if (!defined(bootstrapperUrlResult)) { bootstrapperUrlResult = getWorkerUrl('Workers/cesiumWorkerBootstrapper.js'); } return bootstrapperUrlResult; } function createWorker(processor) { var worker = new Worker(getBootstrapperUrl()); worker.postMessage = defaultValue(worker.webkitPostMessage, worker.postMessage); var bootstrapMessage = { loaderConfig: { paths: { 'Workers': buildModuleUrl('Workers') }, baseUrl: buildModuleUrl.getCesiumBaseUrl().url }, workerModule: TaskProcessor._workerModulePrefix + processor._workerName }; worker.postMessage(bootstrapMessage); worker.onmessage = function(event) { completeTask(processor, event.data); }; return worker; } function getWebAssemblyLoaderConfig(processor, wasmOptions) { var config = { modulePath : undefined, wasmBinaryFile : undefined, wasmBinary : undefined }; // Web assembly not supported, use fallback js module if provided if (!FeatureDetection.supportsWebAssembly()) { if (!defined(wasmOptions.fallbackModulePath)) { throw new RuntimeError('This browser does not support Web Assembly, and no backup module was provided for ' + processor._workerName); } config.modulePath = buildModuleUrl(wasmOptions.fallbackModulePath); return when.resolve(config); } config.modulePath = buildModuleUrl(wasmOptions.modulePath); config.wasmBinaryFile = buildModuleUrl(wasmOptions.wasmBinaryFile); return Resource.fetchArrayBuffer({ url: config.wasmBinaryFile }).then(function (arrayBuffer) { config.wasmBinary = arrayBuffer; return config; }); } /** * A wrapper around a web worker that allows scheduling tasks for a given worker, * returning results asynchronously via a promise. * * The Worker is not constructed until a task is scheduled. * * @alias TaskProcessor * @constructor * * @param {String} workerName The name of the worker. This is expected to be a script * in the Workers folder. * @param {Number} [maximumActiveTasks=5] The maximum number of active tasks. Once exceeded, * scheduleTask will not queue any more tasks, allowing * work to be rescheduled in future frames. */ function TaskProcessor(workerName, maximumActiveTasks) { this._workerName = workerName; this._maximumActiveTasks = defaultValue(maximumActiveTasks, 5); this._activeTasks = 0; this._deferreds = {}; this._nextID = 0; } var emptyTransferableObjectArray = []; /** * Schedule a task to be processed by the web worker asynchronously. If there are currently more * tasks active than the maximum set by the constructor, will immediately return undefined. * Otherwise, returns a promise that will resolve to the result posted back by the worker when * finished. * * @param {Object} parameters Any input data that will be posted to the worker. * @param {Object[]} [transferableObjects] An array of objects contained in parameters that should be * transferred to the worker instead of copied. * @returns {Promise.|undefined} Either a promise that will resolve to the result when available, or undefined * if there are too many active tasks, * * @example * var taskProcessor = new Cesium.TaskProcessor('myWorkerName'); * var promise = taskProcessor.scheduleTask({ * someParameter : true, * another : 'hello' * }); * if (!Cesium.defined(promise)) { * // too many active tasks - try again later * } else { * Cesium.when(promise, function(result) { * // use the result of the task * }); * } */ TaskProcessor.prototype.scheduleTask = function(parameters, transferableObjects) { if (!defined(this._worker)) { this._worker = createWorker(this); } if (this._activeTasks >= this._maximumActiveTasks) { return undefined; } ++this._activeTasks; var processor = this; return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) { if (!defined(transferableObjects)) { transferableObjects = emptyTransferableObjectArray; } else if (!canTransferArrayBuffer) { transferableObjects.length = 0; } var id = processor._nextID++; var deferred = when.defer(); processor._deferreds[id] = deferred; processor._worker.postMessage({ id : id, parameters : parameters, canTransferArrayBuffer : canTransferArrayBuffer }, transferableObjects); return deferred.promise; }); }; /** * Posts a message to a web worker with configuration to initialize loading * and compiling a web assembly module asychronously, as well as an optional * fallback JavaScript module to use if Web Assembly is not supported. * * @param {Object} [webAssemblyOptions] An object with the following properties: * @param {String} [webAssemblyOptions.modulePath] The path of the web assembly JavaScript wrapper module. * @param {String} [webAssemblyOptions.wasmBinaryFile] The path of the web assembly binary file. * @param {String} [webAssemblyOptions.fallbackModulePath] The path of the fallback JavaScript module to use if web assembly is not supported. * @returns {Promise.} 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. */ TaskProcessor.prototype.initWebAssemblyModule = function (webAssemblyOptions) { if (!defined(this._worker)) { this._worker = createWorker(this); } var deferred = when.defer(); var processor = this; var worker = this._worker; getWebAssemblyLoaderConfig(this, webAssemblyOptions).then(function(wasmConfig) { return when(canTransferArrayBuffer(), function(canTransferArrayBuffer) { var transferableObjects; var binary = wasmConfig.wasmBinary; if (defined(binary) && canTransferArrayBuffer) { transferableObjects = [binary]; } worker.onmessage = function(event) { worker.onmessage = function(event) { completeTask(processor, event.data); }; deferred.resolve(event.data); }; worker.postMessage({ webAssemblyConfig : wasmConfig }, transferableObjects); }); }); return deferred; }; /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. * * @returns {Boolean} True if this object was destroyed; otherwise, false. * * @see TaskProcessor#destroy */ TaskProcessor.prototype.isDestroyed = function() { return false; }; /** * Destroys this object. This will immediately terminate the Worker. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. */ TaskProcessor.prototype.destroy = function() { if (defined(this._worker)) { this._worker.terminate(); } return destroyObject(this); }; /** * An event that's raised when a task is completed successfully. Event handlers are passed * the error object is a task fails. * * @type {Event} * * @private */ TaskProcessor.taskCompletedEvent = taskCompletedEvent; // exposed for testing purposes TaskProcessor._defaultWorkerModulePrefix = 'Workers/'; TaskProcessor._workerModulePrefix = TaskProcessor._defaultWorkerModulePrefix; TaskProcessor._canTransferArrayBuffer = undefined; export default TaskProcessor;