@itwin/core-frontend
Version:
iTwin.js frontend components
153 lines • 6.77 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Tiles
*/
import { assert, compareStrings, SortedArray } from "@itwin/core-bentley";
import { IModelApp } from "../../IModelApp";
import { IpcApp } from "../../IpcApp";
import { IModelTile, TileRequestChannel } from "../../tile/internal";
/** Handles requests to the cloud storage tile cache, if one is configured. If a tile's content is not found in the cache, subsequent requests for the same tile will
* use the IModelTileChannel instead.
*/
class CloudStorageCacheChannel extends TileRequestChannel {
async requestContent(tile) {
assert(tile instanceof IModelTile);
return IModelApp.tileAdmin.requestCachedTileContent(tile);
}
onNoContent(request) {
assert(request.tile instanceof IModelTile);
request.tile.requestChannel = IModelApp.tileAdmin.channels.iModelChannels.rpc;
++this._statistics.totalCacheMisses;
return true;
}
}
/** For an [[IpcApp]], allows backend tile generation requests in progress to be canceled. */
class IModelTileChannel extends TileRequestChannel {
_canceled = new Map();
onActiveRequestCanceled(request) {
const tree = request.tile.tree;
let entry = this._canceled.get(tree.iModel);
if (!entry)
this._canceled.set(tree.iModel, entry = new Map());
let ids = entry.get(tree.id);
if (!ids)
entry.set(tree.id, ids = new Set());
ids.add(request.tile.contentId);
}
processCancellations() {
for (const [imodel, entries] of this._canceled) {
const treeContentIds = [];
for (const [treeId, tileIds] of entries) {
const contentIds = Array.from(tileIds);
treeContentIds.push({ treeId, contentIds });
this._statistics.totalAbortedRequests += contentIds.length;
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
IpcApp.appFunctionIpc.cancelTileContentRequests(imodel.getRpcProps(), treeContentIds);
}
this._canceled.clear();
}
onIModelClosed(imodel) {
this._canceled.delete(imodel);
}
}
/** If TileAdmin.Props.cacheTileMetadata is true, then this is the first channel through which we request content for an IModelTile.
* It serves a niche purpose: a tile pre-generation agent that wants to ensure that every tile selected during interaction with the application
* has its tile generated and cached in cloud storage. This agent might request thousands of tiles in sequence, causing a given tile to be discarded
* and reloaded many times. To avoid pointlessly reloading tiles whose contents have already been generated, this channel caches the metadata for each tile;
* on subsequent requests for the same tile, it produces the metadata and an empty RenderGraphic.
*/
class IModelTileMetadataCacheChannel extends TileRequestChannel {
_cacheByIModel = new Map();
constructor() {
super("itwinjs-imodel-metadata-cache", 100);
}
onNoContent(request) {
assert(request.tile instanceof IModelTile);
const channels = IModelApp.tileAdmin.channels.iModelChannels;
request.tile.requestChannel = channels.cloudStorage ?? channels.rpc;
return true;
}
async requestContent(tile) {
assert(tile instanceof IModelTile);
const content = this.getCachedContent(tile);
return content ? { content } : undefined;
}
getCachedContent(tile) {
const cached = this._cacheByIModel.get(tile.iModel)?.get(tile.tree)?.findEquivalent((x) => compareStrings(x.contentId, tile.contentId));
if (!cached)
return undefined;
const content = {
...cached,
graphic: cached.hasGraphic ? IModelApp.renderSystem.createGraphicList([]) : undefined,
contentRange: cached.contentRange?.clone(),
};
return content;
}
onIModelClosed(imodel) {
this._cacheByIModel.delete(imodel);
}
registerChannel(channel) {
channel.contentCallback = (tile, content) => this.cache(tile, content);
}
cache(tile, content) {
assert(tile instanceof IModelTile);
let trees = this._cacheByIModel.get(tile.iModel);
if (!trees)
this._cacheByIModel.set(tile.iModel, trees = new Map());
let list = trees.get(tile.tree);
if (!list)
trees.set(tile.tree, list = new SortedArray((lhs, rhs) => compareStrings(lhs.contentId, rhs.contentId)));
assert(undefined === list.findEquivalent((x) => compareStrings(x.contentId, tile.contentId)));
list.insert({
contentId: tile.contentId,
hasGraphic: undefined !== content.graphic,
contentRange: content.contentRange?.clone(),
isLeaf: content.isLeaf,
sizeMultiplier: content.sizeMultiplier,
emptySubRangeMask: content.emptySubRangeMask,
});
}
}
/** TileRequestChannels used for requesting content for IModelTiles.
*/
export class IModelTileRequestChannels {
_cloudStorage;
_contentCache;
rpc;
constructor(args) {
const channelName = "itwinjs-tile-rpc";
this.rpc = args.usesHttp ? new TileRequestChannel(channelName, args.concurrency) : new IModelTileChannel(channelName, args.concurrency);
if (args.cacheMetadata) {
this._contentCache = new IModelTileMetadataCacheChannel();
this._contentCache.registerChannel(this.rpc);
}
this._cloudStorage = new CloudStorageCacheChannel("itwinjs-cloud-cache", args.cacheConcurrency);
this._contentCache?.registerChannel(this._cloudStorage);
}
get cloudStorage() {
return this._cloudStorage;
}
[Symbol.iterator]() {
const channels = [this.rpc];
if (this._cloudStorage)
channels.push(this._cloudStorage);
if (this._contentCache)
channels.push(this._contentCache);
return channels[Symbol.iterator]();
}
setRpcConcurrency(concurrency) {
this.rpc.concurrency = concurrency;
}
getChannelForTile(tile) {
return tile.requestChannel || this._contentCache || this._cloudStorage || this.rpc;
}
/** Strictly for tests. */
getCachedContent(tile) {
return this._contentCache?.getCachedContent(tile);
}
}
//# sourceMappingURL=IModelTileRequestChannels.js.map