UNPKG

@itwin/core-frontend

Version:
153 lines 6.77 kB
/*--------------------------------------------------------------------------------------------- * 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