bruce-cesium 6.7.9 → 6.8.0

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 +743 -157
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +743 -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 +147 -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
@@ -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,137 @@ 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
+ get HandoffKey() {
14365
+ return Manager.GetHandoffKey(this.item);
14366
+ }
14367
+ static GetHandoffKey(item) {
14368
+ var _a, _b;
14369
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
14370
+ if (!tilesetId) {
14371
+ return null;
14372
+ }
14373
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
14374
+ return `${accountId}:${tilesetId}`;
14375
+ }
14376
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
14377
+ PrepareHandoff() {
14378
+ var _a, _b;
14379
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
14380
+ return null;
14381
+ }
14382
+ // Drain anything mid-flight, once handed off, this instance no longer owns the tile events.
14383
+ while (this.featureQueue.length) {
14384
+ this.processFeatureQueueBatch();
14385
+ }
14386
+ if (this.featureQueueInterval) {
14387
+ clearInterval(this.featureQueueInterval);
14388
+ this.featureQueueInterval = null;
14389
+ }
14390
+ if (this.handoffRestyleInterval) {
14391
+ clearInterval(this.handoffRestyleInterval);
14392
+ this.handoffRestyleInterval = null;
14393
+ }
14394
+ // Not transferred, the receiving manager sets up its own fresh copy if needed.
14395
+ this.viewerDateTimeDispose();
14396
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
14397
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
14398
+ this.tileLoadRemoval = null;
14399
+ this.tileUnloadRemoval = null;
14400
+ const payload = {
14401
+ cTileset: this.cTileset,
14402
+ tileset: this.tileset,
14403
+ rootId: this.rootId,
14404
+ modelTree: this.modelTree,
14405
+ treeNodeByGeomId: this.treeNodeByGeomId,
14406
+ treeNodeByEntityId: this.treeNodeByEntityId,
14407
+ loadedCesiumEntities: this.loadedCesiumEntities,
14408
+ sourceMenuItemId: this.item.id
14409
+ };
14410
+ // Not routed through Dispose(), that would remove the tileset/regos just handed off.
14411
+ this.cTileset = null;
14412
+ this.disposed = true;
14413
+ return payload;
14414
+ }
14415
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
14416
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
14417
+ AdoptHandoff(payload) {
14418
+ var _a, _b, _c, _d;
14419
+ this.cTileset = payload.cTileset;
14420
+ this.tileset = payload.tileset;
14421
+ this.rootId = payload.rootId;
14422
+ this.modelTree = payload.modelTree;
14423
+ this.treeNodeByGeomId = payload.treeNodeByGeomId;
14424
+ this.treeNodeByEntityId = payload.treeNodeByEntityId;
14425
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
14426
+ this.renderPriority = this.item.renderPriority;
14427
+ if (this.renderPriority == null) {
14428
+ this.renderPriority = 0;
14429
+ }
14430
+ // Same visuals, same registrations - just re-keyed onto this menu item.
14431
+ this.visualsManager.ReassignMenuItem({
14432
+ fromMenuItemId: payload.sourceMenuItemId,
14433
+ toMenuItemId: this.item.id,
14434
+ toMenuItemType: this.item.Type,
14435
+ toPriority: this.renderPriority,
14436
+ requestRender: false
14437
+ });
14438
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
14439
+ try {
14440
+ this.queueTile(tile, true);
14441
+ }
14442
+ catch (e) {
14443
+ console.error(e);
14444
+ }
14445
+ });
14446
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
14447
+ try {
14448
+ this.queueTile(tile, false);
14449
+ }
14450
+ catch (e) {
14451
+ console.error(e);
14452
+ }
14453
+ });
14454
+ this.onCTilesetLoad();
14455
+ this.styler.Init({
14456
+ viewer: this.viewer,
14457
+ api: this.getters.GetBruceApi(),
14458
+ cTileset: this.cTileset,
14459
+ fallbackStyleId: this.item.styleId,
14460
+ styleMapping: this.item.StyleMapping,
14461
+ expandSources: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.ExpandSources,
14462
+ menuItemId: this.item.id,
14463
+ register: this.visualsManager,
14464
+ scenario: (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.Scenario,
14465
+ historic: (_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic,
14466
+ });
14467
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
14468
+ this.viewer.scene.requestRender();
14469
+ if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
14470
+ this.viewerDateTimeSub(this.tileset);
14471
+ }
14472
+ }
14473
+ queueHandoffRestyle(regos) {
14474
+ if (!regos.length) {
14475
+ return;
14476
+ }
14477
+ const BATCH_SIZE = 5000;
14478
+ let index = 0;
14479
+ this.handoffRestyleInterval = setInterval(() => {
14480
+ if (this.disposed) {
14481
+ clearInterval(this.handoffRestyleInterval);
14482
+ this.handoffRestyleInterval = null;
14483
+ return;
14484
+ }
14485
+ const end = Math.min(index + BATCH_SIZE, regos.length);
14486
+ this.styler.QueueEntities(regos.slice(index, end), true);
14487
+ index = end;
14488
+ if (index >= regos.length) {
14489
+ clearInterval(this.handoffRestyleInterval);
14490
+ this.handoffRestyleInterval = null;
14491
+ }
14492
+ }, 10);
14493
+ }
14135
14494
  /**
14136
14495
  * @param tile
14137
14496
  * @param load indicates if we are loading or unloading the tile.
@@ -14144,7 +14503,7 @@ var TilesetCadRenderManager;
14144
14503
  }
14145
14504
  for (let i = 0; i < content.featuresLength; i++) {
14146
14505
  const feature = content.getFeature(i);
14147
- if (!this.featureQueue.includes(feature)) {
14506
+ if (!this.featureQueueStates.has(feature)) {
14148
14507
  this.featureQueue.push(feature);
14149
14508
  }
14150
14509
  this.featureQueueStates.set(feature, load);
@@ -14681,6 +15040,10 @@ var TilesetCadRenderManager;
14681
15040
  this.featureQueueInterval = null;
14682
15041
  this.featureQueue = [];
14683
15042
  }
15043
+ if (this.handoffRestyleInterval) {
15044
+ clearInterval(this.handoffRestyleInterval);
15045
+ this.handoffRestyleInterval = null;
15046
+ }
14684
15047
  if (this.cTileset) {
14685
15048
  const viewer = this.viewer;
14686
15049
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17334,6 +17697,14 @@ var TilesetEntitiesRenderManager;
17334
17697
  this.initCounter = 0;
17335
17698
  this.disposed = false;
17336
17699
  this.cTileset = null;
17700
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
17701
+ this.tileset = null;
17702
+ // Removal callbacks for this instance's own tile stream listeners.
17703
+ this.tileLoadRemoval = null;
17704
+ this.tileUnloadRemoval = null;
17705
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
17706
+ // synchronous pass over potentially millions of regos.
17707
+ this.handoffRestyleInterval = null;
17337
17708
  this.styler = new TilesetRenderEngine.Styler();
17338
17709
  // Entity ID -> rego.
17339
17710
  // We retain this information as a quick look-up on what has been registered.
@@ -17421,6 +17792,7 @@ var TilesetEntitiesRenderManager;
17421
17792
  if (!tileset || this.disposed || counter !== this.initCounter) {
17422
17793
  return;
17423
17794
  }
17795
+ this.tileset = tileset;
17424
17796
  this.typeId = tileset.settings.entityTypeId;
17425
17797
  // Other account so we'll prefer the fallback entity-type ID if it's set.
17426
17798
  if (accountId != this.getters.GetAccountId()) {
@@ -17469,7 +17841,7 @@ var TilesetEntitiesRenderManager;
17469
17841
  console.error(e);
17470
17842
  }
17471
17843
  });
17472
- cTileset.tileLoad.addEventListener((tile) => {
17844
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
17473
17845
  try {
17474
17846
  this.queueTile(tile, true);
17475
17847
  }
@@ -17477,7 +17849,7 @@ var TilesetEntitiesRenderManager;
17477
17849
  console.error(e);
17478
17850
  }
17479
17851
  });
17480
- cTileset.tileUnload.addEventListener((tile) => {
17852
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
17481
17853
  try {
17482
17854
  this.queueTile(tile, false);
17483
17855
  }
@@ -17518,7 +17890,7 @@ var TilesetEntitiesRenderManager;
17518
17890
  }
17519
17891
  for (let i = 0; i < content.featuresLength; i++) {
17520
17892
  const feature = content.getFeature(i);
17521
- if (!this.featureQueue.includes(feature)) {
17893
+ if (!this.featureQueueStates.has(feature)) {
17522
17894
  this.featureQueue.push(feature);
17523
17895
  }
17524
17896
  this.featureQueueStates.set(feature, load);
@@ -17637,6 +18009,10 @@ var TilesetEntitiesRenderManager;
17637
18009
  this.featureQueueInterval = null;
17638
18010
  this.featureQueue = [];
17639
18011
  }
18012
+ if (this.handoffRestyleInterval) {
18013
+ clearInterval(this.handoffRestyleInterval);
18014
+ this.handoffRestyleInterval = null;
18015
+ }
17640
18016
  if (this.cTileset) {
17641
18017
  const viewer = this.viewer;
17642
18018
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17657,6 +18033,141 @@ var TilesetEntitiesRenderManager;
17657
18033
  this.viewer.zoomTo(this.cTileset, new HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
17658
18034
  }
17659
18035
  }
18036
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
18037
+ get HandoffKey() {
18038
+ return Manager.GetHandoffKey(this.item);
18039
+ }
18040
+ static GetHandoffKey(item) {
18041
+ var _a, _b;
18042
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
18043
+ if (!tilesetId) {
18044
+ return null;
18045
+ }
18046
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
18047
+ return `${accountId}:${tilesetId}`;
18048
+ }
18049
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
18050
+ PrepareHandoff() {
18051
+ var _a, _b;
18052
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
18053
+ return null;
18054
+ }
18055
+ // Drain anything mid-flight - once handed off, this instance no longer owns the tile events.
18056
+ while (this.featureQueue.length) {
18057
+ this.processFeatureQueueBatch();
18058
+ }
18059
+ if (this.featureQueueInterval) {
18060
+ clearInterval(this.featureQueueInterval);
18061
+ this.featureQueueInterval = null;
18062
+ }
18063
+ if (this.handoffRestyleInterval) {
18064
+ clearInterval(this.handoffRestyleInterval);
18065
+ this.handoffRestyleInterval = null;
18066
+ }
18067
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
18068
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
18069
+ this.tileLoadRemoval = null;
18070
+ this.tileUnloadRemoval = null;
18071
+ const payload = {
18072
+ cTileset: this.cTileset,
18073
+ tileset: this.tileset,
18074
+ typeId: this.typeId,
18075
+ loadedCesiumEntities: this.loadedCesiumEntities,
18076
+ sourceMenuItemId: this.item.id
18077
+ };
18078
+ // Not routed through Dispose() - that would remove the tileset/regos just handed off.
18079
+ this.cTileset = null;
18080
+ this.disposed = true;
18081
+ return payload;
18082
+ }
18083
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
18084
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
18085
+ AdoptHandoff(payload) {
18086
+ var _a;
18087
+ this.cTileset = payload.cTileset;
18088
+ this.tileset = payload.tileset;
18089
+ this.typeId = payload.typeId;
18090
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
18091
+ this.renderPriority = this.item.renderPriority;
18092
+ if (this.renderPriority == null) {
18093
+ this.renderPriority = 0;
18094
+ }
18095
+ this.visualsManager.ReassignMenuItem({
18096
+ fromMenuItemId: payload.sourceMenuItemId,
18097
+ toMenuItemId: this.item.id,
18098
+ toMenuItemType: this.item.Type,
18099
+ toPriority: this.renderPriority,
18100
+ requestRender: false
18101
+ });
18102
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
18103
+ try {
18104
+ this.queueTile(tile, true);
18105
+ }
18106
+ catch (e) {
18107
+ console.error(e);
18108
+ }
18109
+ });
18110
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
18111
+ try {
18112
+ this.queueTile(tile, false);
18113
+ }
18114
+ catch (e) {
18115
+ console.error(e);
18116
+ }
18117
+ });
18118
+ if (this.item.ApplyStyles) {
18119
+ this.styler.Init({
18120
+ viewer: this.viewer,
18121
+ api: this.getters.GetBruceApi(),
18122
+ cTileset: this.cTileset,
18123
+ fallbackStyleId: this.item.styleId,
18124
+ styleMapping: [],
18125
+ expandSources: false,
18126
+ menuItemId: this.item.id,
18127
+ register: this.visualsManager,
18128
+ historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
18129
+ });
18130
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
18131
+ }
18132
+ this.onCTilesetLoad();
18133
+ let attenuation = this.item.attenuation;
18134
+ if (!attenuation && attenuation != false) {
18135
+ attenuation = true;
18136
+ }
18137
+ let attenuationMax = this.item.attenuationMax;
18138
+ if (isNaN(attenuationMax)) {
18139
+ attenuationMax = 20;
18140
+ }
18141
+ TilesetRenderEngine.ApplySettings({
18142
+ cTileset: this.cTileset,
18143
+ settings: {
18144
+ attenuation: attenuation,
18145
+ maximumAttenuation: attenuationMax
18146
+ }
18147
+ });
18148
+ this.viewer.scene.requestRender();
18149
+ }
18150
+ queueHandoffRestyle(regos) {
18151
+ if (!regos.length) {
18152
+ return;
18153
+ }
18154
+ const BATCH_SIZE = 5000;
18155
+ let index = 0;
18156
+ this.handoffRestyleInterval = setInterval(() => {
18157
+ if (this.disposed) {
18158
+ clearInterval(this.handoffRestyleInterval);
18159
+ this.handoffRestyleInterval = null;
18160
+ return;
18161
+ }
18162
+ const end = Math.min(index + BATCH_SIZE, regos.length);
18163
+ this.styler.QueueEntities(regos.slice(index, end), true);
18164
+ index = end;
18165
+ if (index >= regos.length) {
18166
+ clearInterval(this.handoffRestyleInterval);
18167
+ this.handoffRestyleInterval = null;
18168
+ }
18169
+ }, 10);
18170
+ }
17660
18171
  mapTilesetFeature(feature) {
17661
18172
  var _a, _b, _c;
17662
18173
  this.evaluateFeatureProps(feature);
@@ -18498,7 +19009,7 @@ var TilesetArbRenderManager;
18498
19009
  }
18499
19010
  for (let i = 0; i < content.featuresLength; i++) {
18500
19011
  const feature = content.getFeature(i);
18501
- if (!this.featureQueue.includes(feature)) {
19012
+ if (!this.featureQueueStates.has(feature)) {
18502
19013
  this.featureQueue.push(feature);
18503
19014
  }
18504
19015
  this.featureQueueStates.set(feature, load);
@@ -21226,6 +21737,7 @@ var MenuItemManager;
21226
21737
  }
21227
21738
  constructor(params) {
21228
21739
  this.items = [];
21740
+ this.pendingHandoffPool = [];
21229
21741
  this.onUpdate = null;
21230
21742
  let { viewer, visualsRegister, getters } = params;
21231
21743
  this.viewer = viewer;
@@ -21249,7 +21761,7 @@ var MenuItemManager;
21249
21761
  * This will dispose all render managers and unregister all menu items.
21250
21762
  */
21251
21763
  Dispose(params) {
21252
- var _a;
21764
+ var _a, _b;
21253
21765
  const { disposeRegister } = params !== null && params !== void 0 ? params : {};
21254
21766
  for (let i = 0; i < this.items.length; i++) {
21255
21767
  const item = this.items[i];
@@ -21261,7 +21773,9 @@ var MenuItemManager;
21261
21773
  }
21262
21774
  }
21263
21775
  this.items = [];
21776
+ this.FlushPendingHandoffs();
21264
21777
  this.tilesetInitQueue.Dispose();
21778
+ (_b = this.sharedMonitor) === null || _b === void 0 ? void 0 : _b.Dispose();
21265
21779
  if (this.visualsRegister && disposeRegister != false) {
21266
21780
  this.visualsRegister.Dispose();
21267
21781
  }
@@ -21610,8 +22124,14 @@ var MenuItemManager;
21610
22124
  console.error("Menu item type is not implemented.", params.item.Type);
21611
22125
  }
21612
22126
  if (rItem.renderManager && !rItem.renderManager.Disposed) {
22127
+ const handoffPayload = this.tryClaimHandoffPayload(params.item, rItem.renderManager);
21613
22128
  try {
21614
- rItem.renderManager.Init();
22129
+ if (handoffPayload) {
22130
+ rItem.renderManager.AdoptHandoff(handoffPayload);
22131
+ }
22132
+ else {
22133
+ rItem.renderManager.Init();
22134
+ }
21615
22135
  }
21616
22136
  catch (e) {
21617
22137
  rItem.errors.push(e);
@@ -21642,7 +22162,7 @@ var MenuItemManager;
21642
22162
  */
21643
22163
  RemoveItemById(params) {
21644
22164
  var _a, _b;
21645
- let { menuItemId: id, recursive } = params;
22165
+ let { menuItemId: id, recursive, allowHandoff } = params;
21646
22166
  if (this.viewer.isDestroyed()) {
21647
22167
  return;
21648
22168
  }
@@ -21656,19 +22176,79 @@ var MenuItemManager;
21656
22176
  const child = this.items.find(x => x.id === item.childIds[i]);
21657
22177
  if (child) {
21658
22178
  this.RemoveItemById({
21659
- menuItemId: child.id
22179
+ menuItemId: child.id,
22180
+ allowHandoff
21660
22181
  });
21661
22182
  }
21662
22183
  }
21663
22184
  }
22185
+ if (allowHandoff && this.isHandoffCapable(item.renderManager)) {
22186
+ this.pendingHandoffPool.push(item);
22187
+ }
22188
+ else {
22189
+ try {
22190
+ (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
22191
+ }
22192
+ catch (e) {
22193
+ console.error(e);
22194
+ }
22195
+ }
22196
+ this.items = this.items.filter(x => x.id !== id);
22197
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
22198
+ }
22199
+ }
22200
+ isHandoffCapable(renderManager) {
22201
+ return Boolean(renderManager) && typeof renderManager.PrepareHandoff === "function";
22202
+ }
22203
+ // Resolves the hand-off key a menu item's data would have.
22204
+ computeHandoffKey(item) {
22205
+ if (item.Type === MenuItem.EType.CadTileset) {
22206
+ return TilesetCadRenderManager.Manager.GetHandoffKey(item);
22207
+ }
22208
+ if (item.Type === MenuItem.EType.EntityTileset) {
22209
+ return TilesetEntitiesRenderManager.Manager.GetHandoffKey(item);
22210
+ }
22211
+ return null;
22212
+ }
22213
+ // Claims a pooled manager for the same resource, if one exists and both sides opt in.
22214
+ // Returns null (meaning: construct normally via Init()) otherwise.
22215
+ tryClaimHandoffPayload(item, newManager) {
22216
+ if (typeof newManager.AdoptHandoff !== "function") {
22217
+ return null;
22218
+ }
22219
+ const key = this.computeHandoffKey(item);
22220
+ if (!key) {
22221
+ return null;
22222
+ }
22223
+ const poolIndex = this.pendingHandoffPool.findIndex(pooled => pooled.type === item.Type &&
22224
+ this.isHandoffCapable(pooled.renderManager) &&
22225
+ pooled.renderManager.HandoffKey === key);
22226
+ if (poolIndex < 0) {
22227
+ return null;
22228
+ }
22229
+ const pooled = this.pendingHandoffPool[poolIndex].renderManager;
22230
+ const payload = pooled.PrepareHandoff();
22231
+ if (!payload) {
22232
+ return null;
22233
+ }
22234
+ this.pendingHandoffPool.splice(poolIndex, 1);
22235
+ return payload;
22236
+ }
22237
+ // Disposes everything left unclaimed in the pending-hand-off pool.
22238
+ FlushPendingHandoffs() {
22239
+ var _a;
22240
+ if (!this.pendingHandoffPool.length) {
22241
+ return;
22242
+ }
22243
+ const pool = this.pendingHandoffPool;
22244
+ this.pendingHandoffPool = [];
22245
+ for (const item of pool) {
21664
22246
  try {
21665
22247
  (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
21666
22248
  }
21667
22249
  catch (e) {
21668
22250
  console.error(e);
21669
22251
  }
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
22252
  }
21673
22253
  }
21674
22254
  GetEnabledItemIds() {
@@ -24689,7 +25269,8 @@ async function renderLegacyNavigator(iteration, params, bookmark, view) {
24689
25269
  if (newItemIds.indexOf(id) === -1 ||
24690
25270
  id == RELATION_MENU_ITEM_ID) {
24691
25271
  params.manager.RemoveItemById({
24692
- menuItemId: id
25272
+ menuItemId: id,
25273
+ allowHandoff: true
24693
25274
  });
24694
25275
  }
24695
25276
  }
@@ -24704,6 +25285,8 @@ async function renderLegacyNavigator(iteration, params, bookmark, view) {
24704
25285
  if (!assertIteration$1(params.viewer, iteration)) {
24705
25286
  return;
24706
25287
  }
25288
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25289
+ manager.FlushPendingHandoffs();
24707
25290
  }
24708
25291
  if ((_e = bSettings === null || bSettings === void 0 ? void 0 : bSettings.drawnRelationEntityIDs) === null || _e === void 0 ? void 0 : _e.length) {
24709
25292
  const menuItem = {
@@ -25228,7 +25811,8 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
25228
25811
  }
25229
25812
  if (shouldRemove) {
25230
25813
  params.manager.RemoveItemById({
25231
- menuItemId: id
25814
+ menuItemId: id,
25815
+ allowHandoff: true
25232
25816
  });
25233
25817
  }
25234
25818
  }
@@ -25242,6 +25826,8 @@ async function renderNavigator(iteration, params, bookmark, view, getters) {
25242
25826
  if (!assertIteration$1(params.viewer, iteration)) {
25243
25827
  return;
25244
25828
  }
25829
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25830
+ params.manager.FlushPendingHandoffs();
25245
25831
  }
25246
25832
  if (legacyRelationIds.length || relations.length) {
25247
25833
  if (relations.length) {
@@ -36596,7 +37182,7 @@ var StyleUtils;
36596
37182
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
36597
37183
  })(StyleUtils || (StyleUtils = {}));
36598
37184
 
36599
- const VERSION = "6.7.9";
37185
+ const VERSION = "6.8.0";
36600
37186
  /**
36601
37187
  * Updates the environment instance used by bruce-cesium to one specified.
36602
37188
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.