cesium
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
339 lines (288 loc) • 12.7 kB
JavaScript
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.<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('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.<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;