import Uri from "../ThirdParty/Uri.js"; 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: processor._workerPath, }; 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._workerPath ); } 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} workerPath The Url to the worker. This can either be an absolute path or relative to the Cesium Workers folder. * @param {Number} [maximumActiveTasks=Number.POSITIVE_INFINITY] 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(workerPath, maximumActiveTasks) { this._workerPath = new Uri(workerPath).isAbsolute() ? workerPath : TaskProcessor._workerModulePrefix + workerPath; this._maximumActiveTasks = defaultValue( maximumActiveTasks, Number.POSITIVE_INFINITY ); 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('myWorkerPath'); * 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;