From 6f75bfc95929ff7341ebff84634a217ee5c56b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Legern=C3=A6s?= Date: Mon, 4 Sep 2023 13:45:26 +0200 Subject: [PATCH 1/5] sector buffering + indexset opt --- .../cad-geometry-loaders/src/CadManager.ts | 14 +++++++++++--- .../cad-model/src/utilities/GeometryBufferUtils.ts | 9 ++++++++- .../rendering/src/utilities/renderUtilities.ts | 8 ++++---- viewer/packages/utilities/src/indexset/IndexSet.ts | 14 ++++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/viewer/packages/cad-geometry-loaders/src/CadManager.ts b/viewer/packages/cad-geometry-loaders/src/CadManager.ts index cdf559fca3e..29ca1b886ea 100644 --- a/viewer/packages/cad-geometry-loaders/src/CadManager.ts +++ b/viewer/packages/cad-geometry-loaders/src/CadManager.ts @@ -4,7 +4,7 @@ import * as THREE from 'three'; -import { Subscription, Observable } from 'rxjs'; +import { Subscription, Observable, auditTime, buffer } from 'rxjs'; import { LevelOfDetail, ConsumedSector, CadModelMetadata } from '@reveal/cad-parsers'; import { CadModelUpdateHandler } from './CadModelUpdateHandler'; @@ -107,9 +107,17 @@ export class CadManager { this.updateTreeIndexToSectorsMap(cadModel, sector); }; + const consumeNextSectors = (sectors: ConsumedSector[]) => { + for (const sector of sectors) { + consumeNextSector(sector); + } + }; + + const consumedSectorsObservable = this._cadModelUpdateHandler.consumedSectorObservable(); + const flushAt = consumedSectorsObservable.pipe(auditTime(350)); this._subscription.add( - this._cadModelUpdateHandler.consumedSectorObservable().subscribe({ - next: consumeNextSector, + consumedSectorsObservable.pipe(buffer(flushAt)).subscribe({ + next: consumeNextSectors, error: error => { MetricsLogger.trackError(error, { moduleName: 'CadManager', diff --git a/viewer/packages/cad-model/src/utilities/GeometryBufferUtils.ts b/viewer/packages/cad-model/src/utilities/GeometryBufferUtils.ts index 1a12e64ae89..13933e06d45 100644 --- a/viewer/packages/cad-model/src/utilities/GeometryBufferUtils.ts +++ b/viewer/packages/cad-model/src/utilities/GeometryBufferUtils.ts @@ -4,7 +4,13 @@ import { TypedArray, TypedArrayConstructor } from '@reveal/utilities'; import assert from 'assert'; -import { BufferAttribute, InterleavedBufferAttribute, BufferGeometry, InstancedInterleavedBuffer } from 'three'; +import { + BufferAttribute, + InterleavedBufferAttribute, + BufferGeometry, + InstancedInterleavedBuffer, + DynamicDrawUsage +} from 'three'; export class GeometryBufferUtils { private static readonly TypedArrayViews = new Map([ @@ -51,6 +57,7 @@ export class GeometryBufferUtils { const ComponentType = GeometryBufferUtils.TypedArrayViews.get(componentSize)!; const interleavedAttributesBuffer = new InstancedInterleavedBuffer(new ComponentType(backingBuffer), stride); + interleavedAttributesBuffer.setUsage(DynamicDrawUsage); bufferGeometry.setAttribute( name, diff --git a/viewer/packages/rendering/src/utilities/renderUtilities.ts b/viewer/packages/rendering/src/utilities/renderUtilities.ts index 79b599c7c80..2dc0621dff5 100644 --- a/viewer/packages/rendering/src/utilities/renderUtilities.ts +++ b/viewer/packages/rendering/src/utilities/renderUtilities.ts @@ -265,14 +265,14 @@ export function setModelRenderLayers(rootNode: THREE.Object3D, stylingSets: Styl return; } - if (visible.hasIntersectionWith(objectTreeIndices)) { - if (back.hasIntersectionWith(objectTreeIndices)) { + if (visible.hasIntersectionWithMap(objectTreeIndices)) { + if (back.hasIntersectionWithMap(objectTreeIndices)) { node.layers.enable(RenderLayer.Back); } - if (ghost.hasIntersectionWith(objectTreeIndices)) { + if (ghost.hasIntersectionWithMap(objectTreeIndices)) { node.layers.enable(RenderLayer.Ghost); } - if (inFront.hasIntersectionWith(objectTreeIndices)) { + if (inFront.hasIntersectionWithMap(objectTreeIndices)) { node.layers.enable(RenderLayer.InFront); } } diff --git a/viewer/packages/utilities/src/indexset/IndexSet.ts b/viewer/packages/utilities/src/indexset/IndexSet.ts index d869a966784..242546719da 100644 --- a/viewer/packages/utilities/src/indexset/IndexSet.ts +++ b/viewer/packages/utilities/src/indexset/IndexSet.ts @@ -142,6 +142,20 @@ export class IndexSet { return this; } + hasIntersectionWithMap(otherMap: Map): boolean { + if (!this.rootNode) { + return false; + } + + for (const index of otherMap.keys()) { + if (this.rootNode.contains(index)) { + return true; + } + } + + return false; + } + hasIntersectionWith(otherSet: IndexSet | Map | Set): boolean { if (otherSet instanceof IndexSet) { if (this.rootNode === undefined || otherSet.rootNode === undefined) { From d497d482a9d296602803aef9aa9fd0f6a3e64969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Legern=C3=A6s?= Date: Mon, 4 Sep 2023 14:01:26 +0200 Subject: [PATCH 2/5] add progress reporting --- .../cad-geometry-loaders/src/CadManager.ts | 1 + .../src/CadModelUpdateHandler.ts | 11 +++++++--- .../src/sector/SectorLoader.ts | 22 +++++++++++-------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/viewer/packages/cad-geometry-loaders/src/CadManager.ts b/viewer/packages/cad-geometry-loaders/src/CadManager.ts index 29ca1b886ea..9abd946a3d7 100644 --- a/viewer/packages/cad-geometry-loaders/src/CadManager.ts +++ b/viewer/packages/cad-geometry-loaders/src/CadManager.ts @@ -111,6 +111,7 @@ export class CadManager { for (const sector of sectors) { consumeNextSector(sector); } + this._cadModelUpdateHandler.reportNewSectorsLoaded(sectors.length); }; const consumedSectorsObservable = this._cadModelUpdateHandler.consumedSectorObservable(); diff --git a/viewer/packages/cad-geometry-loaders/src/CadModelUpdateHandler.ts b/viewer/packages/cad-geometry-loaders/src/CadModelUpdateHandler.ts index c5effdb74c5..87ac46651d5 100644 --- a/viewer/packages/cad-geometry-loaders/src/CadModelUpdateHandler.ts +++ b/viewer/packages/cad-geometry-loaders/src/CadModelUpdateHandler.ts @@ -38,6 +38,7 @@ export class CadModelUpdateHandler { private readonly _modelStateHandler: ModelStateHandler; private _budget: CadModelBudget; private _lastSpent: SectorLoadingSpent; + private readonly _determineSectorsHandler: SectorLoader; private readonly _cameraSubject: Subject = new Subject(); private readonly _clippingPlaneSubject: Subject = new Subject(); @@ -106,7 +107,7 @@ export class CadModelUpdateHandler { }; this._progressSubject.next(state); }; - const determineSectorsHandler = new SectorLoader( + this._determineSectorsHandler = new SectorLoader( sectorCuller, this._modelStateHandler, collectStatisticsCallback, @@ -114,7 +115,7 @@ export class CadModelUpdateHandler { continuousModelStreaming ); - async function* loadSectors(input: DetermineSectorsPayload) { + async function* loadSectors(input: DetermineSectorsPayload, determineSectorsHandler: SectorLoader) { for await (const sector of determineSectorsHandler.loadSectors(input)) { yield sector; } @@ -124,7 +125,7 @@ export class CadModelUpdateHandler { observeOn(asyncScheduler), // Schedule tasks on macro task queue (setInterval) map(createDetermineSectorsInput), // Map from array to interface (enables destructuring) filter(loadingEnabled), // should we load? - mergeMap(async x => loadSectors(x)), + mergeMap(async x => loadSectors(x, this._determineSectorsHandler)), mergeMap(x => x) ); } @@ -182,6 +183,10 @@ export class CadModelUpdateHandler { return this._progressSubject; } + reportNewSectorsLoaded(loadedCountChange: number): void { + this._determineSectorsHandler.reportNewSectorsLoaded(loadedCountChange); + } + /** * When loading hints of a CAD model changes, propagate the event down to the stream and either add or remove * the {@link CadModelMetadata} from the array and push the new array down stream diff --git a/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts b/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts index f2dc1379155..fad2c0aa9a0 100644 --- a/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts +++ b/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts @@ -31,7 +31,7 @@ const SectorLoadingBatchSize = 20; */ export class SectorLoader { private readonly _modelStateHandler: ModelStateHandler; - private readonly _progressCallback: (sectorsLoaded: number, sectorsScheduled: number, sectorsCulled: number) => void; + private readonly _progressHelper: ProgressReportHelper; private readonly _collectStatisticsCallback: (spent: SectorLoadingSpent) => void; private readonly _sectorCuller: SectorCuller; private readonly _continuousModelStreaming: boolean; @@ -52,7 +52,7 @@ export class SectorLoader { this._modelStateHandler = modelStateHandler; this._collectStatisticsCallback = collectStatisticsCallback; - this._progressCallback = progressCallback; + this._progressHelper = new ProgressReportHelper(progressCallback); this._continuousModelStreaming = continuousModelStreaming; } @@ -86,14 +86,13 @@ export class SectorLoader { hasSectorChanged(sector.modelIdentifier, sector.metadata.id, sector.levelOfDetail) ); - const progressHelper = new ProgressReportHelper(this._progressCallback); - progressHelper.start(changedSectors.length); + this._progressHelper.reset(changedSectors.length); this._batchId++; const currentBatchId = this._batchId; for (const batch of chunk(changedSectors, SectorLoadingBatchSize)) { - const filteredSectors = await this.filterSectors(sectorCullerInput, batch, sectorCuller, progressHelper); + const filteredSectors = await this.filterSectors(sectorCullerInput, batch, sectorCuller, this._progressHelper); const consumedPromises = this.startLoadingBatch(filteredSectors, cadModels); for await (const consumed of PromiseUtils.raceUntilAllCompleted(consumedPromises)) { const resolvedSector = consumed.result; @@ -103,13 +102,18 @@ export class SectorLoader { resolvedSector.metadata.id, resolvedSector.levelOfDetail ); - yield resolvedSector; + yield resolvedSector; // progress will be reported when sector is loaded by CadManager + } else { + this._progressHelper.reportNewSectorsLoaded(1); } - progressHelper.reportNewSectorsLoaded(1); } } } + reportNewSectorsLoaded(loadedCountChange: number): void { + this._progressHelper.reportNewSectorsLoaded(loadedCountChange); + } + private shouldLoad(input: DetermineSectorsPayload) { if (input.models.length == 0) { return false; @@ -156,8 +160,8 @@ class ProgressReportHelper { this._progressCallback = reportCb; } - start(sectorsScheduled: number) { - this._sectorsScheduled = sectorsScheduled; + reset(sectorsScheduledChange: number) { + this._sectorsScheduled += sectorsScheduledChange - this._sectorsLoaded; this._sectorsLoaded = 0; this._sectorsCulled = 0; this.triggerCallback(); From 9fa8727683d6eb74ab5818cc2c3a7471dd403481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Legern=C3=A6s?= Date: Mon, 4 Sep 2023 21:21:24 +0200 Subject: [PATCH 3/5] update IndexSet tests --- .../utilities/src/indexset/IndexSet.test.ts | 16 ++++++++ .../utilities/src/indexset/IndexSet.ts | 38 ++++++++----------- viewer/reveal.api.md | 4 +- 3 files changed, 34 insertions(+), 24 deletions(-) diff --git a/viewer/packages/utilities/src/indexset/IndexSet.test.ts b/viewer/packages/utilities/src/indexset/IndexSet.test.ts index 4c86807b89a..740af762d71 100644 --- a/viewer/packages/utilities/src/indexset/IndexSet.test.ts +++ b/viewer/packages/utilities/src/indexset/IndexSet.test.ts @@ -141,6 +141,22 @@ describe('IndexSet', () => { expect(set1.hasIntersectionWith(set2)).toBeFalse(); }); + test('hasIntersectionWithMap returns true if there is any overlap', () => { + const set = new IndexSet(); + set.addRange(new NumericRange(1, 5)); + const map = new Map(); + map.set(3, 0); + expect(set.hasIntersectionWithMap(map)).toBeTrue(); + }); + + test('hasIntersectionWithMap returns false if there is no overlap', () => { + const set = new IndexSet(); + set.addRange(new NumericRange(1, 5)); + const map = new Map(); + map.set(6, 0); + expect(set.hasIntersectionWithMap(map)).toBeFalse(); + }); + test('differenceWith removes overlapping elements', () => { const set1 = new IndexSet(); set1.addRange(new NumericRange(1, 5)); diff --git a/viewer/packages/utilities/src/indexset/IndexSet.ts b/viewer/packages/utilities/src/indexset/IndexSet.ts index 242546719da..fef665c23f2 100644 --- a/viewer/packages/utilities/src/indexset/IndexSet.ts +++ b/viewer/packages/utilities/src/indexset/IndexSet.ts @@ -142,35 +142,13 @@ export class IndexSet { return this; } - hasIntersectionWithMap(otherMap: Map): boolean { - if (!this.rootNode) { - return false; - } - - for (const index of otherMap.keys()) { - if (this.rootNode.contains(index)) { - return true; - } - } - - return false; - } - - hasIntersectionWith(otherSet: IndexSet | Map | Set): boolean { + hasIntersectionWith(otherSet: IndexSet | Set): boolean { if (otherSet instanceof IndexSet) { if (this.rootNode === undefined || otherSet.rootNode === undefined) { return false; } return this.rootNode.hasIntersectionWith(otherSet.rootNode); - } else if (otherSet instanceof Map) { - for (const index of otherSet.keys()) { - if (this.contains(index)) { - return true; - } - } - - return false; } else { for (const index of otherSet) { if (this.contains(index)) { @@ -182,6 +160,20 @@ export class IndexSet { } } + hasIntersectionWithMap(otherMap: Map): boolean { + if (!this.rootNode) { + return false; + } + + for (const index of otherMap.keys()) { + if (this.rootNode.contains(index)) { + return true; + } + } + + return false; + } + intersectWith(otherSet: IndexSet): IndexSet { if (this.rootNode && otherSet.rootNode) { // Tackle endpoints diff --git a/viewer/reveal.api.md b/viewer/reveal.api.md index 23088e5fdec..cbbcc42b946 100644 --- a/viewer/reveal.api.md +++ b/viewer/reveal.api.md @@ -900,7 +900,9 @@ export class IndexSet { // (undocumented) forEachRange(visitor: (range: NumericRange) => void): void; // (undocumented) - hasIntersectionWith(otherSet: IndexSet | Map | Set): boolean; + hasIntersectionWith(otherSet: IndexSet | Set): boolean; + // (undocumented) + hasIntersectionWithMap(otherMap: Map): boolean; // (undocumented) intersectWith(otherSet: IndexSet): IndexSet; // (undocumented) From 686fbf205b51aca96caf8d35d65c1b74b596c586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Legern=C3=A6s?= Date: Tue, 3 Oct 2023 09:38:19 +0200 Subject: [PATCH 4/5] fix progress reporting when sector batch is discarded --- .../cad-geometry-loaders/src/sector/SectorLoader.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts b/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts index ac5d1608880..bd97d72476d 100644 --- a/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts +++ b/viewer/packages/cad-geometry-loaders/src/sector/SectorLoader.ts @@ -92,7 +92,11 @@ export class SectorLoader { const currentBatchId = this._batchId; for (const batch of chunk(changedSectors, SectorLoadingBatchSize)) { - if (currentBatchId !== this._batchId) break; // Stop processing this batch as a new batch has started, and will discard results from old batches. + if (currentBatchId !== this._batchId) { + // Stop processing this batch as a new batch has started, and will discard results from old batches. + this._progressHelper.reportNewSectorsLoaded(batch.length); + continue; + } const filteredSectors = await this.filterSectors(sectorCullerInput, batch, sectorCuller, this._progressHelper); const consumedPromises = this.startLoadingBatch(filteredSectors, cadModels); for await (const consumed of PromiseUtils.raceUntilAllCompleted(consumedPromises)) { From cecab98fccf2f47fe519f8e34d7e79fc95b16dcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eirik=20Legern=C3=A6s?= Date: Tue, 3 Oct 2023 10:27:38 +0200 Subject: [PATCH 5/5] sector buffer time variable --- viewer/packages/cad-geometry-loaders/src/CadManager.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/viewer/packages/cad-geometry-loaders/src/CadManager.ts b/viewer/packages/cad-geometry-loaders/src/CadManager.ts index 9abd946a3d7..174054d6c5d 100644 --- a/viewer/packages/cad-geometry-loaders/src/CadManager.ts +++ b/viewer/packages/cad-geometry-loaders/src/CadManager.ts @@ -35,6 +35,8 @@ export class CadManager { private readonly _markNeedsRedrawBound = this.markNeedsRedraw.bind(this); private readonly _materialsChangedListener = this.handleMaterialsChanged.bind(this); + private readonly _sectorBufferTime = 350; + get materialManager(): CadMaterialManager { return this._materialManager; } @@ -115,7 +117,7 @@ export class CadManager { }; const consumedSectorsObservable = this._cadModelUpdateHandler.consumedSectorObservable(); - const flushAt = consumedSectorsObservable.pipe(auditTime(350)); + const flushAt = consumedSectorsObservable.pipe(auditTime(this._sectorBufferTime)); this._subscription.add( consumedSectorsObservable.pipe(buffer(flushAt)).subscribe({ next: consumeNextSectors,