bruce-cesium 6.7.9 → 6.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/bruce-cesium.es5.js +749 -159
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +747 -157
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/menu-item-manager.js +75 -6
  7. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  8. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  9. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +1 -1
  10. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  11. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +151 -3
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +151 -3
  14. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  15. package/dist/lib/rendering/view-render-engine.js +8 -2
  16. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  17. package/dist/lib/rendering/visuals-register.js +360 -141
  18. package/dist/lib/rendering/visuals-register.js.map +1 -1
  19. package/dist/types/bruce-cesium.d.ts +1 -1
  20. package/dist/types/rendering/menu-item-manager.d.ts +6 -0
  21. package/dist/types/rendering/render-managers/render-manager.d.ts +30 -0
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +10 -1
  23. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +10 -1
  24. package/dist/types/rendering/visuals-register.d.ts +39 -3
  25. package/package.json +8 -2
@@ -1,6 +1,6 @@
1
1
  import * as Cesium from 'cesium';
2
- import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, DistanceDisplayCondition, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, HorizontalOrigin, VerticalOrigin, ConstantPositionProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, OrthographicFrustum, EasingFunction, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, BoundingSphere, GeometryInstance, PolygonPipeline, CesiumInspector, ClockRange, ScreenSpaceEventHandler, ScreenSpaceEventType, Intersect, CzmlDataSource, Fullscreen } from 'cesium';
3
- import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Api, Bounds, Geometry, Carto, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DataLab, DelayQueue, EntityHistoricData, AccountConcept, RecordChangeFeed, BruceApi, EntityRelation, ProgramKey, EntitySource, ProjectViewLegacyTile, ProjectViewTile, Camera, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, Session, EntityRelationType } from 'bruce-models';
2
+ import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, Math as Math$1, Cartesian3, JulianDate, Quaternion, Transforms, HeadingPitchRoll, Matrix4, DistanceDisplayCondition, HeightReference, ColorBlendMode, ShadowMode, ClassificationType, Model, HorizontalOrigin, VerticalOrigin, ConstantPositionProperty, PolygonHierarchy, PolylineGraphics, ArcType, CornerType, Cartesian2, SceneTransforms, NearFarScalar, Matrix3, Rectangle, KmlDataSource, GeoJsonDataSource, SceneMode, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, OrthographicFrustum, EasingFunction, Cesium3DTileset, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, PolygonPipeline, BoundingSphere, GeometryInstance, CesiumInspector, ClockRange, ScreenSpaceEventHandler, ScreenSpaceEventType, Intersect, CzmlDataSource, Fullscreen } from 'cesium';
3
+ import { Cartes, Entity as Entity$1, ENVIRONMENT, Calculator, ClientFile, EntityLod, EntityTag, EntityType, LRUCache, ObjectUtils, Style, Api, Bounds, Geometry, Carto, BruceEvent, EntityCoords, MenuItem, Tileset, ProjectView, ProjectViewBookmark, ZoomControl, DataLab, DelayQueue, EntityHistoricData, AccountConcept, RecordChangeFeed, BruceApi, EntityRelation, ProgramKey, EntitySource, ProjectViewLegacyTile, ProjectViewTile, Camera, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, Session, EntityRelationType } from 'bruce-models';
4
4
 
5
5
  /**
6
6
  * Ensures a number is returned from a given value.
@@ -6536,6 +6536,18 @@ var VisualsRegister;
6536
6536
  this.id = ObjectUtils.UId();
6537
6537
  this.disposed = false;
6538
6538
  this.rego = {};
6539
+ // Menu Item ID -> Set of Entity IDs registered under that menu item, for O(1) lookup.
6540
+ this.entityIdsByMenuItem = {};
6541
+ // Caches GetIsIsolatedAny(). `null` means "unknown, recompute".
6542
+ // Invalidated on any write that could affect isolation.
6543
+ this.isolatedAnyCache = null;
6544
+ // Lazy OverrideStates() bookkeeping. See cancelLazyOverride()/startLazyOverride().
6545
+ this.lazyOverrideToken = 0;
6546
+ this.lazyOverrideActive = false;
6547
+ // Entity ids a direct (non-lazy) write touched while a lazy apply was in-flight-
6548
+ // skipped by the applier so a live user action always wins.
6549
+ this.lazyOverrideExclusions = new Set();
6550
+ this.lazyOverrideInterval = null;
6539
6551
  this.onUpdate = null;
6540
6552
  // Entity ID -> Menu Item ID -> State.
6541
6553
  // When no Menu Item ID is set, then the second key is a blank string.
@@ -6543,6 +6555,8 @@ var VisualsRegister;
6543
6555
  this.states = {};
6544
6556
  // Queue of Entity updates to process in batches to avoid overloading the render loop.
6545
6557
  this.updateQueue = [];
6558
+ // Mirrors updateQueue for O(1) "already queued" checks.
6559
+ this.updateQueueIds = new Set();
6546
6560
  // Settings related to those Entities in the queue.
6547
6561
  // This lets us modify the update without re-adding the item to the queue if it's already in it.
6548
6562
  this.updateQueueSettings = {};
@@ -6553,6 +6567,42 @@ var VisualsRegister;
6553
6567
  register: this
6554
6568
  });
6555
6569
  }
6570
+ indexMenuItemEntity(menuItemId, entityId) {
6571
+ if (!menuItemId) {
6572
+ return;
6573
+ }
6574
+ let set = this.entityIdsByMenuItem[menuItemId];
6575
+ if (!set) {
6576
+ set = this.entityIdsByMenuItem[menuItemId] = new Set();
6577
+ }
6578
+ set.add(entityId);
6579
+ }
6580
+ // Removes entityId from the menu item index only if it no longer has any rego
6581
+ // registered under that menu item. Safe to call speculatively.
6582
+ unindexMenuItemEntityIfAbsent(menuItemId, entityId) {
6583
+ var _a;
6584
+ if (!menuItemId) {
6585
+ return;
6586
+ }
6587
+ const set = this.entityIdsByMenuItem[menuItemId];
6588
+ if (!set) {
6589
+ return;
6590
+ }
6591
+ const stillPresent = (_a = this.rego[entityId]) === null || _a === void 0 ? void 0 : _a.some(r => r.menuItemId === menuItemId);
6592
+ if (!stillPresent) {
6593
+ set.delete(entityId);
6594
+ if (!set.size) {
6595
+ delete this.entityIdsByMenuItem[menuItemId];
6596
+ }
6597
+ }
6598
+ }
6599
+ // Records that `entityId` was just written to directly, outside of any lazy
6600
+ // OverrideStates() apply. No-op unless a lazy apply is currently in-flight.
6601
+ markLazyExclusion(entityId) {
6602
+ if (this.lazyOverrideActive) {
6603
+ this.lazyOverrideExclusions.add(entityId);
6604
+ }
6605
+ }
6556
6606
  queueUpdate(update) {
6557
6607
  if (this.Disposed) {
6558
6608
  return;
@@ -6585,7 +6635,8 @@ var VisualsRegister;
6585
6635
  }
6586
6636
  this.updateQueueSettings[update.entityId] = settings;
6587
6637
  // Queue ID if it's not already in the queue.
6588
- if (!this.updateQueue.includes(update.entityId)) {
6638
+ if (!this.updateQueueIds.has(update.entityId)) {
6639
+ this.updateQueueIds.add(update.entityId);
6589
6640
  this.updateQueue.push(update.entityId);
6590
6641
  this.pingUpdateQueue();
6591
6642
  }
@@ -6606,6 +6657,7 @@ var VisualsRegister;
6606
6657
  const cache = {};
6607
6658
  for (let i = 0; i < batch.length; i++) {
6608
6659
  const entityId = batch[i];
6660
+ this.updateQueueIds.delete(entityId);
6609
6661
  // Check if still registered.
6610
6662
  if (entityId && (!this.rego[entityId] || !this.rego[entityId].length)) {
6611
6663
  continue;
@@ -6718,6 +6770,8 @@ var VisualsRegister;
6718
6770
  SetStates(states, source) {
6719
6771
  // Array of changed Entity IDs so we know what to refresh/notify at the end.
6720
6772
  const entityIds = [];
6773
+ // Mirrors entityIds for O(1) "already recorded" checks.
6774
+ const entityIdsSeen = new Set();
6721
6775
  // If we need to update all Entities.
6722
6776
  // This is usually for isolation changes.
6723
6777
  let updateAll = false;
@@ -6750,8 +6804,9 @@ var VisualsRegister;
6750
6804
  };
6751
6805
  const doUpdate = (state) => {
6752
6806
  const eChanged = this.SetState(state, false, source);
6753
- if (eChanged && !entityIds.includes(state.entityId)) {
6807
+ if (eChanged && !entityIdsSeen.has(state.entityId)) {
6754
6808
  updateRefreshMarkers(state);
6809
+ entityIdsSeen.add(state.entityId);
6755
6810
  entityIds.push(state.entityId);
6756
6811
  changed = true;
6757
6812
  }
@@ -6786,7 +6841,7 @@ var VisualsRegister;
6786
6841
  for (let i = 0; i < keys.length; i++) {
6787
6842
  const key = keys[i];
6788
6843
  const state = states[key];
6789
- if (entityIds.includes(state.entityId)) {
6844
+ if (entityIdsSeen.has(state.entityId)) {
6790
6845
  const update = {
6791
6846
  type: EVisualUpdateType.Update,
6792
6847
  entityId: state.entityId
@@ -6816,12 +6871,18 @@ var VisualsRegister;
6816
6871
  * Sets the provided settings for a given Entity's state.
6817
6872
  * @param params
6818
6873
  */
6819
- setStateValues(values) {
6874
+ setStateValues(values, fromLazyApplier = false) {
6820
6875
  const { entityId, menuItemId } = values;
6821
6876
  const keys = Object.keys(values).filter(k => k !== "entityId" && k !== "menuItemId");
6822
6877
  if (!keys.length) {
6823
6878
  return false;
6824
6879
  }
6880
+ if (!fromLazyApplier) {
6881
+ this.markLazyExclusion(entityId);
6882
+ }
6883
+ if (keys.includes("isolated")) {
6884
+ this.isolatedAnyCache = null;
6885
+ }
6825
6886
  let changed = false;
6826
6887
  const eStates = this.states[entityId] || {};
6827
6888
  this.states[entityId] = eStates;
@@ -6992,18 +7053,68 @@ var VisualsRegister;
6992
7053
  }
6993
7054
  return false;
6994
7055
  }
7056
+ buildClearDiffItem(state) {
7057
+ const clearState = {
7058
+ entityId: state.entityId,
7059
+ menuItemId: state.menuItemId
7060
+ };
7061
+ const refresh = {};
7062
+ // For each property that exists, set it to null to clear it.
7063
+ // This will cause setStateValues to delete the property.
7064
+ if (state.highlighted != null) {
7065
+ clearState.highlighted = null;
7066
+ refresh.highlighted = true;
7067
+ }
7068
+ if (state.selected != null) {
7069
+ clearState.selected = null;
7070
+ refresh.selected = true;
7071
+ }
7072
+ if (state.opacity != null) {
7073
+ clearState.opacity = null;
7074
+ refresh.opacity = true;
7075
+ }
7076
+ if (state.labelled != null) {
7077
+ clearState.labelled = null;
7078
+ }
7079
+ if (state.hidden != null) {
7080
+ clearState.hidden = null;
7081
+ }
7082
+ if (state.isolated != null) {
7083
+ clearState.isolated = null;
7084
+ }
7085
+ return { clearState, refresh };
7086
+ }
7087
+ /**
7088
+ * Refresh flags needed for an "add"/"update" state - the new value is applied as-is,
7089
+ * this just says which visual properties changed.
7090
+ */
7091
+ buildSetDiffRefresh(state) {
7092
+ const refresh = {};
7093
+ if (state.hasOwnProperty("highlighted")) {
7094
+ refresh.highlighted = true;
7095
+ }
7096
+ if (state.hasOwnProperty("selected")) {
7097
+ refresh.selected = true;
7098
+ }
7099
+ if (state.hasOwnProperty("opacity")) {
7100
+ refresh.opacity = true;
7101
+ }
7102
+ return refresh;
7103
+ }
6995
7104
  /**
6996
7105
  * Applies a set of new states to override the existing ones using differential updates.
6997
- * This is more efficient than clearing and re-applying all states.
6998
- * @param states
6999
7106
  */
7000
- OverrideStates(states) {
7001
- // Calculate what needs to change.
7107
+ OverrideStates(states, options) {
7108
+ this.cancelLazyOverride();
7002
7109
  const diff = this.calculateStateDifference(states);
7003
7110
  // If nothing changed, exit early.
7004
7111
  if (diff.toAdd.length === 0 && diff.toRemove.length === 0 && diff.toUpdate.length === 0) {
7005
7112
  return;
7006
7113
  }
7114
+ if (options === null || options === void 0 ? void 0 : options.lazy) {
7115
+ this.startLazyOverride(diff);
7116
+ return;
7117
+ }
7007
7118
  // Track which visual properties need refreshing per entity.
7008
7119
  const refreshMap = new Map();
7009
7120
  const updateRefreshForEntity = (entityId, props) => {
@@ -7014,76 +7125,16 @@ var VisualsRegister;
7014
7125
  opacity: existing.opacity || props.opacity || false
7015
7126
  });
7016
7127
  };
7017
- // Process removals.
7018
7128
  for (const state of diff.toRemove) {
7019
- // Create a state with inverted/cleared values.
7020
- const clearState = {
7021
- entityId: state.entityId,
7022
- menuItemId: state.menuItemId
7023
- };
7024
- // For each property that exists, set it to null to clear it.
7025
- // This will cause setStateValues to delete the property.
7026
- if (state.highlighted != null) {
7027
- clearState.highlighted = null;
7028
- updateRefreshForEntity(state.entityId, {
7029
- highlighted: true
7030
- });
7031
- }
7032
- if (state.selected != null) {
7033
- clearState.selected = null;
7034
- updateRefreshForEntity(state.entityId, {
7035
- selected: true
7036
- });
7037
- }
7038
- if (state.opacity != null) {
7039
- clearState.opacity = null;
7040
- updateRefreshForEntity(state.entityId, {
7041
- opacity: true
7042
- });
7043
- }
7044
- if (state.labelled != null) {
7045
- clearState.labelled = null;
7046
- updateRefreshForEntity(state.entityId, {});
7047
- }
7048
- if (state.hidden != null) {
7049
- clearState.hidden = null;
7050
- updateRefreshForEntity(state.entityId, {});
7051
- }
7052
- if (state.isolated != null) {
7053
- clearState.isolated = null;
7054
- updateRefreshForEntity(state.entityId, {});
7055
- }
7129
+ const { clearState, refresh } = this.buildClearDiffItem(state);
7130
+ updateRefreshForEntity(state.entityId, refresh);
7056
7131
  this.setStateValues(clearState);
7057
7132
  }
7058
- // Process additions and updates.
7059
7133
  const statesToSet = [...diff.toAdd, ...diff.toUpdate];
7060
7134
  for (const state of statesToSet) {
7061
- // Determine what needs visual refresh based on what's changing.
7062
- if (state.hasOwnProperty("highlighted")) {
7063
- updateRefreshForEntity(state.entityId, {
7064
- highlighted: true
7065
- });
7066
- }
7067
- if (state.hasOwnProperty("selected")) {
7068
- updateRefreshForEntity(state.entityId, {
7069
- selected: true
7070
- });
7071
- }
7072
- if (state.hasOwnProperty("opacity")) {
7073
- updateRefreshForEntity(state.entityId, {
7074
- opacity: true
7075
- });
7076
- }
7077
- if (state.hasOwnProperty("labelled")) {
7078
- updateRefreshForEntity(state.entityId, {});
7079
- }
7080
- if (state.hasOwnProperty("hidden") || state.hasOwnProperty("isolated")) {
7081
- updateRefreshForEntity(state.entityId, {});
7082
- }
7135
+ updateRefreshForEntity(state.entityId, this.buildSetDiffRefresh(state));
7083
7136
  this.setStateValues(state);
7084
7137
  }
7085
- // Queue updates for all affected entities.
7086
- // TODO: if a massive list we should async batch this.
7087
7138
  for (const entityId of diff.affectedEntityIds) {
7088
7139
  const refresh = refreshMap.get(entityId);
7089
7140
  this.queueUpdate({
@@ -7093,6 +7144,77 @@ var VisualsRegister;
7093
7144
  }
7094
7145
  this.viewer.scene.requestRender();
7095
7146
  }
7147
+ /**
7148
+ * Cancels any in-flight lazy OverrideStates() apply.
7149
+ */
7150
+ cancelLazyOverride() {
7151
+ if (!this.lazyOverrideActive) {
7152
+ return;
7153
+ }
7154
+ this.lazyOverrideToken++;
7155
+ this.lazyOverrideActive = false;
7156
+ this.lazyOverrideExclusions.clear();
7157
+ if (this.lazyOverrideInterval) {
7158
+ clearInterval(this.lazyOverrideInterval);
7159
+ this.lazyOverrideInterval = null;
7160
+ }
7161
+ }
7162
+ /**
7163
+ * Starts (or restarts) a chunked, cancellable apply of a previously-computed diff.
7164
+ * See OverrideStates() for the cancellation/priority semantics.
7165
+ */
7166
+ startLazyOverride(diff) {
7167
+ const BATCH_SIZE = 2000;
7168
+ const queue = [];
7169
+ for (const state of diff.toRemove) {
7170
+ queue.push({ state, isRemoval: true });
7171
+ }
7172
+ for (const state of diff.toAdd) {
7173
+ queue.push({ state, isRemoval: false });
7174
+ }
7175
+ for (const state of diff.toUpdate) {
7176
+ queue.push({ state, isRemoval: false });
7177
+ }
7178
+ this.lazyOverrideToken++;
7179
+ const token = this.lazyOverrideToken;
7180
+ this.lazyOverrideActive = true;
7181
+ this.lazyOverrideExclusions.clear();
7182
+ let index = 0;
7183
+ this.lazyOverrideInterval = setInterval(() => {
7184
+ // A newer lazy call (or a sync OverrideStates()) superseded this run.
7185
+ if (token !== this.lazyOverrideToken || this.Disposed) {
7186
+ clearInterval(this.lazyOverrideInterval);
7187
+ this.lazyOverrideInterval = null;
7188
+ return;
7189
+ }
7190
+ const end = Math.min(index + BATCH_SIZE, queue.length);
7191
+ for (; index < end; index++) {
7192
+ const { state, isRemoval } = queue[index];
7193
+ // A direct write already claimed this entity, the bookmark's still-pending
7194
+ // value for it is dropped for the rest of this run.
7195
+ if (this.lazyOverrideExclusions.has(state.entityId)) {
7196
+ continue;
7197
+ }
7198
+ if (isRemoval) {
7199
+ const { clearState, refresh } = this.buildClearDiffItem(state);
7200
+ this.setStateValues(clearState, true);
7201
+ this.queueUpdate({ entityId: state.entityId, refresh });
7202
+ }
7203
+ else {
7204
+ const refresh = this.buildSetDiffRefresh(state);
7205
+ this.setStateValues(state, true);
7206
+ this.queueUpdate({ entityId: state.entityId, refresh });
7207
+ }
7208
+ }
7209
+ this.viewer.scene.requestRender();
7210
+ if (index >= queue.length) {
7211
+ clearInterval(this.lazyOverrideInterval);
7212
+ this.lazyOverrideInterval = null;
7213
+ this.lazyOverrideActive = false;
7214
+ this.lazyOverrideExclusions.clear();
7215
+ }
7216
+ }, 10);
7217
+ }
7096
7218
  /**
7097
7219
  * Returns all states with the first detected Menu Item ID settings included.
7098
7220
  * This is a utility function for returning settings for apps that don't support the newer state settings.
@@ -7108,9 +7230,8 @@ var VisualsRegister;
7108
7230
  if (!eStates) {
7109
7231
  continue;
7110
7232
  }
7111
- if (!entityIds.includes(entityId)) {
7112
- entityIds.push(entityId);
7113
- }
7233
+ // Keys of this.states are already unique, no dedupe check needed.
7234
+ entityIds.push(entityId);
7114
7235
  const mKeys = Object.keys(eStates);
7115
7236
  for (let i = 0; i < mKeys.length; i++) {
7116
7237
  const menuItemId = mKeys[i];
@@ -7176,6 +7297,9 @@ var VisualsRegister;
7176
7297
  this.cameraCullerDispose = null;
7177
7298
  clearInterval(this.updateQueueInterval);
7178
7299
  this.updateQueueInterval = null;
7300
+ clearInterval(this.lazyOverrideInterval);
7301
+ this.lazyOverrideInterval = null;
7302
+ this.lazyOverrideActive = false;
7179
7303
  this.disposed = true;
7180
7304
  }
7181
7305
  ForceUpdate(params) {
@@ -7245,12 +7369,15 @@ var VisualsRegister;
7245
7369
  */
7246
7370
  ClearLabelled(params) {
7247
7371
  const toUpdateIds = [];
7372
+ const toUpdateIdsSeen = new Set();
7248
7373
  this.ForEachState((state) => {
7249
7374
  if (state.labelled == null || state.labelled === undefined) {
7250
7375
  return;
7251
7376
  }
7377
+ this.markLazyExclusion(state.entityId);
7252
7378
  delete state.labelled;
7253
- if (!toUpdateIds.includes(state.entityId)) {
7379
+ if (!toUpdateIdsSeen.has(state.entityId)) {
7380
+ toUpdateIdsSeen.add(state.entityId);
7254
7381
  toUpdateIds.push(state.entityId);
7255
7382
  }
7256
7383
  });
@@ -7378,11 +7505,12 @@ var VisualsRegister;
7378
7505
  }
7379
7506
  ClearSelected(params) {
7380
7507
  var _a;
7381
- const cleared = [];
7508
+ const cleared = new Set();
7382
7509
  this.ForEachState((state) => {
7383
7510
  if (state.selected == null || state.selected === undefined) {
7384
7511
  return;
7385
7512
  }
7513
+ this.markLazyExclusion(state.entityId);
7386
7514
  delete state.selected;
7387
7515
  const regos = this.GetRegos({
7388
7516
  entityId: state.entityId,
@@ -7390,13 +7518,13 @@ var VisualsRegister;
7390
7518
  });
7391
7519
  for (let i = 0; i < regos.length; i++) {
7392
7520
  const rego = regos[i];
7393
- if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.includes(rego)) {
7521
+ if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.has(rego)) {
7394
7522
  CesiumEntityStyler.Deselect({
7395
7523
  entity: rego.visual,
7396
7524
  viewer: this.viewer,
7397
7525
  requestRender: false
7398
7526
  });
7399
- cleared.push(rego);
7527
+ cleared.add(rego);
7400
7528
  }
7401
7529
  }
7402
7530
  });
@@ -7509,10 +7637,13 @@ var VisualsRegister;
7509
7637
  var _a;
7510
7638
  // Null all states.
7511
7639
  let entityIds = [];
7640
+ const entityIdsSeen = new Set();
7512
7641
  this.ForEachState((state) => {
7513
7642
  if (state.highlighted) {
7643
+ this.markLazyExclusion(state.entityId);
7514
7644
  delete state.highlighted;
7515
- if (!entityIds.includes(state.entityId)) {
7645
+ if (!entityIdsSeen.has(state.entityId)) {
7646
+ entityIdsSeen.add(state.entityId);
7516
7647
  entityIds.push(state.entityId);
7517
7648
  }
7518
7649
  }
@@ -7601,7 +7732,10 @@ var VisualsRegister;
7601
7732
  return state.isolated === true;
7602
7733
  }
7603
7734
  GetIsIsolatedAny() {
7604
- return Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
7735
+ if (this.isolatedAnyCache == null) {
7736
+ this.isolatedAnyCache = Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
7737
+ }
7738
+ return this.isolatedAnyCache;
7605
7739
  }
7606
7740
  /**
7607
7741
  * @deprecated Use GetStates() or GetState().
@@ -7622,10 +7756,15 @@ var VisualsRegister;
7622
7756
  this.ForEachState((state) => {
7623
7757
  if (state.isolated) {
7624
7758
  changed = true;
7759
+ this.markLazyExclusion(state.entityId);
7625
7760
  }
7626
7761
  delete state.isolated;
7627
7762
  });
7628
- if (changed && params.doUpdate !== false) {
7763
+ if (changed) {
7764
+ // Mutated `isolated` directly rather than through setStateValues().
7765
+ this.isolatedAnyCache = false;
7766
+ }
7767
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
7629
7768
  this.updateAllEntities({
7630
7769
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
7631
7770
  refresh: true
@@ -7676,10 +7815,11 @@ var VisualsRegister;
7676
7815
  this.ForEachState((state) => {
7677
7816
  if (state.hidden) {
7678
7817
  changed = true;
7818
+ this.markLazyExclusion(state.entityId);
7679
7819
  }
7680
7820
  delete state.hidden;
7681
7821
  });
7682
- if (changed && params.doUpdate !== false) {
7822
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
7683
7823
  this.updateAllEntities({
7684
7824
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
7685
7825
  refresh: true
@@ -7715,6 +7855,7 @@ var VisualsRegister;
7715
7855
  const entityRegos = (_a = this.rego[entityId]) !== null && _a !== void 0 ? _a : [];
7716
7856
  entityRegos.push(rego);
7717
7857
  this.rego[entityId] = entityRegos;
7858
+ this.indexMenuItemEntity(rego.menuItemId, entityId);
7718
7859
  // Mark the visual as part of this register so selection works.
7719
7860
  markEntity(this, rego, rego.visual, false);
7720
7861
  // Run any updates on the visual based on the calculated state.
@@ -7738,6 +7879,74 @@ var VisualsRegister;
7738
7879
  this.viewer.scene.requestRender();
7739
7880
  }
7740
7881
  }
7882
+ /**
7883
+ * Re-parents every rego registered under one menu item id to a different menu item id.
7884
+ */
7885
+ ReassignMenuItem(params) {
7886
+ var _a, _b;
7887
+ const { fromMenuItemId, toMenuItemId, toMenuItemType, toPriority, requestRender, source } = params;
7888
+ if (!fromMenuItemId || !toMenuItemId || fromMenuItemId === toMenuItemId) {
7889
+ return;
7890
+ }
7891
+ const entityIds = this.entityIdsByMenuItem[fromMenuItemId];
7892
+ if (!entityIds) {
7893
+ return;
7894
+ }
7895
+ // Snapshot: unindexMenuItemEntityIfAbsent() below mutates this same set.
7896
+ for (const entityId of Array.from(entityIds)) {
7897
+ const regos = this.rego[entityId];
7898
+ if (!regos) {
7899
+ continue;
7900
+ }
7901
+ for (const rego of regos) {
7902
+ if (rego.menuItemId !== fromMenuItemId) {
7903
+ continue;
7904
+ }
7905
+ if (source !== EUpdateSource.TREE_CASCADE) {
7906
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger({
7907
+ type: EVisualUpdateType.Remove,
7908
+ entityId,
7909
+ rego: { ...rego, menuItemId: fromMenuItemId },
7910
+ source
7911
+ });
7912
+ }
7913
+ rego.menuItemId = toMenuItemId;
7914
+ rego.menuItemType = toMenuItemType;
7915
+ if (toPriority != null) {
7916
+ rego.priority = toPriority;
7917
+ }
7918
+ if (source !== EUpdateSource.TREE_CASCADE) {
7919
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({
7920
+ type: EVisualUpdateType.Add,
7921
+ entityId,
7922
+ rego,
7923
+ source
7924
+ });
7925
+ }
7926
+ }
7927
+ const eStates = this.states[entityId];
7928
+ const movingState = eStates === null || eStates === void 0 ? void 0 : eStates[fromMenuItemId];
7929
+ if (movingState) {
7930
+ delete eStates[fromMenuItemId];
7931
+ const existingTargetState = eStates[toMenuItemId];
7932
+ eStates[toMenuItemId] = {
7933
+ ...existingTargetState,
7934
+ ...movingState,
7935
+ entityId,
7936
+ menuItemId: toMenuItemId
7937
+ };
7938
+ }
7939
+ this.unindexMenuItemEntityIfAbsent(fromMenuItemId, entityId);
7940
+ this.indexMenuItemEntity(toMenuItemId, entityId);
7941
+ this.queueUpdate({
7942
+ entityId,
7943
+ refresh: true
7944
+ });
7945
+ }
7946
+ if (requestRender != false) {
7947
+ this.viewer.scene.requestRender();
7948
+ }
7949
+ }
7741
7950
  /**
7742
7951
  * Locates a visual corresponding to a given entity id.
7743
7952
  * If no menu item id is provided, then it will pick the "best" one.
@@ -7848,7 +8057,10 @@ var VisualsRegister;
7848
8057
  // TODO: refactor.
7849
8058
  // Currently this was made by merging two functions.
7850
8059
  if (menuItemId && !entityId) {
7851
- for (const entityId in this.rego) {
8060
+ // Entities registered under this menu item, keyed via entityIdsByMenuItem.
8061
+ // Snapshot to an array since removals below mutate the backing set.
8062
+ const entityIds = this.entityIdsByMenuItem[menuItemId] ? Array.from(this.entityIdsByMenuItem[menuItemId]) : [];
8063
+ for (const entityId of entityIds) {
7852
8064
  const entityRegos = this.rego[entityId];
7853
8065
  if (entityRegos) {
7854
8066
  const rego = entityRegos.find(r => r.menuItemId === menuItemId);
@@ -7870,6 +8082,7 @@ var VisualsRegister;
7870
8082
  const doesInclude = this.rego[entityId].find(r => r.menuItemId === menuItemId);
7871
8083
  if (doesInclude) {
7872
8084
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8085
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
7873
8086
  }
7874
8087
  const update = {
7875
8088
  type: EVisualUpdateType.Remove,
@@ -7922,6 +8135,7 @@ var VisualsRegister;
7922
8135
  (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger(update);
7923
8136
  }
7924
8137
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8138
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
7925
8139
  if (doUpdate && menuItemId) {
7926
8140
  this.queueUpdate({
7927
8141
  entityId: entityId,
@@ -7944,7 +8158,11 @@ var VisualsRegister;
7944
8158
  });
7945
8159
  removeEntity(this.viewer, rego.visual);
7946
8160
  rego.visual = null;
7947
- this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8161
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
8162
+ if (!this.rego[entityId].length) {
8163
+ delete this.rego[entityId];
8164
+ }
8165
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
7948
8166
  const update = {
7949
8167
  type: EVisualUpdateType.Remove,
7950
8168
  entityId: rego.entityId,
@@ -7956,7 +8174,7 @@ var VisualsRegister;
7956
8174
  if (source !== EUpdateSource.TREE_CASCADE) {
7957
8175
  (_c = this.onUpdate) === null || _c === void 0 ? void 0 : _c.Trigger(update);
7958
8176
  }
7959
- if (doUpdate && menuItemId) {
8177
+ if (doUpdate) {
7960
8178
  this.queueUpdate({
7961
8179
  entityId: entityId,
7962
8180
  refresh: false
@@ -7973,68 +8191,68 @@ var VisualsRegister;
7973
8191
  * @param params
7974
8192
  */
7975
8193
  RemoveRegosByVisuals(params) {
7976
- var _a, _b, _c;
8194
+ var _a, _b;
7977
8195
  const { doRemove = true, requestRender = true, source, doUpdate, menuItemId } = params;
7978
- const entityIds = Object.keys(this.rego);
7979
- const removedEntityIds = [];
7980
- for (const entityId of entityIds) {
7981
- const regos = this.rego[entityId];
7982
- if (regos && regos.length) {
7983
- for (const rego of regos) {
7984
- if (params.visuals.includes(rego.visual)) {
7985
- EntityLabel.Detatch({
7986
- rego
7987
- });
7988
- if (doRemove != false) {
7989
- removeEntity(this.viewer, rego.visual);
7990
- }
7991
- rego.visual = null;
7992
- this.rego[entityId] = regos.filter(r => r.visual !== rego.visual);
7993
- const update = {
7994
- type: EVisualUpdateType.Remove,
7995
- entityId: rego.entityId,
7996
- rego: rego
7997
- };
7998
- if (source) {
7999
- update.source = source;
8000
- }
8001
- if (source !== EUpdateSource.TREE_CASCADE) {
8002
- (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
8003
- }
8004
- if (doUpdate && menuItemId) {
8005
- this.queueUpdate({
8006
- entityId: entityId,
8007
- refresh: false
8008
- });
8009
- }
8010
- if (!removedEntityIds.includes(rego.entityId)) {
8011
- removedEntityIds.push(rego.entityId);
8012
- }
8013
- }
8014
- // Check siblings.
8015
- else if ((_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"]) {
8016
- let siblings = rego.visual["_siblingGraphics"];
8017
- if (doRemove != false) {
8018
- for (const sibling of siblings) {
8019
- if (params.visuals.includes(sibling)) {
8020
- removeEntity(this.viewer, sibling);
8021
- }
8022
- }
8023
- }
8024
- siblings = siblings.filter(s => !params.visuals.includes(s));
8025
- rego.visual["_siblingGraphics"] = siblings;
8026
- }
8196
+ const removedEntityIds = new Set();
8197
+ for (const visual of params.visuals) {
8198
+ const visExt = visual;
8199
+ const rego = (visExt === null || visExt === void 0 ? void 0 : visExt._register) === this ? visExt._rego : null;
8200
+ if (!rego || !rego.visual) {
8201
+ continue;
8202
+ }
8203
+ const entityId = rego.entityId;
8204
+ const entityRegos = this.rego[entityId];
8205
+ if (!entityRegos) {
8206
+ continue;
8207
+ }
8208
+ if (rego.visual === visual) {
8209
+ // The primary graphic for this rego - remove the rego entirely.
8210
+ EntityLabel.Detatch({
8211
+ rego
8212
+ });
8213
+ if (doRemove != false) {
8214
+ removeEntity(this.viewer, rego.visual);
8215
+ }
8216
+ rego.visual = null;
8217
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
8218
+ if (!this.rego[entityId].length) {
8219
+ delete this.rego[entityId];
8220
+ }
8221
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
8222
+ const update = {
8223
+ type: EVisualUpdateType.Remove,
8224
+ entityId: rego.entityId,
8225
+ rego: rego
8226
+ };
8227
+ if (source) {
8228
+ update.source = source;
8229
+ }
8230
+ if (source !== EUpdateSource.TREE_CASCADE) {
8231
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
8232
+ }
8233
+ if (doUpdate && menuItemId) {
8234
+ this.queueUpdate({
8235
+ entityId: entityId,
8236
+ refresh: false
8237
+ });
8027
8238
  }
8239
+ removedEntityIds.add(entityId);
8028
8240
  }
8029
- if (!((_c = this.rego[entityId]) === null || _c === void 0 ? void 0 : _c.length)) {
8030
- delete this.rego[entityId];
8241
+ else {
8242
+ const siblings = (_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"];
8243
+ if (siblings === null || siblings === void 0 ? void 0 : siblings.length) {
8244
+ if (doRemove != false && siblings.includes(visual)) {
8245
+ removeEntity(this.viewer, visual);
8246
+ }
8247
+ rego.visual["_siblingGraphics"] = siblings.filter(s => s !== visual);
8248
+ }
8031
8249
  }
8032
8250
  }
8033
8251
  if (requestRender) {
8034
8252
  this.viewer.scene.requestRender();
8035
8253
  }
8036
8254
  return {
8037
- removedEntityIds
8255
+ removedEntityIds: Array.from(removedEntityIds)
8038
8256
  };
8039
8257
  }
8040
8258
  /**
@@ -8190,16 +8408,17 @@ var VisualsRegister;
8190
8408
  return (totalOpacity && totalTicks) ? (totalOpacity / totalTicks) : totalOpacity != null && typeof totalOpacity == "number" ? totalOpacity : 1;
8191
8409
  }
8192
8410
  ClearOpacity(params) {
8193
- const clearedIds = [];
8411
+ const clearedIds = new Set();
8194
8412
  this.ForEachState((state) => {
8195
- if (!clearedIds.includes(state.entityId) && state.opacity != null) {
8413
+ if (!clearedIds.has(state.entityId) && state.opacity != null) {
8414
+ this.markLazyExclusion(state.entityId);
8196
8415
  this.queueUpdate({
8197
8416
  entityId: state.entityId,
8198
8417
  refresh: {
8199
8418
  opacity: true
8200
8419
  }
8201
8420
  });
8202
- clearedIds.push(state.entityId);
8421
+ clearedIds.add(state.entityId);
8203
8422
  }
8204
8423
  delete state.opacity;
8205
8424
  });
@@ -13832,6 +14051,14 @@ var TilesetCadRenderManager;
13832
14051
  this.disposed = false;
13833
14052
  this.modelSpace = false;
13834
14053
  this.cTileset = null;
14054
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
14055
+ this.tileset = null;
14056
+ // Removal callbacks for this instance's own tile stream listeners.
14057
+ this.tileLoadRemoval = null;
14058
+ this.tileUnloadRemoval = null;
14059
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
14060
+ // synchronous pass over potentially millions of regos.
14061
+ this.handoffRestyleInterval = null;
13835
14062
  this.styler = new TilesetRenderEngine.Styler();
13836
14063
  this.modelTreeUpdate = new BruceEvent();
13837
14064
  // Quick look-up of the model tree nodes by entity/geomId.
@@ -13938,6 +14165,7 @@ var TilesetCadRenderManager;
13938
14165
  if (!tileset || this.disposed) {
13939
14166
  return;
13940
14167
  }
14168
+ this.tileset = tileset;
13941
14169
  if (!api) {
13942
14170
  api = this.getters.GetBruceApi({
13943
14171
  accountId: accountId
@@ -14104,7 +14332,7 @@ var TilesetCadRenderManager;
14104
14332
  console.error(e);
14105
14333
  }
14106
14334
  });
14107
- cTileset.tileLoad.addEventListener((tile) => {
14335
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
14108
14336
  try {
14109
14337
  this.queueTile(tile, true);
14110
14338
  }
@@ -14112,7 +14340,7 @@ var TilesetCadRenderManager;
14112
14340
  console.error(e);
14113
14341
  }
14114
14342
  });
14115
- cTileset.tileUnload.addEventListener((tile) => {
14343
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
14116
14344
  try {
14117
14345
  this.queueTile(tile, false);
14118
14346
  }
@@ -14132,6 +14360,141 @@ var TilesetCadRenderManager;
14132
14360
  this.viewer.zoomTo(this.cTileset, new HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
14133
14361
  }
14134
14362
  }
14363
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
14364
+ // Model-space vs. geospatial, and historic vs. live, position the tileset via entirely
14365
+ // different code paths (baked in once at Init() time), so either changing must never
14366
+ // match an existing key and must instead force a real reload.
14367
+ get HandoffKey() {
14368
+ return Manager.GetHandoffKey(this.item, this.modelSpace);
14369
+ }
14370
+ static GetHandoffKey(item, modelSpace) {
14371
+ var _a, _b, _c;
14372
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
14373
+ if (!tilesetId) {
14374
+ return null;
14375
+ }
14376
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
14377
+ const historic = Boolean((_c = item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic);
14378
+ return `${accountId}:${tilesetId}:${Boolean(modelSpace)}:${historic}`;
14379
+ }
14380
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
14381
+ PrepareHandoff() {
14382
+ var _a, _b;
14383
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
14384
+ return null;
14385
+ }
14386
+ // Drain anything mid-flight, once handed off, this instance no longer owns the tile events.
14387
+ while (this.featureQueue.length) {
14388
+ this.processFeatureQueueBatch();
14389
+ }
14390
+ if (this.featureQueueInterval) {
14391
+ clearInterval(this.featureQueueInterval);
14392
+ this.featureQueueInterval = null;
14393
+ }
14394
+ if (this.handoffRestyleInterval) {
14395
+ clearInterval(this.handoffRestyleInterval);
14396
+ this.handoffRestyleInterval = null;
14397
+ }
14398
+ // Not transferred, the receiving manager sets up its own fresh copy if needed.
14399
+ this.viewerDateTimeDispose();
14400
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
14401
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
14402
+ this.tileLoadRemoval = null;
14403
+ this.tileUnloadRemoval = null;
14404
+ const payload = {
14405
+ cTileset: this.cTileset,
14406
+ tileset: this.tileset,
14407
+ rootId: this.rootId,
14408
+ modelTree: this.modelTree,
14409
+ treeNodeByGeomId: this.treeNodeByGeomId,
14410
+ treeNodeByEntityId: this.treeNodeByEntityId,
14411
+ loadedCesiumEntities: this.loadedCesiumEntities,
14412
+ sourceMenuItemId: this.item.id
14413
+ };
14414
+ // Not routed through Dispose(), that would remove the tileset/regos just handed off.
14415
+ this.cTileset = null;
14416
+ this.disposed = true;
14417
+ return payload;
14418
+ }
14419
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
14420
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
14421
+ AdoptHandoff(payload) {
14422
+ var _a, _b, _c, _d;
14423
+ this.cTileset = payload.cTileset;
14424
+ this.tileset = payload.tileset;
14425
+ this.rootId = payload.rootId;
14426
+ this.modelTree = payload.modelTree;
14427
+ this.treeNodeByGeomId = payload.treeNodeByGeomId;
14428
+ this.treeNodeByEntityId = payload.treeNodeByEntityId;
14429
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
14430
+ this.renderPriority = this.item.renderPriority;
14431
+ if (this.renderPriority == null) {
14432
+ this.renderPriority = 0;
14433
+ }
14434
+ // Same visuals, same registrations - just re-keyed onto this menu item.
14435
+ this.visualsManager.ReassignMenuItem({
14436
+ fromMenuItemId: payload.sourceMenuItemId,
14437
+ toMenuItemId: this.item.id,
14438
+ toMenuItemType: this.item.Type,
14439
+ toPriority: this.renderPriority,
14440
+ requestRender: false
14441
+ });
14442
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
14443
+ try {
14444
+ this.queueTile(tile, true);
14445
+ }
14446
+ catch (e) {
14447
+ console.error(e);
14448
+ }
14449
+ });
14450
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
14451
+ try {
14452
+ this.queueTile(tile, false);
14453
+ }
14454
+ catch (e) {
14455
+ console.error(e);
14456
+ }
14457
+ });
14458
+ this.onCTilesetLoad();
14459
+ this.styler.Init({
14460
+ viewer: this.viewer,
14461
+ api: this.getters.GetBruceApi(),
14462
+ cTileset: this.cTileset,
14463
+ fallbackStyleId: this.item.styleId,
14464
+ styleMapping: this.item.StyleMapping,
14465
+ expandSources: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.ExpandSources,
14466
+ menuItemId: this.item.id,
14467
+ register: this.visualsManager,
14468
+ scenario: (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.Scenario,
14469
+ historic: (_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic,
14470
+ });
14471
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
14472
+ this.viewer.scene.requestRender();
14473
+ if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
14474
+ this.viewerDateTimeSub(this.tileset);
14475
+ }
14476
+ }
14477
+ queueHandoffRestyle(regos) {
14478
+ if (!regos.length) {
14479
+ return;
14480
+ }
14481
+ const BATCH_SIZE = 5000;
14482
+ let index = 0;
14483
+ this.handoffRestyleInterval = setInterval(() => {
14484
+ if (this.disposed) {
14485
+ clearInterval(this.handoffRestyleInterval);
14486
+ this.handoffRestyleInterval = null;
14487
+ return;
14488
+ }
14489
+ const end = Math.min(index + BATCH_SIZE, regos.length);
14490
+ this.styler.QueueEntities(regos.slice(index, end), true);
14491
+ index = end;
14492
+ if (index >= regos.length) {
14493
+ clearInterval(this.handoffRestyleInterval);
14494
+ this.handoffRestyleInterval = null;
14495
+ }
14496
+ }, 10);
14497
+ }
14135
14498
  /**
14136
14499
  * @param tile
14137
14500
  * @param load indicates if we are loading or unloading the tile.
@@ -14144,7 +14507,7 @@ var TilesetCadRenderManager;
14144
14507
  }
14145
14508
  for (let i = 0; i < content.featuresLength; i++) {
14146
14509
  const feature = content.getFeature(i);
14147
- if (!this.featureQueue.includes(feature)) {
14510
+ if (!this.featureQueueStates.has(feature)) {
14148
14511
  this.featureQueue.push(feature);
14149
14512
  }
14150
14513
  this.featureQueueStates.set(feature, load);
@@ -14681,6 +15044,10 @@ var TilesetCadRenderManager;
14681
15044
  this.featureQueueInterval = null;
14682
15045
  this.featureQueue = [];
14683
15046
  }
15047
+ if (this.handoffRestyleInterval) {
15048
+ clearInterval(this.handoffRestyleInterval);
15049
+ this.handoffRestyleInterval = null;
15050
+ }
14684
15051
  if (this.cTileset) {
14685
15052
  const viewer = this.viewer;
14686
15053
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17334,6 +17701,14 @@ var TilesetEntitiesRenderManager;
17334
17701
  this.initCounter = 0;
17335
17702
  this.disposed = false;
17336
17703
  this.cTileset = null;
17704
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
17705
+ this.tileset = null;
17706
+ // Removal callbacks for this instance's own tile stream listeners.
17707
+ this.tileLoadRemoval = null;
17708
+ this.tileUnloadRemoval = null;
17709
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
17710
+ // synchronous pass over potentially millions of regos.
17711
+ this.handoffRestyleInterval = null;
17337
17712
  this.styler = new TilesetRenderEngine.Styler();
17338
17713
  // Entity ID -> rego.
17339
17714
  // We retain this information as a quick look-up on what has been registered.
@@ -17421,6 +17796,7 @@ var TilesetEntitiesRenderManager;
17421
17796
  if (!tileset || this.disposed || counter !== this.initCounter) {
17422
17797
  return;
17423
17798
  }
17799
+ this.tileset = tileset;
17424
17800
  this.typeId = tileset.settings.entityTypeId;
17425
17801
  // Other account so we'll prefer the fallback entity-type ID if it's set.
17426
17802
  if (accountId != this.getters.GetAccountId()) {
@@ -17469,7 +17845,7 @@ var TilesetEntitiesRenderManager;
17469
17845
  console.error(e);
17470
17846
  }
17471
17847
  });
17472
- cTileset.tileLoad.addEventListener((tile) => {
17848
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
17473
17849
  try {
17474
17850
  this.queueTile(tile, true);
17475
17851
  }
@@ -17477,7 +17853,7 @@ var TilesetEntitiesRenderManager;
17477
17853
  console.error(e);
17478
17854
  }
17479
17855
  });
17480
- cTileset.tileUnload.addEventListener((tile) => {
17856
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
17481
17857
  try {
17482
17858
  this.queueTile(tile, false);
17483
17859
  }
@@ -17518,7 +17894,7 @@ var TilesetEntitiesRenderManager;
17518
17894
  }
17519
17895
  for (let i = 0; i < content.featuresLength; i++) {
17520
17896
  const feature = content.getFeature(i);
17521
- if (!this.featureQueue.includes(feature)) {
17897
+ if (!this.featureQueueStates.has(feature)) {
17522
17898
  this.featureQueue.push(feature);
17523
17899
  }
17524
17900
  this.featureQueueStates.set(feature, load);
@@ -17637,6 +18013,10 @@ var TilesetEntitiesRenderManager;
17637
18013
  this.featureQueueInterval = null;
17638
18014
  this.featureQueue = [];
17639
18015
  }
18016
+ if (this.handoffRestyleInterval) {
18017
+ clearInterval(this.handoffRestyleInterval);
18018
+ this.handoffRestyleInterval = null;
18019
+ }
17640
18020
  if (this.cTileset) {
17641
18021
  const viewer = this.viewer;
17642
18022
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17657,6 +18037,141 @@ var TilesetEntitiesRenderManager;
17657
18037
  this.viewer.zoomTo(this.cTileset, new HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
17658
18038
  }
17659
18039
  }
18040
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
18041
+ get HandoffKey() {
18042
+ return Manager.GetHandoffKey(this.item);
18043
+ }
18044
+ static GetHandoffKey(item) {
18045
+ var _a, _b;
18046
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
18047
+ if (!tilesetId) {
18048
+ return null;
18049
+ }
18050
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
18051
+ return `${accountId}:${tilesetId}`;
18052
+ }
18053
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
18054
+ PrepareHandoff() {
18055
+ var _a, _b;
18056
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
18057
+ return null;
18058
+ }
18059
+ // Drain anything mid-flight - once handed off, this instance no longer owns the tile events.
18060
+ while (this.featureQueue.length) {
18061
+ this.processFeatureQueueBatch();
18062
+ }
18063
+ if (this.featureQueueInterval) {
18064
+ clearInterval(this.featureQueueInterval);
18065
+ this.featureQueueInterval = null;
18066
+ }
18067
+ if (this.handoffRestyleInterval) {
18068
+ clearInterval(this.handoffRestyleInterval);
18069
+ this.handoffRestyleInterval = null;
18070
+ }
18071
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
18072
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
18073
+ this.tileLoadRemoval = null;
18074
+ this.tileUnloadRemoval = null;
18075
+ const payload = {
18076
+ cTileset: this.cTileset,
18077
+ tileset: this.tileset,
18078
+ typeId: this.typeId,
18079
+ loadedCesiumEntities: this.loadedCesiumEntities,
18080
+ sourceMenuItemId: this.item.id
18081
+ };
18082
+ // Not routed through Dispose() - that would remove the tileset/regos just handed off.
18083
+ this.cTileset = null;
18084
+ this.disposed = true;
18085
+ return payload;
18086
+ }
18087
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
18088
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
18089
+ AdoptHandoff(payload) {
18090
+ var _a;
18091
+ this.cTileset = payload.cTileset;
18092
+ this.tileset = payload.tileset;
18093
+ this.typeId = payload.typeId;
18094
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
18095
+ this.renderPriority = this.item.renderPriority;
18096
+ if (this.renderPriority == null) {
18097
+ this.renderPriority = 0;
18098
+ }
18099
+ this.visualsManager.ReassignMenuItem({
18100
+ fromMenuItemId: payload.sourceMenuItemId,
18101
+ toMenuItemId: this.item.id,
18102
+ toMenuItemType: this.item.Type,
18103
+ toPriority: this.renderPriority,
18104
+ requestRender: false
18105
+ });
18106
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
18107
+ try {
18108
+ this.queueTile(tile, true);
18109
+ }
18110
+ catch (e) {
18111
+ console.error(e);
18112
+ }
18113
+ });
18114
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
18115
+ try {
18116
+ this.queueTile(tile, false);
18117
+ }
18118
+ catch (e) {
18119
+ console.error(e);
18120
+ }
18121
+ });
18122
+ if (this.item.ApplyStyles) {
18123
+ this.styler.Init({
18124
+ viewer: this.viewer,
18125
+ api: this.getters.GetBruceApi(),
18126
+ cTileset: this.cTileset,
18127
+ fallbackStyleId: this.item.styleId,
18128
+ styleMapping: [],
18129
+ expandSources: false,
18130
+ menuItemId: this.item.id,
18131
+ register: this.visualsManager,
18132
+ historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
18133
+ });
18134
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
18135
+ }
18136
+ this.onCTilesetLoad();
18137
+ let attenuation = this.item.attenuation;
18138
+ if (!attenuation && attenuation != false) {
18139
+ attenuation = true;
18140
+ }
18141
+ let attenuationMax = this.item.attenuationMax;
18142
+ if (isNaN(attenuationMax)) {
18143
+ attenuationMax = 20;
18144
+ }
18145
+ TilesetRenderEngine.ApplySettings({
18146
+ cTileset: this.cTileset,
18147
+ settings: {
18148
+ attenuation: attenuation,
18149
+ maximumAttenuation: attenuationMax
18150
+ }
18151
+ });
18152
+ this.viewer.scene.requestRender();
18153
+ }
18154
+ queueHandoffRestyle(regos) {
18155
+ if (!regos.length) {
18156
+ return;
18157
+ }
18158
+ const BATCH_SIZE = 5000;
18159
+ let index = 0;
18160
+ this.handoffRestyleInterval = setInterval(() => {
18161
+ if (this.disposed) {
18162
+ clearInterval(this.handoffRestyleInterval);
18163
+ this.handoffRestyleInterval = null;
18164
+ return;
18165
+ }
18166
+ const end = Math.min(index + BATCH_SIZE, regos.length);
18167
+ this.styler.QueueEntities(regos.slice(index, end), true);
18168
+ index = end;
18169
+ if (index >= regos.length) {
18170
+ clearInterval(this.handoffRestyleInterval);
18171
+ this.handoffRestyleInterval = null;
18172
+ }
18173
+ }, 10);
18174
+ }
17660
18175
  mapTilesetFeature(feature) {
17661
18176
  var _a, _b, _c;
17662
18177
  this.evaluateFeatureProps(feature);
@@ -18498,7 +19013,7 @@ var TilesetArbRenderManager;
18498
19013
  }
18499
19014
  for (let i = 0; i < content.featuresLength; i++) {
18500
19015
  const feature = content.getFeature(i);
18501
- if (!this.featureQueue.includes(feature)) {
19016
+ if (!this.featureQueueStates.has(feature)) {
18502
19017
  this.featureQueue.push(feature);
18503
19018
  }
18504
19019
  this.featureQueueStates.set(feature, load);
@@ -21226,6 +21741,7 @@ var MenuItemManager;
21226
21741
  }
21227
21742
  constructor(params) {
21228
21743
  this.items = [];
21744
+ this.pendingHandoffPool = [];
21229
21745
  this.onUpdate = null;
21230
21746
  let { viewer, visualsRegister, getters } = params;
21231
21747
  this.viewer = viewer;
@@ -21249,7 +21765,7 @@ var MenuItemManager;
21249
21765
  * This will dispose all render managers and unregister all menu items.
21250
21766
  */
21251
21767
  Dispose(params) {
21252
- var _a;
21768
+ var _a, _b;
21253
21769
  const { disposeRegister } = params !== null && params !== void 0 ? params : {};
21254
21770
  for (let i = 0; i < this.items.length; i++) {
21255
21771
  const item = this.items[i];
@@ -21261,7 +21777,9 @@ var MenuItemManager;
21261
21777
  }
21262
21778
  }
21263
21779
  this.items = [];
21780
+ this.FlushPendingHandoffs();
21264
21781
  this.tilesetInitQueue.Dispose();
21782
+ (_b = this.sharedMonitor) === null || _b === void 0 ? void 0 : _b.Dispose();
21265
21783
  if (this.visualsRegister && disposeRegister != false) {
21266
21784
  this.visualsRegister.Dispose();
21267
21785
  }
@@ -21610,8 +22128,14 @@ var MenuItemManager;
21610
22128
  console.error("Menu item type is not implemented.", params.item.Type);
21611
22129
  }
21612
22130
  if (rItem.renderManager && !rItem.renderManager.Disposed) {
22131
+ const handoffPayload = this.tryClaimHandoffPayload(params.item, rItem.renderManager, params.modelSpace);
21613
22132
  try {
21614
- rItem.renderManager.Init();
22133
+ if (handoffPayload) {
22134
+ rItem.renderManager.AdoptHandoff(handoffPayload);
22135
+ }
22136
+ else {
22137
+ rItem.renderManager.Init();
22138
+ }
21615
22139
  }
21616
22140
  catch (e) {
21617
22141
  rItem.errors.push(e);
@@ -21642,7 +22166,7 @@ var MenuItemManager;
21642
22166
  */
21643
22167
  RemoveItemById(params) {
21644
22168
  var _a, _b;
21645
- let { menuItemId: id, recursive } = params;
22169
+ let { menuItemId: id, recursive, allowHandoff } = params;
21646
22170
  if (this.viewer.isDestroyed()) {
21647
22171
  return;
21648
22172
  }
@@ -21656,19 +22180,79 @@ var MenuItemManager;
21656
22180
  const child = this.items.find(x => x.id === item.childIds[i]);
21657
22181
  if (child) {
21658
22182
  this.RemoveItemById({
21659
- menuItemId: child.id
22183
+ menuItemId: child.id,
22184
+ allowHandoff
21660
22185
  });
21661
22186
  }
21662
22187
  }
21663
22188
  }
22189
+ if (allowHandoff && this.isHandoffCapable(item.renderManager)) {
22190
+ this.pendingHandoffPool.push(item);
22191
+ }
22192
+ else {
22193
+ try {
22194
+ (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
22195
+ }
22196
+ catch (e) {
22197
+ console.error(e);
22198
+ }
22199
+ }
22200
+ this.items = this.items.filter(x => x.id !== id);
22201
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
22202
+ }
22203
+ }
22204
+ isHandoffCapable(renderManager) {
22205
+ return Boolean(renderManager) && typeof renderManager.PrepareHandoff === "function";
22206
+ }
22207
+ // Resolves the hand-off key a menu item's data would have.
22208
+ computeHandoffKey(item, modelSpace) {
22209
+ if (item.Type === MenuItem.EType.CadTileset) {
22210
+ return TilesetCadRenderManager.Manager.GetHandoffKey(item, modelSpace);
22211
+ }
22212
+ if (item.Type === MenuItem.EType.EntityTileset) {
22213
+ return TilesetEntitiesRenderManager.Manager.GetHandoffKey(item);
22214
+ }
22215
+ return null;
22216
+ }
22217
+ // Claims a pooled manager for the same resource, if one exists and both sides opt in.
22218
+ // Returns null (meaning: construct normally via Init()) otherwise.
22219
+ tryClaimHandoffPayload(item, newManager, modelSpace) {
22220
+ if (typeof newManager.AdoptHandoff !== "function") {
22221
+ return null;
22222
+ }
22223
+ const key = this.computeHandoffKey(item, modelSpace);
22224
+ if (!key) {
22225
+ return null;
22226
+ }
22227
+ const poolIndex = this.pendingHandoffPool.findIndex(pooled => pooled.type === item.Type &&
22228
+ this.isHandoffCapable(pooled.renderManager) &&
22229
+ pooled.renderManager.HandoffKey === key);
22230
+ if (poolIndex < 0) {
22231
+ return null;
22232
+ }
22233
+ const pooled = this.pendingHandoffPool[poolIndex].renderManager;
22234
+ const payload = pooled.PrepareHandoff();
22235
+ if (!payload) {
22236
+ return null;
22237
+ }
22238
+ this.pendingHandoffPool.splice(poolIndex, 1);
22239
+ return payload;
22240
+ }
22241
+ // Disposes everything left unclaimed in the pending-hand-off pool.
22242
+ FlushPendingHandoffs() {
22243
+ var _a;
22244
+ if (!this.pendingHandoffPool.length) {
22245
+ return;
22246
+ }
22247
+ const pool = this.pendingHandoffPool;
22248
+ this.pendingHandoffPool = [];
22249
+ for (const item of pool) {
21664
22250
  try {
21665
22251
  (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
21666
22252
  }
21667
22253
  catch (e) {
21668
22254
  console.error(e);
21669
22255
  }
21670
- this.items = this.items.filter(x => x.id !== id);
21671
- (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
21672
22256
  }
21673
22257
  }
21674
22258
  GetEnabledItemIds() {
@@ -24689,7 +25273,8 @@ async function renderLegacyNavigator(iteration, params, bookmark, view) {
24689
25273
  if (newItemIds.indexOf(id) === -1 ||
24690
25274
  id == RELATION_MENU_ITEM_ID) {
24691
25275
  params.manager.RemoveItemById({
24692
- menuItemId: id
25276
+ menuItemId: id,
25277
+ allowHandoff: true
24693
25278
  });
24694
25279
  }
24695
25280
  }
@@ -24704,6 +25289,8 @@ async function renderLegacyNavigator(iteration, params, bookmark, view) {
24704
25289
  if (!assertIteration$1(params.viewer, iteration)) {
24705
25290
  return;
24706
25291
  }
25292
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25293
+ manager.FlushPendingHandoffs();
24707
25294
  }
24708
25295
  if ((_e = bSettings === null || bSettings === void 0 ? void 0 : bSettings.drawnRelationEntityIDs) === null || _e === void 0 ? void 0 : _e.length) {
24709
25296
  const menuItem = {
@@ -25228,7 +25815,8 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
25228
25815
  }
25229
25816
  if (shouldRemove) {
25230
25817
  params.manager.RemoveItemById({
25231
- menuItemId: id
25818
+ menuItemId: id,
25819
+ allowHandoff: true
25232
25820
  });
25233
25821
  }
25234
25822
  }
@@ -25242,6 +25830,8 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
25242
25830
  if (!assertIteration$1(params.viewer, iteration)) {
25243
25831
  return;
25244
25832
  }
25833
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25834
+ params.manager.FlushPendingHandoffs();
25245
25835
  }
25246
25836
  if (legacyRelationIds.length || relations.length) {
25247
25837
  if (relations.length) {
@@ -36596,7 +37186,7 @@ var StyleUtils;
36596
37186
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
36597
37187
  })(StyleUtils || (StyleUtils = {}));
36598
37188
 
36599
- const VERSION = "6.7.9";
37189
+ const VERSION = "6.8.1";
36600
37190
  /**
36601
37191
  * Updates the environment instance used by bruce-cesium to one specified.
36602
37192
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.