@itwin/core-backend
Version:
iTwin.js backend components
193 lines • 8.44 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 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