@itwin/core-frontend
Version:
iTwin.js frontend components
196 lines • 9.08 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 Tiles
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TileRequest = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const IModelApp_1 = require("../IModelApp");
const internal_1 = require("./internal");
/** Represents a pending or active request to load the contents of a [[Tile]]. The request coordinates with the [[Tile.requestContent]] to obtain the raw content and
* [[Tile.readContent]] to convert the result into a [[RenderGraphic]]. TileRequests are created internally as needed; it is never necessary or useful for external code to create them.
* @public
* @extensions
*/
class TileRequest {
/** The requested tile. While the request is pending or active, `tile.request` points back to this TileRequest. */
tile;
/** The channel via which the request will be executed. */
channel;
/** The set of [[TileUser]]s that are awaiting the result of this request. When this becomes empty, the request is canceled because no user cares about it.
* @internal
*/
users;
_state;
/** Determines the order in which pending requests are pulled off the queue to become active. A tile with a lower priority value takes precedence over one with a higher value. */
priority = 0;
/** Constructor */
constructor(tile, user) {
this._state = TileRequest.State.Queued;
this.tile = tile;
this.channel = tile.channel;
this.users = IModelApp_1.IModelApp.tileAdmin.getTileUserSetForRequest(user);
}
/** The set of [[Viewport]]s that are awaiting the result of this request. When this becomes empty, the request is canceled because no user cares about it. */
get viewports() {
return internal_1.TileUser.viewportsFromUsers(this.users);
}
/** The request's current state. */
get state() { return this._state; }
/** True if the request has been enqueued but not yet dispatched. */
get isQueued() { return TileRequest.State.Queued === this._state; }
/** True if the request has been canceled. */
get isCanceled() {
// If iModel was closed, cancel immediately
if (this.tile.iModel.tiles.isDisposed)
return true;
// After we've received the raw tile data, always finish processing it - otherwise tile may end up in limbo (and producing tile content should be faster than re-requesting raw data).
if (TileRequest.State.Loading === this._state)
return false;
// If no user cares about this tile any more, we're canceled.
return this.users.isEmpty;
}
/** The tile tree to which the requested [[Tile]] belongs. */
get tree() { return this.tile.tree; }
/** Indicate that the specified user is awaiting the result of this request.
* @internal
*/
addUser(user) {
this.users = IModelApp_1.IModelApp.tileAdmin.getTileUserSetForRequest(user, this.users);
}
/** Transition the request from "queued" to "active", kicking off a series of asynchronous operations usually beginning with an http request, and -
* if the request is not subsequently canceled - resulting in either a successfully-loaded Tile, or a failed ("not found") Tile.
* @internal
*/
async dispatch(onHttpResponse) {
if (this.isCanceled)
return;
(0, core_bentley_1.assert)(this._state === TileRequest.State.Queued);
this._state = TileRequest.State.Dispatched;
let response;
let gotResponse = false;
try {
response = await this.channel.requestContent(this.tile, () => this.isCanceled);
gotResponse = true;
// Set this now, so our `isCanceled` check can see it.
this._state = TileRequest.State.Loading;
}
catch (err) {
if (err.errorNumber && err.errorNumber === core_bentley_1.IModelStatus.ServerTimeout) {
// Invalidate scene - if tile is re-selected, it will be re-requested.
this.notifyAndClear();
this._state = TileRequest.State.Failed;
this.channel.recordTimeout();
}
else {
// Unknown error - not retryable
this.setFailed();
}
}
// Notify caller that we have finished http activity.
onHttpResponse();
if (!gotResponse || this.isCanceled)
return;
if (undefined === response && this.channel.onNoContent(this)) {
// Invalidate scene - if tile is re-selected, it will be re-requested - presumably via a different channel.
this.notifyAndClear();
this._state = TileRequest.State.Failed;
return;
}
return this.handleResponse(response);
}
/** Cancels this request. This leaves the associated Tile's state untouched.
* @internal
*/
cancel() {
this.notifyAndClear();
if (TileRequest.State.Dispatched === this._state)
this.channel.onActiveRequestCanceled(this);
this._state = TileRequest.State.Failed;
}
/** Invalidates the scene of each [[TileUser]] interested in this request - typically because the request succeeded, failed, or was canceled. */
notify() {
this.users.forEach((user) => {
if (user.onRequestStateChanged)
user.onRequestStateChanged(this);
});
}
/** Invalidates the scene of each [[TileUser]] interested in this request and clears the set of interested users. */
notifyAndClear() {
this.notify();
this.users = IModelApp_1.IModelApp.tileAdmin.emptyTileUserSet;
this.tile.request = undefined;
}
setFailed() {
this.notifyAndClear();
this._state = TileRequest.State.Failed;
this.tile.setNotFound();
this.channel.recordFailure();
}
/** Invoked when the raw tile content becomes available, to convert it into a tile graphic. */
async handleResponse(response) {
let content;
let data;
if (undefined !== response) {
if (typeof response === "string")
data = (0, core_bentley_1.base64StringToUint8Array)(response);
else if (response instanceof Uint8Array || response instanceof core_common_1.ImageSource)
data = response;
else if (response instanceof ArrayBuffer)
data = new Uint8Array(response);
else if (typeof response === "object") {
if ("content" in response)
content = response.content;
else if ("data" in response)
data = response;
}
}
if (!content && !data) {
this.setFailed();
return;
}
try {
const start = Date.now();
if (!content) {
(0, core_bentley_1.assert)(undefined !== data);
content = await this.tile.readContent(data, IModelApp_1.IModelApp.renderSystem, () => this.isCanceled);
if (this.isCanceled)
return;
}
this._state = TileRequest.State.Completed;
this.tile.setContent(content);
this.notifyAndClear();
this.channel.recordCompletion(this.tile, content, Date.now() - start);
}
catch {
this.setFailed();
}
}
}
exports.TileRequest = TileRequest;
/** @public */
(function (TileRequest) {
/** The states through which a [[TileRequest]] proceeds. During the first 3 states, the [[Tile]]'s `request` member is defined,
* and its [[Tile.LoadStatus]] is computed based on the state of its request.
*@ public
*/
let State;
(function (State) {
/** Initial state. Request is pending but not yet dispatched. */
State[State["Queued"] = 0] = "Queued";
/** Follows `Queued` when request begins to be actively processed. */
State[State["Dispatched"] = 1] = "Dispatched";
/** Follows `Dispatched` when the response to the request is being converted into tile graphics. */
State[State["Loading"] = 2] = "Loading";
/** Follows `Loading` when tile graphic has successfully been produced. */
State[State["Completed"] = 3] = "Completed";
/** Follows any state in which an error prevents progression, or during which the request was canceled. */
State[State["Failed"] = 4] = "Failed";
})(State = TileRequest.State || (TileRequest.State = {}));
})(TileRequest || (exports.TileRequest = TileRequest = {}));
//# sourceMappingURL=TileRequest.js.map