You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
371 lines
11 KiB
JavaScript
371 lines
11 KiB
JavaScript
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.<Object>|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.<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.
|
|
*/
|
|
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.
|
|
* <br /><br />
|
|
* If this object was destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> 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.
|
|
* <br /><br />
|
|
* Once an object is destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> 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;
|