import { expect } from "chai" ; import { compareStrings } from "@itwin/core-bentley" ; import { IModelTileTreeProps , ServerTimeoutError } from "@itwin/core-common" ; import { IModelApp , IModelConnection , overrideRequestTileTreeProps , RenderSystem , SnapshotConnection , Tile , TileContent , TileDrawArgs , TileLoadPriority , TileRequest , TileRequestChannel , TileTree , } from "@itwin/core-frontend" ; import { Range3d , Transform } from "@itwin/core-geometry" ; import { TestUtility } from "../../TestUtility" ; class MockTile extends Tile { protected _loadChildren ( resolve , _reject ) { resolve ( [ ] ) ; } public get channel ( ) { return { } as unknown as TileRequestChannel ; } public async requestContent ( _canceled ) { return undefined ; } public async readContent ( _data , _system , _canceled ? ) { return { } ; } public constructor ( tree ) { super ( { contentId : "tile" , range : new Range3d ( ) , maximumSize : 512 } , tree ) ; } } class MockTree extends TileTree { private _rootTile : Tile ; public constructor ( id , iModel ) { super ( { id , iModel , modelId : "0x2a" , location : Transform . createIdentity ( ) , priority : TileLoadPriority . Primary } ) ; this . _rootTile = new MockTile ( this ) ; } public get rootTile ( ) { return this . _rootTile ; } public get is3d ( ) { return true ; } public get maxDepth ( ) { return 1 ; } public get viewFlagOverrides ( ) { return { } ; } public override get isContentUnbounded ( ) { return false ; } protected _selectTiles ( _args ) { return [ ] ; } public draw ( _args ) { } public prune ( ) { } } describe ( "TileTreeSupplier" , ( ) => { let imodel ; before ( async ( ) => { await TestUtility . startFrontend ( ) ; imodel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; } ) ; after ( async ( ) => { if ( imodel ) await imodel . close ( ) ; await TestUtility . shutdownFrontend ( ) ; } ) ; it ( "should be discarded when ecef location is modified if tiles are ecef-dependent" , async ( ) => { class Supplier { public readonly isEcefDependent ? : true ; public constructor ( isEcefDependent ) { if ( isEcefDependent ) this . isEcefDependent = true ; } public compareTileTreeIds ( lhs , rhs ) { return compareStrings ( lhs , rhs ) ; } public async createTileTree ( id , iModel ) { return new MockTree ( id , iModel ) ; } } const ecefSup = new Supplier ( true ) ; const nonEcefSup = new Supplier ( false ) ; const ecefOwner = imodel . tiles . getTileTreeOwner ( "ecef" , ecefSup ) ; const nonEcefOwner = imodel . tiles . getTileTreeOwner ( "nonEcef" , nonEcefSup ) ; const ecefTree = ( await ecefOwner . loadTree ( ) ) ! ; const nonEcefTree = ( await nonEcefOwner . loadTree ( ) ) ! ; expect ( ecefTree ) . not . to . be . undefined ; expect ( nonEcefTree ) . not . to . be . undefined ; expect ( ecefTree . isDisposed ) . to . be . false ; expect ( nonEcefTree . isDisposed ) . to . be . false ; imodel . setEcefLocation ( { origin : [ 0 , 0 , 0 ] , orientation : { yaw : 0 , pitch : 0 , roll : 0 } } ) ; expect ( ecefTree . isDisposed ) . to . be . true ; expect ( nonEcefTree . isDisposed ) . to . be . false ; const ecefTree2 = ( await ecefOwner . loadTree ( ) ) ! ; const nonEcefTree2 = ( await nonEcefOwner . loadTree ( ) ) ! ; expect ( ecefTree2 ) . not . to . be . undefined ; expect ( nonEcefTree2 ) . not . to . be . undefined ; expect ( ecefTree2 . isDisposed ) . to . be . false ; expect ( nonEcefTree2 . isDisposed ) . to . be . false ; expect ( ecefTree2 ) . not . to . equal ( ecefTree ) ; expect ( nonEcefTree2 ) . to . equal ( nonEcefTree ) ; } ) ; } ) ; describe ( "requestTileTreeProps" , ( ) => { let imodel ; let imodel2 ; const maxActiveTileTreePropsRequests = 2 ; before ( async ( ) => { const tileAdmin = { maxActiveTileTreePropsRequests } ; await TestUtility . startFrontend ( { tileAdmin } ) ; imodel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; } ) ; after ( async ( ) => { overrideRequestTileTreeProps ( undefined ) ; if ( imodel ) await imodel . close ( ) ; if ( imodel2 ) await imodel2 . close ( ) ; await TestUtility . shutdownFrontend ( ) ; } ) ; it ( "should process requests in FIFO order" , async ( ) => { const makePromise = async ( id ) => { try { await IModelApp . tileAdmin . requestTileTreeProps ( imodel , id . toString ( ) ) ; } catch ( err ) { throw err ; } } ; const processed = [ ] ; overrideRequestTileTreeProps ( async ( _imodel , treeId ) => { processed . push ( Number . parseInt ( treeId , 10 ) ) ; return new Promise ( ( resolve , _reject ) => { setTimeout ( resolve , 15 ) ; } ) ; } ) ; const promises = [ ] ; const numRequests = 10 ; for ( let id = 0 ; id < numRequests ; id ++ ) promises . push ( makePromise ( id ) ) ; await Promise . all ( promises ) ; expect ( processed . length ) . to . equal ( numRequests ) ; for ( let i = 0 ; i < numRequests ; i ++ ) expect ( processed [ i ] ) . to . equal ( i ) ; overrideRequestTileTreeProps ( undefined ) ; } ) ; it ( "should fulfill requests after failed request" , async ( ) => { const fulfilled = [ ] ; const getProps = async ( id ) => { try { await IModelApp . tileAdmin . requestTileTreeProps ( imodel , id ) ; fulfilled . push ( id ) ; } catch { } } ; const promises = [ getProps ( "0x1c" ) , getProps ( "invalid" ) , getProps ( "0x1c" ) , getProps ( "notanid" ) , getProps ( "0x1c" ) ] ; await Promise . all ( promises ) ; expect ( fulfilled . length ) . to . equal ( 3 ) ; expect ( fulfilled . every ( ( x ) => x === "0x1c" ) ) . to . be . true ; } ) ; it . skip ( "should throttle requests" , async ( ) => { const numRequests = 10 ; const getProps = async ( index ) => { await IModelApp . tileAdmin . requestTileTreeProps ( imodel , "0x1c" ) ; const stats = IModelApp . tileAdmin . statistics ; const numRemaining = numRequests - index ; const expectedNumActive = Math . min ( maxActiveTileTreePropsRequests , numRemaining ) ; expect ( stats . numActiveTileTreePropsRequests ) . to . equal ( expectedNumActive ) ; const expectedNumPending = numRemaining - expectedNumActive ; expect ( stats . numPendingTileTreePropsRequests ) . to . equal ( expectedNumPending ) ; } ; const promises = [ ] ; for ( let i = 1 ; i <= numRequests ; i ++ ) promises . push ( getProps ( i ) ) ; await Promise . all ( promises ) ; } ) ; it . skip ( "should reject when iModel closed" , async ( ) => { overrideRequestTileTreeProps ( async ( iModel , _treeId ) => { return new Promise ( ( resolve , _reject ) => { iModel . onClose . addOnce ( ( _ ) => { setTimeout ( resolve , 15 ) ; } ) ; } ) ; } ) ; const numRequests = 5 ; const promises = [ ] ; for ( let i = 0 ; i < numRequests ; i ++ ) promises . push ( IModelApp . tileAdmin . requestTileTreeProps ( imodel , i . toString ( ) ) . then ( ( _props ) => i ) . catch ( ( err ) => err ) ) ; await imodel . close ( ) ; const results = await Promise . all ( promises ) ; expect ( results . length ) . to . equal ( numRequests ) ; for ( let i = 0 ; i < numRequests ; i ++ ) { const result = results [ i ] ; if ( i < maxActiveTileTreePropsRequests ) { expect ( typeof result ) . to . equal ( "number" ) ; expect ( result ) . to . equal ( i ) ; } else { expect ( result ) . instanceof ( ServerTimeoutError ) ; } } overrideRequestTileTreeProps ( undefined ) ; imodel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; } ) ; it . skip ( "should fulfill requests for other iModels after a different iModel is closed" , async ( ) => { imodel2 = await SnapshotConnection . openFile ( "test.bim" ) ; overrideRequestTileTreeProps ( async ( iModel , treeId ) => { if ( iModel === imodel2 ) return { id : treeId } as IModelTileTreeProps ; return new Promise ( ( resolve , _reject ) => { iModel . onClose . addOnce ( ( _ ) => setTimeout ( resolve , 15 ) ) ; } ) ; } ) ; const promises = [ ] ; for ( let i = 0 ; i < 3 ; i ++ ) promises . push ( IModelApp . tileAdmin . requestTileTreeProps ( imodel , i . toString ( ) ) . then ( ( _props ) => i ) . catch ( ( err ) => err ) ) ; promises . push ( IModelApp . tileAdmin . requestTileTreeProps ( imodel , "imodel2" ) . then ( ( _props ) => "imodel2" ) . catch ( ( err ) => err ) ) ; await imodel . close ( ) ; const results = await Promise . all ( promises ) ; expect ( results . length ) . to . equal ( 4 ) ; expect ( typeof results [ 0 ] ) . to . equal ( "number" ) ; expect ( typeof results [ 1 ] ) . to . equal ( "number" ) ; expect ( results [ 2 ] ) . instanceof ( ServerTimeoutError ) ; expect ( results [ 3 ] ) . to . equal ( "imodel2" ) ; overrideRequestTileTreeProps ( undefined ) ; imodel = await SnapshotConnection . openFile ( "mirukuru.ibim" ) ; await imodel2 . close ( ) ; imodel2 = undefined ; } ) ; } ) ;