itowns
Version:
A JS/WebGL framework for 3D geospatial data visualization
388 lines (319 loc) • 12.8 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _jsPriorityQueue = _interopRequireDefault(require("js-priority-queue"));
var _DataSourceProvider = _interopRequireDefault(require("../../Provider/DataSourceProvider"));
var _TileProvider = _interopRequireDefault(require("../../Provider/TileProvider"));
var _dTilesProvider = _interopRequireDefault(require("../../Provider/3dTilesProvider"));
var _PointCloudProvider = _interopRequireDefault(require("../../Provider/PointCloudProvider"));
var _CancelledCommandException = _interopRequireDefault(require("./CancelledCommandException"));
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
function queueOrdering(a, b) {
var cmp = b.priority - a.priority; // Prioritize recent commands
if (cmp === 0) {
return b.timestamp - a.timestamp;
}
return cmp;
}
function drawNextLayer(storages) {
// Dithering algorithm to select the next layer
// see https://gamedev.stackexchange.com/a/95696 for more details
var sum = 0;
var selected;
var max;
var _iterator = _createForOfIteratorHelper(storages),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var item = _step.value;
var st = item[1];
if (st.q.length > 0) {
sum += st.priority;
st.accumulator += st.priority; // Select the biggest accumulator
if (!selected || st.accumulator > max) {
selected = st;
max = st.accumulator;
}
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
if (selected) {
selected.accumulator -= sum;
return selected.q;
}
}
function _instanciateQueue() {
return {
queue: function queue(command) {
var layer = command.layer;
var st = this.storages.get(layer.id);
if (!st) {
st = {
q: new _jsPriorityQueue["default"]({
comparator: queueOrdering
}),
priority: 1,
accumulator: 0
};
this.storages.set(layer.id, st);
} // update priority (layer.priority may have changed)
st.priority = layer.priority || 1;
st.q.queue(command);
this.counters.pending++;
},
storages: new Map(),
counters: {
// commands in progress
executing: 0,
// commands successfully executed
executed: 0,
// commands failed
failed: 0,
// commands cancelled
cancelled: 0,
// commands pending
pending: 0
},
execute: function execute(cmd, provider) {
var _this = this;
this.counters.pending--;
this.counters.executing++;
return provider.executeCommand(cmd).then(function (result) {
_this.counters.executing--;
cmd.resolve(result); // only count successul commands
_this.counters.executed++;
}, function (err) {
_this.counters.executing--;
cmd.reject(err);
_this.counters.failed++;
});
}
};
}
/**
* The Scheduler is in charge of managing the [Providers]{@link Provider} that
* are used to gather resources needed to display the layers on a {@link View}.
* There is only one instance of a Scheduler per webview, and it is instanciated
* with the creation of the first view.
*
* @constructor
*/
function Scheduler() {
// Constructor
this.defaultQueue = _instanciateQueue();
this.hostQueues = new Map();
this.providers = {};
this.maxCommandsPerHost = 6; // TODO: add an options to not instanciate default providers
this.initDefaultProviders();
}
Scheduler.prototype.constructor = Scheduler;
Scheduler.prototype.initDefaultProviders = function () {
// Register all providers
this.addProtocolProvider('tile', _TileProvider["default"]);
this.addProtocolProvider('3d-tiles', _dTilesProvider["default"]);
this.addProtocolProvider('pointcloud', _PointCloudProvider["default"]);
};
Scheduler.prototype.runCommand = function (command, queue, executingCounterUpToDate) {
var _this2 = this;
var provider = this.getProtocolProvider(command.layer.protocol);
if (!provider) {
throw new Error("No known provider for layer ".concat(command.layer.id));
}
queue.execute(command, provider, executingCounterUpToDate).then(function () {
// notify view that one command ended.
command.view.notifyChange(command.requester, command.redraw); // try to execute next command
if (queue.counters.executing < _this2.maxCommandsPerHost) {
var cmd = _this2.deQueue(queue);
if (cmd) {
_this2.runCommand(cmd, queue);
}
}
});
};
Scheduler.prototype.execute = function (command) {
var _this3 = this;
// TODO: check for mandatory commands fields
// parse host
var layer = command.layer;
var host = layer.source && layer.source.url ? new URL(layer.source.url, document.location).host : undefined;
command.promise = new Promise(function (resolve, reject) {
command.resolve = resolve;
command.reject = reject;
}); // init queue if needed
if (host && !this.hostQueues.has(host)) {
this.hostQueues.set(host, _instanciateQueue());
}
var q = host ? this.hostQueues.get(host) : this.defaultQueue;
command.timestamp = Date.now();
q.queue(command);
if (q.counters.executing < this.maxCommandsPerHost) {
// Defer the processing after the end of the current frame.
// Promise.resolve or setTimeout(..., 0) will do the job, the difference
// is:
// - setTimeout is a new task, queued in the event-loop queues
// - Promise is a micro-task, executed before other tasks
Promise.resolve().then(function () {
if (q.counters.executing < _this3.maxCommandsPerHost) {
var cmd = _this3.deQueue(q);
if (cmd) {
_this3.runCommand(cmd, q);
}
}
});
}
return command.promise;
};
/**
* A Provider has the responsability to handle protocols and datablobs. Given a
* data request (see {@link Provider#executeCommand} for details about this
* request), it fetches serialized datasets, file content or even file chunks.
*
* @interface Provider
*/
/**
* When adding a layer to a view, some preprocessing can be done on it, before
* fetching or creating resources attached to it. For example, in the WMTS and
* WFS providers (included in iTowns), default options to the layer are added if
* some are missing.
*
* @param {Layer} layer
* @param {View} [view]
* @param {Scheduler} [scheduler]
* @param {Layer} [parentLayer]
*/
/**
* In the {@link Scheduler} loop, this function is called every time the layer
* needs new information about itself. For tiled layers, it gets the necessary
* tiles, given the current position of the camera on the map. For simple layers
* like a GPX trace, it gets the data once.
* <br><br>
* It passes a `command` object as a parameter, with the `view` and the `layer`
* always present. The other parameters are optional.
*
* @function
* @name Provider#executeCommand
*
* @param {Object} command
* @param {View} command.view
* @param {Layer} command.layer
* @param {TileMesh} [command.requester] - Every layer is attached to a tile.
* @param {number} [command.targetLevel] - The target level is used when there
* is a tiled layer, such as WMTS or TMS, but not in case like a GPX layer.
*
* @return {Promise} The {@link Scheduler} always expect a Promise as a result,
* resolving to an object containing sufficient information for the associated
* processing to the current layer. For example, see the
* [LayeredMaterialNodeProcessing#updateLayeredMaterialNodeElevation]{@link
* https://github.com/iTowns/itowns/blob/master/src/Process/LayeredMaterialNodeProcessing.js}
* class or other processing class.
*/
/**
* Adds a provider for a specified protocol. The provider will be used when
* executing the queue to provide resources. See {@link Provider} for more
* informations.
* By default, some protocols are already set in iTowns: WMTS, WMS, WFS, TMS,
* XYZ, PotreeConverter, Rasterizer, 3D-Tiles and Static.
* <br><br>
* Warning: if the specified protocol has already a provider attached to it, the
* current provider will be overwritten by the given provider.
*
* @param {string} protocol - The name of the protocol to add. This is the
* `protocol` parameter put inside the configuration when adding a layer. The
* capitalization of the name is not taken into account here.
* @param {Provider} provider - The provider to link to the protocol, that must
* respect the {@link Provider} interface description.
*
* @throws {Error} an error if any method of the {@link Provider} is not present
* in the provider.
*/
Scheduler.prototype.addProtocolProvider = function (protocol, provider) {
if (typeof provider.executeCommand !== 'function') {
throw new Error("Can't add provider for ".concat(protocol, ": missing a executeCommand function."));
}
this.providers[protocol] = provider;
};
/**
* Get a specific {@link Provider} given a particular protocol.
*
* @param {string} protocol
*
* @return {Provider}
*/
Scheduler.prototype.getProtocolProvider = function (protocol) {
return this.providers[protocol] || _DataSourceProvider["default"];
};
Scheduler.prototype.commandsWaitingExecutionCount = function () {
var sum = this.defaultQueue.counters.pending + this.defaultQueue.counters.executing;
var _iterator2 = _createForOfIteratorHelper(this.hostQueues),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var q = _step2.value;
sum += q[1].counters.pending + q[1].counters.executing;
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return sum;
};
Scheduler.prototype.commandsRunningCount = function () {
var sum = this.defaultQueue.counters.executing;
var _iterator3 = _createForOfIteratorHelper(this.hostQueues),
_step3;
try {
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
var q = _step3.value;
sum += q[1].counters.executing;
}
} catch (err) {
_iterator3.e(err);
} finally {
_iterator3.f();
}
return sum;
};
Scheduler.prototype.resetCommandsCount = function (type) {
var sum = this.defaultQueue.counters[type];
this.defaultQueue.counters[type] = 0;
var _iterator4 = _createForOfIteratorHelper(this.hostQueues),
_step4;
try {
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
var q = _step4.value;
sum += q[1].counters[type];
q[1].counters[type] = 0;
}
} catch (err) {
_iterator4.e(err);
} finally {
_iterator4.f();
}
return sum;
};
Scheduler.prototype.deQueue = function (queue) {
var st = drawNextLayer(queue.storages);
while (st && st.length > 0) {
var cmd = st.dequeue();
if (cmd.earlyDropFunction && cmd.earlyDropFunction(cmd)) {
queue.counters.pending--;
queue.counters.cancelled++;
cmd.reject(new _CancelledCommandException["default"](cmd));
} else {
return cmd;
}
}
return undefined;
};
var _default = Scheduler;
exports["default"] = _default;