UNPKG

@itwin/core-backend

Version:
193 lines • 8.44 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 RpcInterface */ import { assert, BeDuration, Logger } from "@itwin/core-bentley"; import { IModelTileRpcInterface, RpcInterface, RpcManager, RpcPendingResponse, TileContentSource } from "@itwin/core-common"; import { BackendLoggerCategory } from "../BackendLoggerCategory"; import { IModelDb } from "../IModelDb"; import { IModelHost } from "../IModelHost"; import { PromiseMemoizer } from "../PromiseMemoizer"; import { RpcTrace } from "../rpc/tracing"; import { RpcBriefcaseUtility } from "./RpcBriefcaseUtility"; import { IModelNative } from "../internal/NativePlatform"; import { _nativeDb } from "../internal/Symbols"; function generateTileRequestKey(props) { const token = props.tokenProps; return `${JSON.stringify({ key: token.key, iTwinId: token.iTwinId, iModelId: token.iModelId, changesetId: token.changeset?.id, })}:${props.treeId}`; } class TileRequestMemoizer extends PromiseMemoizer { _loggerCategory = BackendLoggerCategory.IModelTileRequestRpc; makeMetadata(props) { const meta = { ...props.tokenProps }; this.addMetadata(meta, props); return meta; } constructor(memoizeFn, generateKeyFn) { super(memoizeFn, generateKeyFn); } memoize(props) { return super.memoize(props); } deleteMemoized(props) { super.deleteMemoized(props); } log(status, props) { const descr = `${this._operationName}(${this.stringify(props)})`; Logger.logTrace(this._loggerCategory, `Backend ${status} ${descr}`, () => this.makeMetadata(props)); } async perform(props) { this.log("received", props); const tileQP = this.memoize(props); await BeDuration.race(this._timeoutMilliseconds, tileQP.promise).catch(() => { }); // Note: Rejections must be caught so that the memoization entry can be deleted if (tileQP.isPending) { this.log("issuing pending status for", props); throw new RpcPendingResponse(); // eslint-disable-line @typescript-eslint/only-throw-error } this.deleteMemoized(props); if (tileQP.isFulfilled) { this.log("completed", props); assert(undefined !== tileQP.result); return tileQP.result; } assert(tileQP.isRejected); this.log("rejected", props); throw tileQP.error; } } async function getTileTreeProps(props) { assert(undefined !== props.accessToken); const db = await RpcBriefcaseUtility.findOpenIModel(props.accessToken, props.tokenProps); return db.tiles.requestTileTreeProps(props.treeId); } class RequestTileTreePropsMemoizer extends TileRequestMemoizer { get _timeoutMilliseconds() { return IModelHost.tileTreeRequestTimeout; } get _operationName() { return "requestTileTreeProps"; } stringify(props) { return props.treeId; } addMetadata(meta, props) { meta.treeId = props.treeId; } static _instance; constructor() { super(getTileTreeProps, generateTileRequestKey); IModelHost.onBeforeShutdown.addOnce(() => { this[Symbol.dispose](); RequestTileTreePropsMemoizer._instance = undefined; }); } static async perform(props) { if (undefined === this._instance) this._instance = new RequestTileTreePropsMemoizer(); return this._instance.perform(props); } } async function getTileContent(props) { assert(undefined !== props.accessToken); const db = await RpcBriefcaseUtility.findOpenIModel(props.accessToken, props.tokenProps); const tile = await db.tiles.requestTileContent(props.treeId, props.contentId); // ###TODO: Verify the guid supplied by the front-end matches the guid stored in the model? if (IModelHost.usingExternalTileCache) { const tileMetadata = { backendName: IModelHost.applicationId, tileGenerationTime: tile.elapsedSeconds.toString(), tileSize: tile.content.byteLength.toString(), }; await IModelHost.tileStorage?.uploadTile(props.tokenProps.iModelId ?? db.iModelId, props.tokenProps.changeset?.id ?? db.changeset.id, props.treeId, props.contentId, tile.content, props.guid, tileMetadata); const { accessToken: _, ...safeProps } = props; Logger.logInfo(BackendLoggerCategory.IModelTileRequestRpc, "Generated and uploaded tile", { tileMetadata, ...safeProps }); return TileContentSource.ExternalCache; } return TileContentSource.Backend; } function generateTileContentKey(props) { return `${generateTileRequestKey(props)}:${props.contentId}`; } class RequestTileContentMemoizer extends TileRequestMemoizer { get _timeoutMilliseconds() { return IModelHost.tileContentRequestTimeout; } get _operationName() { return "requestTileContent"; } stringify(props) { return `${props.treeId}:${props.contentId}`; } addMetadata(meta, props) { meta.treeId = props.treeId; meta.contentId = props.contentId; } static _instance; constructor() { super(getTileContent, generateTileContentKey); IModelHost.onBeforeShutdown.addOnce(() => { this[Symbol.dispose](); RequestTileContentMemoizer._instance = undefined; }); } static get instance() { if (undefined === this._instance) this._instance = new RequestTileContentMemoizer(); return this._instance; } static async perform(props) { return this.instance.perform(props); } } function currentActivity() { return RpcTrace.expectCurrentActivity; } /** @internal */ export class IModelTileRpcImpl extends RpcInterface { static register() { RpcManager.registerImpl(IModelTileRpcInterface, IModelTileRpcImpl); } async requestTileTreeProps(tokenProps, treeId) { return RequestTileTreePropsMemoizer.perform({ accessToken: currentActivity().accessToken, tokenProps, treeId }); } async purgeTileTrees(tokenProps, modelIds) { // `undefined` gets forwarded as `null`... if (null === modelIds) modelIds = undefined; const db = await RpcBriefcaseUtility.findOpenIModel(currentActivity().accessToken, tokenProps); if (!db.isOpen) { return; } return db[_nativeDb].purgeTileTrees(modelIds); } async generateTileContent(tokenProps, treeId, contentId, guid) { return RequestTileContentMemoizer.perform({ accessToken: currentActivity().accessToken, tokenProps, treeId, contentId, guid }); } async retrieveTileContent(tokenProps, key) { const db = await RpcBriefcaseUtility.findOpenIModel(currentActivity().accessToken, tokenProps); return db.tiles.getTileContent(key.treeId, key.contentId); } async getTileCacheConfig(tokenProps) { if (IModelHost.tileStorage === undefined) return undefined; const iModelId = tokenProps.iModelId ?? (await RpcBriefcaseUtility.findOpenIModel(currentActivity().accessToken, tokenProps)).iModelId; return IModelHost.tileStorage.getDownloadConfig(iModelId); } async queryVersionInfo() { return IModelNative.platform.getTileVersionInfo(); } /** @internal */ async requestElementGraphics(rpcProps, request) { const iModel = await RpcBriefcaseUtility.findOpenIModel(currentActivity().accessToken, rpcProps); return iModel.generateElementGraphics(request); } } /** @internal */ export async function cancelTileContentRequests(tokenProps, contentIds) { const iModel = IModelDb.findByKey(tokenProps.key); const props = { tokenProps, treeId: "", contentId: "" }; for (const entry of contentIds) { props.treeId = entry.treeId; for (const contentId of entry.contentIds) { props.contentId = contentId; RequestTileContentMemoizer.instance.deleteMemoized(props); } iModel[_nativeDb].cancelTileContentRequests(entry.treeId, entry.contentIds); } } //# sourceMappingURL=IModelTileRpcImpl.js.map