@itwin/core-frontend
Version:
iTwin.js frontend components
213 lines • 9 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module IModelConnection
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tiles = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const IModelApp_1 = require("./IModelApp");
const internal_1 = require("./tile/internal");
class TreeOwner {
_tileTree;
_loadStatus = internal_1.TileTreeLoadStatus.NotLoaded;
_supplier;
_iModel;
id;
get tileTree() { return this._tileTree; }
get loadStatus() { return this._loadStatus; }
get iModel() { return this._iModel; }
constructor(id, supplier, iModel) {
this.id = id;
this._supplier = supplier;
this._iModel = iModel;
}
load() {
this._load(); // eslint-disable-line @typescript-eslint/no-floating-promises
return this.tileTree;
}
async loadTree() {
await this._load();
return this.tileTree;
}
[Symbol.dispose]() {
this._tileTree = (0, core_bentley_1.dispose)(this._tileTree);
this._loadStatus = internal_1.TileTreeLoadStatus.NotLoaded;
}
async _load() {
if (internal_1.TileTreeLoadStatus.NotLoaded !== this.loadStatus)
return;
this._loadStatus = internal_1.TileTreeLoadStatus.Loading;
let tree;
let newStatus;
try {
tree = await this._supplier.createTileTree(this.id, this._iModel);
newStatus = undefined !== tree && !tree.rootTile.contentRange.isNull ? internal_1.TileTreeLoadStatus.Loaded : internal_1.TileTreeLoadStatus.NotFound;
}
catch (err) {
newStatus = (err.errorNumber && err.errorNumber === core_bentley_1.IModelStatus.ServerTimeout) ? internal_1.TileTreeLoadStatus.NotLoaded : internal_1.TileTreeLoadStatus.NotFound;
}
if (internal_1.TileTreeLoadStatus.Loading === this._loadStatus) {
this._tileTree = tree;
this._loadStatus = newStatus;
IModelApp_1.IModelApp.tileAdmin.onTileTreeLoad.raiseEvent(this);
}
}
}
/** Provides access to [[TileTree]]s associated with an [[IModelConnection]].
* The tile trees are accessed indirectly via their corresponding [[TileTreeOwner]]s.
* Loaded tile trees will be discarded after the iModel is closed, after a period of disuse, or when the contents of a [[GeometricModelState]] they represent
* change.
* @see [[IModelConnection.tiles]].
* @public
* @extensions
*/
class Tiles {
_iModel;
_treesBySupplier = new Map();
_disposed = false;
/** @internal */
get isDisposed() { return this._disposed; }
/** @internal */
constructor(iModel) {
this._iModel = iModel;
iModel.onEcefLocationChanged.addListener(() => {
for (const supplier of this._treesBySupplier.keys()) {
if (supplier.isEcefDependent)
this.dropSupplier(supplier);
}
});
// When project extents change, purge tile trees for spatial models.
iModel.onProjectExtentsChanged.addListener(async () => {
if (!iModel.isBriefcaseConnection() || !iModel.editingScope)
await this.purgeModelTrees(this.getSpatialModels());
});
}
/** @internal */
[Symbol.dispose]() {
this.reset();
this._disposed = true;
}
/** Intended strictly for tests.
* @internal
*/
reset() {
for (const supplier of this._treesBySupplier)
supplier[1].forEach((_key, value) => value[Symbol.dispose]());
this._treesBySupplier.clear();
}
/** @internal */
async purgeTileTrees(modelIds) {
return IModelApp_1.IModelApp.tileAdmin.purgeTileTrees(this._iModel, modelIds);
}
getModelsAnimatedByScheduleScript(scriptSourceElementId) {
const modelIds = new Set();
for (const supplier of this._treesBySupplier.keys())
if (supplier.addModelsAnimatedByScript)
supplier.addModelsAnimatedByScript(modelIds, scriptSourceElementId, this.getTreeOwnersForSupplier(supplier));
return modelIds;
}
/** Update the [[Tile]]s for any [[TileTree]]s that use the [RenderSchedule.Script]($common) hosted by the specified
* [RenderTimeline]($backend) or [DisplayStyle]($backend) element. This method should be invoked after
* the host element is updated in the database with a new script, so that any [[Viewport]]s displaying tiles produced
* based on the previous version of the script are updated to use the new version of the script.
* @param scriptSourceElementId The Id of the RenderTimeline or DisplayStyle element that hosts the script.
* @public
*/
async updateForScheduleScript(scriptSourceElementId) {
return this.purgeModelTrees(this.getModelsAnimatedByScheduleScript(scriptSourceElementId));
}
async purgeModelTrees(modelIds) {
if (0 === modelIds.size)
return;
const ids = Array.from(modelIds);
await this.purgeTileTrees(ids);
IModelApp_1.IModelApp.viewManager.refreshForModifiedModels(ids);
}
getSpatialModels() {
const modelIds = new Set();
for (const supplier of this._treesBySupplier.keys())
if (supplier.addSpatialModels)
supplier.addSpatialModels(modelIds, this.getTreeOwnersForSupplier(supplier));
return modelIds;
}
/** Obtain the owner of a TileTree.
* The `id` is unique within all tile trees associated with `supplier`; its specific structure is an implementation detail known only to the supplier.
* A [[TileTreeReference]] uses this method to obtain the tile tree to which it refers.
*/
getTileTreeOwner(id, supplier) {
let trees = this._treesBySupplier.get(supplier);
if (undefined === trees) {
trees = new core_bentley_1.Dictionary((lhs, rhs) => supplier.compareTileTreeIds(lhs, rhs));
this._treesBySupplier.set(supplier, trees);
}
let tree = trees.get(id);
if (undefined === tree) {
tree = new TreeOwner(id, supplier, this._iModel);
trees.set(id, tree);
}
return tree;
}
/** Remove tile tree matching the provided tile tree supplier/id
* @internal
*/
resetTileTreeOwner(id, supplier) {
const trees = this._treesBySupplier.get(supplier);
const tree = trees?.get(id);
if (tree) {
tree[Symbol.dispose]();
trees?.delete(id);
}
}
/** Disposes of all [[TileTree]]s belonging to `supplier` and removes `supplier` from the set of known tile tree suppliers. */
dropSupplier(supplier) {
const trees = this._treesBySupplier.get(supplier);
if (undefined === trees)
return;
trees.forEach((_key, value) => value[Symbol.dispose]());
this._treesBySupplier.delete(supplier);
}
/** Invokes a function on each extant TileTreeOwner. */
forEachTreeOwner(func) {
for (const dict of this._treesBySupplier.values())
dict.forEach((_key, value) => func(value));
}
/** Iterate over all of the TileTreeOwners. */
*[Symbol.iterator]() {
for (const [supplier, dict] of this._treesBySupplier) {
for (const entry of dict)
yield { supplier, id: entry.key, owner: entry.value };
}
}
/** Obtain the TileTreeOwners supplied by the specified supplier. */
getTreeOwnersForSupplier(supplier) {
function* iterator(trees) {
if (trees)
for (const entry of trees)
yield { id: entry.key, owner: entry.value };
}
return {
[Symbol.iterator]: () => iterator(this._treesBySupplier.get(supplier)),
};
}
/** Unload any tile trees which have not been drawn since at least the specified time, excluding any of the specified TileTrees.
* @internal
*/
purge(olderThan, exclude) {
// NB: It would be nice to be able to detect completely useless leftover Owners or Suppliers, but we can't know if any TileTreeReferences exist pointing to a given Owner.
for (const entry of this._treesBySupplier) {
const dict = entry[1];
dict.forEach((_treeId, owner) => {
const tree = owner.tileTree;
if (undefined !== tree && tree.lastSelectedTime.milliseconds < olderThan.milliseconds)
if (undefined === exclude || !exclude.has(tree))
owner[Symbol.dispose]();
});
}
}
}
exports.Tiles = Tiles;
//# sourceMappingURL=Tiles.js.map