UNPKG

itowns

Version:

A JS/WebGL framework for 3D geospatial data visualization

388 lines (319 loc) 12.8 kB
"use strict"; 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;