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
@@ -6516,6 +6516,18 @@
6516
6516
  this.id = BModels.ObjectUtils.UId();
6517
6517
  this.disposed = false;
6518
6518
  this.rego = {};
6519
+ // Menu Item ID -> Set of Entity IDs registered under that menu item, for O(1) lookup.
6520
+ this.entityIdsByMenuItem = {};
6521
+ // Caches GetIsIsolatedAny(). `null` means "unknown, recompute".
6522
+ // Invalidated on any write that could affect isolation.
6523
+ this.isolatedAnyCache = null;
6524
+ // Lazy OverrideStates() bookkeeping. See cancelLazyOverride()/startLazyOverride().
6525
+ this.lazyOverrideToken = 0;
6526
+ this.lazyOverrideActive = false;
6527
+ // Entity ids a direct (non-lazy) write touched while a lazy apply was in-flight-
6528
+ // skipped by the applier so a live user action always wins.
6529
+ this.lazyOverrideExclusions = new Set();
6530
+ this.lazyOverrideInterval = null;
6519
6531
  this.onUpdate = null;
6520
6532
  // Entity ID -> Menu Item ID -> State.
6521
6533
  // When no Menu Item ID is set, then the second key is a blank string.
@@ -6523,6 +6535,8 @@
6523
6535
  this.states = {};
6524
6536
  // Queue of Entity updates to process in batches to avoid overloading the render loop.
6525
6537
  this.updateQueue = [];
6538
+ // Mirrors updateQueue for O(1) "already queued" checks.
6539
+ this.updateQueueIds = new Set();
6526
6540
  // Settings related to those Entities in the queue.
6527
6541
  // This lets us modify the update without re-adding the item to the queue if it's already in it.
6528
6542
  this.updateQueueSettings = {};
@@ -6533,6 +6547,42 @@
6533
6547
  register: this
6534
6548
  });
6535
6549
  }
6550
+ indexMenuItemEntity(menuItemId, entityId) {
6551
+ if (!menuItemId) {
6552
+ return;
6553
+ }
6554
+ let set = this.entityIdsByMenuItem[menuItemId];
6555
+ if (!set) {
6556
+ set = this.entityIdsByMenuItem[menuItemId] = new Set();
6557
+ }
6558
+ set.add(entityId);
6559
+ }
6560
+ // Removes entityId from the menu item index only if it no longer has any rego
6561
+ // registered under that menu item. Safe to call speculatively.
6562
+ unindexMenuItemEntityIfAbsent(menuItemId, entityId) {
6563
+ var _a;
6564
+ if (!menuItemId) {
6565
+ return;
6566
+ }
6567
+ const set = this.entityIdsByMenuItem[menuItemId];
6568
+ if (!set) {
6569
+ return;
6570
+ }
6571
+ const stillPresent = (_a = this.rego[entityId]) === null || _a === void 0 ? void 0 : _a.some(r => r.menuItemId === menuItemId);
6572
+ if (!stillPresent) {
6573
+ set.delete(entityId);
6574
+ if (!set.size) {
6575
+ delete this.entityIdsByMenuItem[menuItemId];
6576
+ }
6577
+ }
6578
+ }
6579
+ // Records that `entityId` was just written to directly, outside of any lazy
6580
+ // OverrideStates() apply. No-op unless a lazy apply is currently in-flight.
6581
+ markLazyExclusion(entityId) {
6582
+ if (this.lazyOverrideActive) {
6583
+ this.lazyOverrideExclusions.add(entityId);
6584
+ }
6585
+ }
6536
6586
  queueUpdate(update) {
6537
6587
  if (this.Disposed) {
6538
6588
  return;
@@ -6565,7 +6615,8 @@
6565
6615
  }
6566
6616
  this.updateQueueSettings[update.entityId] = settings;
6567
6617
  // Queue ID if it's not already in the queue.
6568
- if (!this.updateQueue.includes(update.entityId)) {
6618
+ if (!this.updateQueueIds.has(update.entityId)) {
6619
+ this.updateQueueIds.add(update.entityId);
6569
6620
  this.updateQueue.push(update.entityId);
6570
6621
  this.pingUpdateQueue();
6571
6622
  }
@@ -6586,6 +6637,7 @@
6586
6637
  const cache = {};
6587
6638
  for (let i = 0; i < batch.length; i++) {
6588
6639
  const entityId = batch[i];
6640
+ this.updateQueueIds.delete(entityId);
6589
6641
  // Check if still registered.
6590
6642
  if (entityId && (!this.rego[entityId] || !this.rego[entityId].length)) {
6591
6643
  continue;
@@ -6698,6 +6750,8 @@
6698
6750
  SetStates(states, source) {
6699
6751
  // Array of changed Entity IDs so we know what to refresh/notify at the end.
6700
6752
  const entityIds = [];
6753
+ // Mirrors entityIds for O(1) "already recorded" checks.
6754
+ const entityIdsSeen = new Set();
6701
6755
  // If we need to update all Entities.
6702
6756
  // This is usually for isolation changes.
6703
6757
  let updateAll = false;
@@ -6730,8 +6784,9 @@
6730
6784
  };
6731
6785
  const doUpdate = (state) => {
6732
6786
  const eChanged = this.SetState(state, false, source);
6733
- if (eChanged && !entityIds.includes(state.entityId)) {
6787
+ if (eChanged && !entityIdsSeen.has(state.entityId)) {
6734
6788
  updateRefreshMarkers(state);
6789
+ entityIdsSeen.add(state.entityId);
6735
6790
  entityIds.push(state.entityId);
6736
6791
  changed = true;
6737
6792
  }
@@ -6766,7 +6821,7 @@
6766
6821
  for (let i = 0; i < keys.length; i++) {
6767
6822
  const key = keys[i];
6768
6823
  const state = states[key];
6769
- if (entityIds.includes(state.entityId)) {
6824
+ if (entityIdsSeen.has(state.entityId)) {
6770
6825
  const update = {
6771
6826
  type: EVisualUpdateType.Update,
6772
6827
  entityId: state.entityId
@@ -6796,12 +6851,18 @@
6796
6851
  * Sets the provided settings for a given Entity's state.
6797
6852
  * @param params
6798
6853
  */
6799
- setStateValues(values) {
6854
+ setStateValues(values, fromLazyApplier = false) {
6800
6855
  const { entityId, menuItemId } = values;
6801
6856
  const keys = Object.keys(values).filter(k => k !== "entityId" && k !== "menuItemId");
6802
6857
  if (!keys.length) {
6803
6858
  return false;
6804
6859
  }
6860
+ if (!fromLazyApplier) {
6861
+ this.markLazyExclusion(entityId);
6862
+ }
6863
+ if (keys.includes("isolated")) {
6864
+ this.isolatedAnyCache = null;
6865
+ }
6805
6866
  let changed = false;
6806
6867
  const eStates = this.states[entityId] || {};
6807
6868
  this.states[entityId] = eStates;
@@ -6972,18 +7033,68 @@
6972
7033
  }
6973
7034
  return false;
6974
7035
  }
7036
+ buildClearDiffItem(state) {
7037
+ const clearState = {
7038
+ entityId: state.entityId,
7039
+ menuItemId: state.menuItemId
7040
+ };
7041
+ const refresh = {};
7042
+ // For each property that exists, set it to null to clear it.
7043
+ // This will cause setStateValues to delete the property.
7044
+ if (state.highlighted != null) {
7045
+ clearState.highlighted = null;
7046
+ refresh.highlighted = true;
7047
+ }
7048
+ if (state.selected != null) {
7049
+ clearState.selected = null;
7050
+ refresh.selected = true;
7051
+ }
7052
+ if (state.opacity != null) {
7053
+ clearState.opacity = null;
7054
+ refresh.opacity = true;
7055
+ }
7056
+ if (state.labelled != null) {
7057
+ clearState.labelled = null;
7058
+ }
7059
+ if (state.hidden != null) {
7060
+ clearState.hidden = null;
7061
+ }
7062
+ if (state.isolated != null) {
7063
+ clearState.isolated = null;
7064
+ }
7065
+ return { clearState, refresh };
7066
+ }
7067
+ /**
7068
+ * Refresh flags needed for an "add"/"update" state - the new value is applied as-is,
7069
+ * this just says which visual properties changed.
7070
+ */
7071
+ buildSetDiffRefresh(state) {
7072
+ const refresh = {};
7073
+ if (state.hasOwnProperty("highlighted")) {
7074
+ refresh.highlighted = true;
7075
+ }
7076
+ if (state.hasOwnProperty("selected")) {
7077
+ refresh.selected = true;
7078
+ }
7079
+ if (state.hasOwnProperty("opacity")) {
7080
+ refresh.opacity = true;
7081
+ }
7082
+ return refresh;
7083
+ }
6975
7084
  /**
6976
7085
  * Applies a set of new states to override the existing ones using differential updates.
6977
- * This is more efficient than clearing and re-applying all states.
6978
- * @param states
6979
7086
  */
6980
- OverrideStates(states) {
6981
- // Calculate what needs to change.
7087
+ OverrideStates(states, options) {
7088
+ this.cancelLazyOverride();
6982
7089
  const diff = this.calculateStateDifference(states);
6983
7090
  // If nothing changed, exit early.
6984
7091
  if (diff.toAdd.length === 0 && diff.toRemove.length === 0 && diff.toUpdate.length === 0) {
6985
7092
  return;
6986
7093
  }
7094
+ if (options === null || options === void 0 ? void 0 : options.lazy) {
7095
+ this.startLazyOverride(diff);
7096
+ return;
7097
+ }
6987
7098
  // Track which visual properties need refreshing per entity.
6988
7099
  const refreshMap = new Map();
6989
7100
  const updateRefreshForEntity = (entityId, props) => {
@@ -6994,76 +7105,16 @@
6994
7105
  opacity: existing.opacity || props.opacity || false
6995
7106
  });
6996
7107
  };
6997
- // Process removals.
6998
7108
  for (const state of diff.toRemove) {
6999
- // Create a state with inverted/cleared values.
7000
- const clearState = {
7001
- entityId: state.entityId,
7002
- menuItemId: state.menuItemId
7003
- };
7004
- // For each property that exists, set it to null to clear it.
7005
- // This will cause setStateValues to delete the property.
7006
- if (state.highlighted != null) {
7007
- clearState.highlighted = null;
7008
- updateRefreshForEntity(state.entityId, {
7009
- highlighted: true
7010
- });
7011
- }
7012
- if (state.selected != null) {
7013
- clearState.selected = null;
7014
- updateRefreshForEntity(state.entityId, {
7015
- selected: true
7016
- });
7017
- }
7018
- if (state.opacity != null) {
7019
- clearState.opacity = null;
7020
- updateRefreshForEntity(state.entityId, {
7021
- opacity: true
7022
- });
7023
- }
7024
- if (state.labelled != null) {
7025
- clearState.labelled = null;
7026
- updateRefreshForEntity(state.entityId, {});
7027
- }
7028
- if (state.hidden != null) {
7029
- clearState.hidden = null;
7030
- updateRefreshForEntity(state.entityId, {});
7031
- }
7032
- if (state.isolated != null) {
7033
- clearState.isolated = null;
7034
- updateRefreshForEntity(state.entityId, {});
7035
- }
7109
+ const { clearState, refresh } = this.buildClearDiffItem(state);
7110
+ updateRefreshForEntity(state.entityId, refresh);
7036
7111
  this.setStateValues(clearState);
7037
7112
  }
7038
- // Process additions and updates.
7039
7113
  const statesToSet = [...diff.toAdd, ...diff.toUpdate];
7040
7114
  for (const state of statesToSet) {
7041
- // Determine what needs visual refresh based on what's changing.
7042
- if (state.hasOwnProperty("highlighted")) {
7043
- updateRefreshForEntity(state.entityId, {
7044
- highlighted: true
7045
- });
7046
- }
7047
- if (state.hasOwnProperty("selected")) {
7048
- updateRefreshForEntity(state.entityId, {
7049
- selected: true
7050
- });
7051
- }
7052
- if (state.hasOwnProperty("opacity")) {
7053
- updateRefreshForEntity(state.entityId, {
7054
- opacity: true
7055
- });
7056
- }
7057
- if (state.hasOwnProperty("labelled")) {
7058
- updateRefreshForEntity(state.entityId, {});
7059
- }
7060
- if (state.hasOwnProperty("hidden") || state.hasOwnProperty("isolated")) {
7061
- updateRefreshForEntity(state.entityId, {});
7062
- }
7115
+ updateRefreshForEntity(state.entityId, this.buildSetDiffRefresh(state));
7063
7116
  this.setStateValues(state);
7064
7117
  }
7065
- // Queue updates for all affected entities.
7066
- // TODO: if a massive list we should async batch this.
7067
7118
  for (const entityId of diff.affectedEntityIds) {
7068
7119
  const refresh = refreshMap.get(entityId);
7069
7120
  this.queueUpdate({
@@ -7073,6 +7124,77 @@
7073
7124
  }
7074
7125
  this.viewer.scene.requestRender();
7075
7126
  }
7127
+ /**
7128
+ * Cancels any in-flight lazy OverrideStates() apply.
7129
+ */
7130
+ cancelLazyOverride() {
7131
+ if (!this.lazyOverrideActive) {
7132
+ return;
7133
+ }
7134
+ this.lazyOverrideToken++;
7135
+ this.lazyOverrideActive = false;
7136
+ this.lazyOverrideExclusions.clear();
7137
+ if (this.lazyOverrideInterval) {
7138
+ clearInterval(this.lazyOverrideInterval);
7139
+ this.lazyOverrideInterval = null;
7140
+ }
7141
+ }
7142
+ /**
7143
+ * Starts (or restarts) a chunked, cancellable apply of a previously-computed diff.
7144
+ * See OverrideStates() for the cancellation/priority semantics.
7145
+ */
7146
+ startLazyOverride(diff) {
7147
+ const BATCH_SIZE = 2000;
7148
+ const queue = [];
7149
+ for (const state of diff.toRemove) {
7150
+ queue.push({ state, isRemoval: true });
7151
+ }
7152
+ for (const state of diff.toAdd) {
7153
+ queue.push({ state, isRemoval: false });
7154
+ }
7155
+ for (const state of diff.toUpdate) {
7156
+ queue.push({ state, isRemoval: false });
7157
+ }
7158
+ this.lazyOverrideToken++;
7159
+ const token = this.lazyOverrideToken;
7160
+ this.lazyOverrideActive = true;
7161
+ this.lazyOverrideExclusions.clear();
7162
+ let index = 0;
7163
+ this.lazyOverrideInterval = setInterval(() => {
7164
+ // A newer lazy call (or a sync OverrideStates()) superseded this run.
7165
+ if (token !== this.lazyOverrideToken || this.Disposed) {
7166
+ clearInterval(this.lazyOverrideInterval);
7167
+ this.lazyOverrideInterval = null;
7168
+ return;
7169
+ }
7170
+ const end = Math.min(index + BATCH_SIZE, queue.length);
7171
+ for (; index < end; index++) {
7172
+ const { state, isRemoval } = queue[index];
7173
+ // A direct write already claimed this entity, the bookmark's still-pending
7174
+ // value for it is dropped for the rest of this run.
7175
+ if (this.lazyOverrideExclusions.has(state.entityId)) {
7176
+ continue;
7177
+ }
7178
+ if (isRemoval) {
7179
+ const { clearState, refresh } = this.buildClearDiffItem(state);
7180
+ this.setStateValues(clearState, true);
7181
+ this.queueUpdate({ entityId: state.entityId, refresh });
7182
+ }
7183
+ else {
7184
+ const refresh = this.buildSetDiffRefresh(state);
7185
+ this.setStateValues(state, true);
7186
+ this.queueUpdate({ entityId: state.entityId, refresh });
7187
+ }
7188
+ }
7189
+ this.viewer.scene.requestRender();
7190
+ if (index >= queue.length) {
7191
+ clearInterval(this.lazyOverrideInterval);
7192
+ this.lazyOverrideInterval = null;
7193
+ this.lazyOverrideActive = false;
7194
+ this.lazyOverrideExclusions.clear();
7195
+ }
7196
+ }, 10);
7197
+ }
7076
7198
  /**
7077
7199
  * Returns all states with the first detected Menu Item ID settings included.
7078
7200
  * This is a utility function for returning settings for apps that don't support the newer state settings.
@@ -7088,9 +7210,8 @@
7088
7210
  if (!eStates) {
7089
7211
  continue;
7090
7212
  }
7091
- if (!entityIds.includes(entityId)) {
7092
- entityIds.push(entityId);
7093
- }
7213
+ // Keys of this.states are already unique, no dedupe check needed.
7214
+ entityIds.push(entityId);
7094
7215
  const mKeys = Object.keys(eStates);
7095
7216
  for (let i = 0; i < mKeys.length; i++) {
7096
7217
  const menuItemId = mKeys[i];
@@ -7156,6 +7277,9 @@
7156
7277
  this.cameraCullerDispose = null;
7157
7278
  clearInterval(this.updateQueueInterval);
7158
7279
  this.updateQueueInterval = null;
7280
+ clearInterval(this.lazyOverrideInterval);
7281
+ this.lazyOverrideInterval = null;
7282
+ this.lazyOverrideActive = false;
7159
7283
  this.disposed = true;
7160
7284
  }
7161
7285
  ForceUpdate(params) {
@@ -7225,12 +7349,15 @@
7225
7349
  */
7226
7350
  ClearLabelled(params) {
7227
7351
  const toUpdateIds = [];
7352
+ const toUpdateIdsSeen = new Set();
7228
7353
  this.ForEachState((state) => {
7229
7354
  if (state.labelled == null || state.labelled === undefined) {
7230
7355
  return;
7231
7356
  }
7357
+ this.markLazyExclusion(state.entityId);
7232
7358
  delete state.labelled;
7233
- if (!toUpdateIds.includes(state.entityId)) {
7359
+ if (!toUpdateIdsSeen.has(state.entityId)) {
7360
+ toUpdateIdsSeen.add(state.entityId);
7234
7361
  toUpdateIds.push(state.entityId);
7235
7362
  }
7236
7363
  });
@@ -7358,11 +7485,12 @@
7358
7485
  }
7359
7486
  ClearSelected(params) {
7360
7487
  var _a;
7361
- const cleared = [];
7488
+ const cleared = new Set();
7362
7489
  this.ForEachState((state) => {
7363
7490
  if (state.selected == null || state.selected === undefined) {
7364
7491
  return;
7365
7492
  }
7493
+ this.markLazyExclusion(state.entityId);
7366
7494
  delete state.selected;
7367
7495
  const regos = this.GetRegos({
7368
7496
  entityId: state.entityId,
@@ -7370,13 +7498,13 @@
7370
7498
  });
7371
7499
  for (let i = 0; i < regos.length; i++) {
7372
7500
  const rego = regos[i];
7373
- if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.includes(rego)) {
7501
+ if ((rego === null || rego === void 0 ? void 0 : rego.visual) && !cleared.has(rego)) {
7374
7502
  exports.CesiumEntityStyler.Deselect({
7375
7503
  entity: rego.visual,
7376
7504
  viewer: this.viewer,
7377
7505
  requestRender: false
7378
7506
  });
7379
- cleared.push(rego);
7507
+ cleared.add(rego);
7380
7508
  }
7381
7509
  }
7382
7510
  });
@@ -7489,10 +7617,13 @@
7489
7617
  var _a;
7490
7618
  // Null all states.
7491
7619
  let entityIds = [];
7620
+ const entityIdsSeen = new Set();
7492
7621
  this.ForEachState((state) => {
7493
7622
  if (state.highlighted) {
7623
+ this.markLazyExclusion(state.entityId);
7494
7624
  delete state.highlighted;
7495
- if (!entityIds.includes(state.entityId)) {
7625
+ if (!entityIdsSeen.has(state.entityId)) {
7626
+ entityIdsSeen.add(state.entityId);
7496
7627
  entityIds.push(state.entityId);
7497
7628
  }
7498
7629
  }
@@ -7581,7 +7712,10 @@
7581
7712
  return state.isolated === true;
7582
7713
  }
7583
7714
  GetIsIsolatedAny() {
7584
- return Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
7715
+ if (this.isolatedAnyCache == null) {
7716
+ this.isolatedAnyCache = Object.values(this.states).some(state => state && Object.values(state).some(item => item.isolated));
7717
+ }
7718
+ return this.isolatedAnyCache;
7585
7719
  }
7586
7720
  /**
7587
7721
  * @deprecated Use GetStates() or GetState().
@@ -7602,10 +7736,15 @@
7602
7736
  this.ForEachState((state) => {
7603
7737
  if (state.isolated) {
7604
7738
  changed = true;
7739
+ this.markLazyExclusion(state.entityId);
7605
7740
  }
7606
7741
  delete state.isolated;
7607
7742
  });
7608
- if (changed && params.doUpdate !== false) {
7743
+ if (changed) {
7744
+ // Mutated `isolated` directly rather than through setStateValues().
7745
+ this.isolatedAnyCache = false;
7746
+ }
7747
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
7609
7748
  this.updateAllEntities({
7610
7749
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
7611
7750
  refresh: true
@@ -7656,10 +7795,11 @@
7656
7795
  this.ForEachState((state) => {
7657
7796
  if (state.hidden) {
7658
7797
  changed = true;
7798
+ this.markLazyExclusion(state.entityId);
7659
7799
  }
7660
7800
  delete state.hidden;
7661
7801
  });
7662
- if (changed && params.doUpdate !== false) {
7802
+ if (changed && (params === null || params === void 0 ? void 0 : params.doUpdate) !== false) {
7663
7803
  this.updateAllEntities({
7664
7804
  requestRender: params === null || params === void 0 ? void 0 : params.requestRender,
7665
7805
  refresh: true
@@ -7695,6 +7835,7 @@
7695
7835
  const entityRegos = (_a = this.rego[entityId]) !== null && _a !== void 0 ? _a : [];
7696
7836
  entityRegos.push(rego);
7697
7837
  this.rego[entityId] = entityRegos;
7838
+ this.indexMenuItemEntity(rego.menuItemId, entityId);
7698
7839
  // Mark the visual as part of this register so selection works.
7699
7840
  markEntity(this, rego, rego.visual, false);
7700
7841
  // Run any updates on the visual based on the calculated state.
@@ -7718,6 +7859,74 @@
7718
7859
  this.viewer.scene.requestRender();
7719
7860
  }
7720
7861
  }
7862
+ /**
7863
+ * Re-parents every rego registered under one menu item id to a different menu item id.
7864
+ */
7865
+ ReassignMenuItem(params) {
7866
+ var _a, _b;
7867
+ const { fromMenuItemId, toMenuItemId, toMenuItemType, toPriority, requestRender, source } = params;
7868
+ if (!fromMenuItemId || !toMenuItemId || fromMenuItemId === toMenuItemId) {
7869
+ return;
7870
+ }
7871
+ const entityIds = this.entityIdsByMenuItem[fromMenuItemId];
7872
+ if (!entityIds) {
7873
+ return;
7874
+ }
7875
+ // Snapshot: unindexMenuItemEntityIfAbsent() below mutates this same set.
7876
+ for (const entityId of Array.from(entityIds)) {
7877
+ const regos = this.rego[entityId];
7878
+ if (!regos) {
7879
+ continue;
7880
+ }
7881
+ for (const rego of regos) {
7882
+ if (rego.menuItemId !== fromMenuItemId) {
7883
+ continue;
7884
+ }
7885
+ if (source !== EUpdateSource.TREE_CASCADE) {
7886
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger({
7887
+ type: EVisualUpdateType.Remove,
7888
+ entityId,
7889
+ rego: { ...rego, menuItemId: fromMenuItemId },
7890
+ source
7891
+ });
7892
+ }
7893
+ rego.menuItemId = toMenuItemId;
7894
+ rego.menuItemType = toMenuItemType;
7895
+ if (toPriority != null) {
7896
+ rego.priority = toPriority;
7897
+ }
7898
+ if (source !== EUpdateSource.TREE_CASCADE) {
7899
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({
7900
+ type: EVisualUpdateType.Add,
7901
+ entityId,
7902
+ rego,
7903
+ source
7904
+ });
7905
+ }
7906
+ }
7907
+ const eStates = this.states[entityId];
7908
+ const movingState = eStates === null || eStates === void 0 ? void 0 : eStates[fromMenuItemId];
7909
+ if (movingState) {
7910
+ delete eStates[fromMenuItemId];
7911
+ const existingTargetState = eStates[toMenuItemId];
7912
+ eStates[toMenuItemId] = {
7913
+ ...existingTargetState,
7914
+ ...movingState,
7915
+ entityId,
7916
+ menuItemId: toMenuItemId
7917
+ };
7918
+ }
7919
+ this.unindexMenuItemEntityIfAbsent(fromMenuItemId, entityId);
7920
+ this.indexMenuItemEntity(toMenuItemId, entityId);
7921
+ this.queueUpdate({
7922
+ entityId,
7923
+ refresh: true
7924
+ });
7925
+ }
7926
+ if (requestRender != false) {
7927
+ this.viewer.scene.requestRender();
7928
+ }
7929
+ }
7721
7930
  /**
7722
7931
  * Locates a visual corresponding to a given entity id.
7723
7932
  * If no menu item id is provided, then it will pick the "best" one.
@@ -7828,7 +8037,10 @@
7828
8037
  // TODO: refactor.
7829
8038
  // Currently this was made by merging two functions.
7830
8039
  if (menuItemId && !entityId) {
7831
- for (const entityId in this.rego) {
8040
+ // Entities registered under this menu item, keyed via entityIdsByMenuItem.
8041
+ // Snapshot to an array since removals below mutate the backing set.
8042
+ const entityIds = this.entityIdsByMenuItem[menuItemId] ? Array.from(this.entityIdsByMenuItem[menuItemId]) : [];
8043
+ for (const entityId of entityIds) {
7832
8044
  const entityRegos = this.rego[entityId];
7833
8045
  if (entityRegos) {
7834
8046
  const rego = entityRegos.find(r => r.menuItemId === menuItemId);
@@ -7850,6 +8062,7 @@
7850
8062
  const doesInclude = this.rego[entityId].find(r => r.menuItemId === menuItemId);
7851
8063
  if (doesInclude) {
7852
8064
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8065
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
7853
8066
  }
7854
8067
  const update = {
7855
8068
  type: EVisualUpdateType.Remove,
@@ -7902,6 +8115,7 @@
7902
8115
  (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger(update);
7903
8116
  }
7904
8117
  this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8118
+ this.unindexMenuItemEntityIfAbsent(menuItemId, entityId);
7905
8119
  if (doUpdate && menuItemId) {
7906
8120
  this.queueUpdate({
7907
8121
  entityId: entityId,
@@ -7924,7 +8138,11 @@
7924
8138
  });
7925
8139
  removeEntity(this.viewer, rego.visual);
7926
8140
  rego.visual = null;
7927
- this.rego[entityId] = entityRegos.filter(r => r.menuItemId !== menuItemId);
8141
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
8142
+ if (!this.rego[entityId].length) {
8143
+ delete this.rego[entityId];
8144
+ }
8145
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
7928
8146
  const update = {
7929
8147
  type: EVisualUpdateType.Remove,
7930
8148
  entityId: rego.entityId,
@@ -7936,7 +8154,7 @@
7936
8154
  if (source !== EUpdateSource.TREE_CASCADE) {
7937
8155
  (_c = this.onUpdate) === null || _c === void 0 ? void 0 : _c.Trigger(update);
7938
8156
  }
7939
- if (doUpdate && menuItemId) {
8157
+ if (doUpdate) {
7940
8158
  this.queueUpdate({
7941
8159
  entityId: entityId,
7942
8160
  refresh: false
@@ -7953,68 +8171,68 @@
7953
8171
  * @param params
7954
8172
  */
7955
8173
  RemoveRegosByVisuals(params) {
7956
- var _a, _b, _c;
8174
+ var _a, _b;
7957
8175
  const { doRemove = true, requestRender = true, source, doUpdate, menuItemId } = params;
7958
- const entityIds = Object.keys(this.rego);
7959
- const removedEntityIds = [];
7960
- for (const entityId of entityIds) {
7961
- const regos = this.rego[entityId];
7962
- if (regos && regos.length) {
7963
- for (const rego of regos) {
7964
- if (params.visuals.includes(rego.visual)) {
7965
- exports.EntityLabel.Detatch({
7966
- rego
7967
- });
7968
- if (doRemove != false) {
7969
- removeEntity(this.viewer, rego.visual);
7970
- }
7971
- rego.visual = null;
7972
- this.rego[entityId] = regos.filter(r => r.visual !== rego.visual);
7973
- const update = {
7974
- type: EVisualUpdateType.Remove,
7975
- entityId: rego.entityId,
7976
- rego: rego
7977
- };
7978
- if (source) {
7979
- update.source = source;
7980
- }
7981
- if (source !== EUpdateSource.TREE_CASCADE) {
7982
- (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
7983
- }
7984
- if (doUpdate && menuItemId) {
7985
- this.queueUpdate({
7986
- entityId: entityId,
7987
- refresh: false
7988
- });
7989
- }
7990
- if (!removedEntityIds.includes(rego.entityId)) {
7991
- removedEntityIds.push(rego.entityId);
7992
- }
7993
- }
7994
- // Check siblings.
7995
- else if ((_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"]) {
7996
- let siblings = rego.visual["_siblingGraphics"];
7997
- if (doRemove != false) {
7998
- for (const sibling of siblings) {
7999
- if (params.visuals.includes(sibling)) {
8000
- removeEntity(this.viewer, sibling);
8001
- }
8002
- }
8003
- }
8004
- siblings = siblings.filter(s => !params.visuals.includes(s));
8005
- rego.visual["_siblingGraphics"] = siblings;
8006
- }
8176
+ const removedEntityIds = new Set();
8177
+ for (const visual of params.visuals) {
8178
+ const visExt = visual;
8179
+ const rego = (visExt === null || visExt === void 0 ? void 0 : visExt._register) === this ? visExt._rego : null;
8180
+ if (!rego || !rego.visual) {
8181
+ continue;
8182
+ }
8183
+ const entityId = rego.entityId;
8184
+ const entityRegos = this.rego[entityId];
8185
+ if (!entityRegos) {
8186
+ continue;
8187
+ }
8188
+ if (rego.visual === visual) {
8189
+ // The primary graphic for this rego - remove the rego entirely.
8190
+ exports.EntityLabel.Detatch({
8191
+ rego
8192
+ });
8193
+ if (doRemove != false) {
8194
+ removeEntity(this.viewer, rego.visual);
8195
+ }
8196
+ rego.visual = null;
8197
+ this.rego[entityId] = entityRegos.filter(r => r !== rego);
8198
+ if (!this.rego[entityId].length) {
8199
+ delete this.rego[entityId];
8200
+ }
8201
+ this.unindexMenuItemEntityIfAbsent(rego.menuItemId, entityId);
8202
+ const update = {
8203
+ type: EVisualUpdateType.Remove,
8204
+ entityId: rego.entityId,
8205
+ rego: rego
8206
+ };
8207
+ if (source) {
8208
+ update.source = source;
8209
+ }
8210
+ if (source !== EUpdateSource.TREE_CASCADE) {
8211
+ (_a = this.onUpdate) === null || _a === void 0 ? void 0 : _a.Trigger(update);
8212
+ }
8213
+ if (doUpdate && menuItemId) {
8214
+ this.queueUpdate({
8215
+ entityId: entityId,
8216
+ refresh: false
8217
+ });
8007
8218
  }
8219
+ removedEntityIds.add(entityId);
8008
8220
  }
8009
- if (!((_c = this.rego[entityId]) === null || _c === void 0 ? void 0 : _c.length)) {
8010
- delete this.rego[entityId];
8221
+ else {
8222
+ const siblings = (_b = rego.visual) === null || _b === void 0 ? void 0 : _b["_siblingGraphics"];
8223
+ if (siblings === null || siblings === void 0 ? void 0 : siblings.length) {
8224
+ if (doRemove != false && siblings.includes(visual)) {
8225
+ removeEntity(this.viewer, visual);
8226
+ }
8227
+ rego.visual["_siblingGraphics"] = siblings.filter(s => s !== visual);
8228
+ }
8011
8229
  }
8012
8230
  }
8013
8231
  if (requestRender) {
8014
8232
  this.viewer.scene.requestRender();
8015
8233
  }
8016
8234
  return {
8017
- removedEntityIds
8235
+ removedEntityIds: Array.from(removedEntityIds)
8018
8236
  };
8019
8237
  }
8020
8238
  /**
@@ -8170,16 +8388,17 @@
8170
8388
  return (totalOpacity && totalTicks) ? (totalOpacity / totalTicks) : totalOpacity != null && typeof totalOpacity == "number" ? totalOpacity : 1;
8171
8389
  }
8172
8390
  ClearOpacity(params) {
8173
- const clearedIds = [];
8391
+ const clearedIds = new Set();
8174
8392
  this.ForEachState((state) => {
8175
- if (!clearedIds.includes(state.entityId) && state.opacity != null) {
8393
+ if (!clearedIds.has(state.entityId) && state.opacity != null) {
8394
+ this.markLazyExclusion(state.entityId);
8176
8395
  this.queueUpdate({
8177
8396
  entityId: state.entityId,
8178
8397
  refresh: {
8179
8398
  opacity: true
8180
8399
  }
8181
8400
  });
8182
- clearedIds.push(state.entityId);
8401
+ clearedIds.add(state.entityId);
8183
8402
  }
8184
8403
  delete state.opacity;
8185
8404
  });
@@ -13786,6 +14005,14 @@
13786
14005
  this.disposed = false;
13787
14006
  this.modelSpace = false;
13788
14007
  this.cTileset = null;
14008
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
14009
+ this.tileset = null;
14010
+ // Removal callbacks for this instance's own tile stream listeners.
14011
+ this.tileLoadRemoval = null;
14012
+ this.tileUnloadRemoval = null;
14013
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
14014
+ // synchronous pass over potentially millions of regos.
14015
+ this.handoffRestyleInterval = null;
13789
14016
  this.styler = new exports.TilesetRenderEngine.Styler();
13790
14017
  this.modelTreeUpdate = new BModels.BruceEvent();
13791
14018
  // Quick look-up of the model tree nodes by entity/geomId.
@@ -13892,6 +14119,7 @@
13892
14119
  if (!tileset || this.disposed) {
13893
14120
  return;
13894
14121
  }
14122
+ this.tileset = tileset;
13895
14123
  if (!api) {
13896
14124
  api = this.getters.GetBruceApi({
13897
14125
  accountId: accountId
@@ -14058,7 +14286,7 @@
14058
14286
  console.error(e);
14059
14287
  }
14060
14288
  });
14061
- cTileset.tileLoad.addEventListener((tile) => {
14289
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
14062
14290
  try {
14063
14291
  this.queueTile(tile, true);
14064
14292
  }
@@ -14066,7 +14294,7 @@
14066
14294
  console.error(e);
14067
14295
  }
14068
14296
  });
14069
- cTileset.tileUnload.addEventListener((tile) => {
14297
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
14070
14298
  try {
14071
14299
  this.queueTile(tile, false);
14072
14300
  }
@@ -14086,6 +14314,137 @@
14086
14314
  this.viewer.zoomTo(this.cTileset, new Cesium.HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
14087
14315
  }
14088
14316
  }
14317
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
14318
+ get HandoffKey() {
14319
+ return Manager.GetHandoffKey(this.item);
14320
+ }
14321
+ static GetHandoffKey(item) {
14322
+ var _a, _b;
14323
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
14324
+ if (!tilesetId) {
14325
+ return null;
14326
+ }
14327
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
14328
+ return `${accountId}:${tilesetId}`;
14329
+ }
14330
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
14331
+ PrepareHandoff() {
14332
+ var _a, _b;
14333
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
14334
+ return null;
14335
+ }
14336
+ // Drain anything mid-flight, once handed off, this instance no longer owns the tile events.
14337
+ while (this.featureQueue.length) {
14338
+ this.processFeatureQueueBatch();
14339
+ }
14340
+ if (this.featureQueueInterval) {
14341
+ clearInterval(this.featureQueueInterval);
14342
+ this.featureQueueInterval = null;
14343
+ }
14344
+ if (this.handoffRestyleInterval) {
14345
+ clearInterval(this.handoffRestyleInterval);
14346
+ this.handoffRestyleInterval = null;
14347
+ }
14348
+ // Not transferred, the receiving manager sets up its own fresh copy if needed.
14349
+ this.viewerDateTimeDispose();
14350
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
14351
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
14352
+ this.tileLoadRemoval = null;
14353
+ this.tileUnloadRemoval = null;
14354
+ const payload = {
14355
+ cTileset: this.cTileset,
14356
+ tileset: this.tileset,
14357
+ rootId: this.rootId,
14358
+ modelTree: this.modelTree,
14359
+ treeNodeByGeomId: this.treeNodeByGeomId,
14360
+ treeNodeByEntityId: this.treeNodeByEntityId,
14361
+ loadedCesiumEntities: this.loadedCesiumEntities,
14362
+ sourceMenuItemId: this.item.id
14363
+ };
14364
+ // Not routed through Dispose(), that would remove the tileset/regos just handed off.
14365
+ this.cTileset = null;
14366
+ this.disposed = true;
14367
+ return payload;
14368
+ }
14369
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
14370
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
14371
+ AdoptHandoff(payload) {
14372
+ var _a, _b, _c, _d;
14373
+ this.cTileset = payload.cTileset;
14374
+ this.tileset = payload.tileset;
14375
+ this.rootId = payload.rootId;
14376
+ this.modelTree = payload.modelTree;
14377
+ this.treeNodeByGeomId = payload.treeNodeByGeomId;
14378
+ this.treeNodeByEntityId = payload.treeNodeByEntityId;
14379
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
14380
+ this.renderPriority = this.item.renderPriority;
14381
+ if (this.renderPriority == null) {
14382
+ this.renderPriority = 0;
14383
+ }
14384
+ // Same visuals, same registrations - just re-keyed onto this menu item.
14385
+ this.visualsManager.ReassignMenuItem({
14386
+ fromMenuItemId: payload.sourceMenuItemId,
14387
+ toMenuItemId: this.item.id,
14388
+ toMenuItemType: this.item.Type,
14389
+ toPriority: this.renderPriority,
14390
+ requestRender: false
14391
+ });
14392
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
14393
+ try {
14394
+ this.queueTile(tile, true);
14395
+ }
14396
+ catch (e) {
14397
+ console.error(e);
14398
+ }
14399
+ });
14400
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
14401
+ try {
14402
+ this.queueTile(tile, false);
14403
+ }
14404
+ catch (e) {
14405
+ console.error(e);
14406
+ }
14407
+ });
14408
+ this.onCTilesetLoad();
14409
+ this.styler.Init({
14410
+ viewer: this.viewer,
14411
+ api: this.getters.GetBruceApi(),
14412
+ cTileset: this.cTileset,
14413
+ fallbackStyleId: this.item.styleId,
14414
+ styleMapping: this.item.StyleMapping,
14415
+ expandSources: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.ExpandSources,
14416
+ menuItemId: this.item.id,
14417
+ register: this.visualsManager,
14418
+ scenario: (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.Scenario,
14419
+ historic: (_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic,
14420
+ });
14421
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
14422
+ this.viewer.scene.requestRender();
14423
+ if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
14424
+ this.viewerDateTimeSub(this.tileset);
14425
+ }
14426
+ }
14427
+ queueHandoffRestyle(regos) {
14428
+ if (!regos.length) {
14429
+ return;
14430
+ }
14431
+ const BATCH_SIZE = 5000;
14432
+ let index = 0;
14433
+ this.handoffRestyleInterval = setInterval(() => {
14434
+ if (this.disposed) {
14435
+ clearInterval(this.handoffRestyleInterval);
14436
+ this.handoffRestyleInterval = null;
14437
+ return;
14438
+ }
14439
+ const end = Math.min(index + BATCH_SIZE, regos.length);
14440
+ this.styler.QueueEntities(regos.slice(index, end), true);
14441
+ index = end;
14442
+ if (index >= regos.length) {
14443
+ clearInterval(this.handoffRestyleInterval);
14444
+ this.handoffRestyleInterval = null;
14445
+ }
14446
+ }, 10);
14447
+ }
14089
14448
  /**
14090
14449
  * @param tile
14091
14450
  * @param load indicates if we are loading or unloading the tile.
@@ -14098,7 +14457,7 @@
14098
14457
  }
14099
14458
  for (let i = 0; i < content.featuresLength; i++) {
14100
14459
  const feature = content.getFeature(i);
14101
- if (!this.featureQueue.includes(feature)) {
14460
+ if (!this.featureQueueStates.has(feature)) {
14102
14461
  this.featureQueue.push(feature);
14103
14462
  }
14104
14463
  this.featureQueueStates.set(feature, load);
@@ -14635,6 +14994,10 @@
14635
14994
  this.featureQueueInterval = null;
14636
14995
  this.featureQueue = [];
14637
14996
  }
14997
+ if (this.handoffRestyleInterval) {
14998
+ clearInterval(this.handoffRestyleInterval);
14999
+ this.handoffRestyleInterval = null;
15000
+ }
14638
15001
  if (this.cTileset) {
14639
15002
  const viewer = this.viewer;
14640
15003
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17282,6 +17645,14 @@
17282
17645
  this.initCounter = 0;
17283
17646
  this.disposed = false;
17284
17647
  this.cTileset = null;
17648
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
17649
+ this.tileset = null;
17650
+ // Removal callbacks for this instance's own tile stream listeners.
17651
+ this.tileLoadRemoval = null;
17652
+ this.tileUnloadRemoval = null;
17653
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
17654
+ // synchronous pass over potentially millions of regos.
17655
+ this.handoffRestyleInterval = null;
17285
17656
  this.styler = new exports.TilesetRenderEngine.Styler();
17286
17657
  // Entity ID -> rego.
17287
17658
  // We retain this information as a quick look-up on what has been registered.
@@ -17369,6 +17740,7 @@
17369
17740
  if (!tileset || this.disposed || counter !== this.initCounter) {
17370
17741
  return;
17371
17742
  }
17743
+ this.tileset = tileset;
17372
17744
  this.typeId = tileset.settings.entityTypeId;
17373
17745
  // Other account so we'll prefer the fallback entity-type ID if it's set.
17374
17746
  if (accountId != this.getters.GetAccountId()) {
@@ -17417,7 +17789,7 @@
17417
17789
  console.error(e);
17418
17790
  }
17419
17791
  });
17420
- cTileset.tileLoad.addEventListener((tile) => {
17792
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
17421
17793
  try {
17422
17794
  this.queueTile(tile, true);
17423
17795
  }
@@ -17425,7 +17797,7 @@
17425
17797
  console.error(e);
17426
17798
  }
17427
17799
  });
17428
- cTileset.tileUnload.addEventListener((tile) => {
17800
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
17429
17801
  try {
17430
17802
  this.queueTile(tile, false);
17431
17803
  }
@@ -17466,7 +17838,7 @@
17466
17838
  }
17467
17839
  for (let i = 0; i < content.featuresLength; i++) {
17468
17840
  const feature = content.getFeature(i);
17469
- if (!this.featureQueue.includes(feature)) {
17841
+ if (!this.featureQueueStates.has(feature)) {
17470
17842
  this.featureQueue.push(feature);
17471
17843
  }
17472
17844
  this.featureQueueStates.set(feature, load);
@@ -17585,6 +17957,10 @@
17585
17957
  this.featureQueueInterval = null;
17586
17958
  this.featureQueue = [];
17587
17959
  }
17960
+ if (this.handoffRestyleInterval) {
17961
+ clearInterval(this.handoffRestyleInterval);
17962
+ this.handoffRestyleInterval = null;
17963
+ }
17588
17964
  if (this.cTileset) {
17589
17965
  const viewer = this.viewer;
17590
17966
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17605,6 +17981,141 @@
17605
17981
  this.viewer.zoomTo(this.cTileset, new Cesium.HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
17606
17982
  }
17607
17983
  }
17984
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
17985
+ get HandoffKey() {
17986
+ return Manager.GetHandoffKey(this.item);
17987
+ }
17988
+ static GetHandoffKey(item) {
17989
+ var _a, _b;
17990
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
17991
+ if (!tilesetId) {
17992
+ return null;
17993
+ }
17994
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
17995
+ return `${accountId}:${tilesetId}`;
17996
+ }
17997
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
17998
+ PrepareHandoff() {
17999
+ var _a, _b;
18000
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
18001
+ return null;
18002
+ }
18003
+ // Drain anything mid-flight - once handed off, this instance no longer owns the tile events.
18004
+ while (this.featureQueue.length) {
18005
+ this.processFeatureQueueBatch();
18006
+ }
18007
+ if (this.featureQueueInterval) {
18008
+ clearInterval(this.featureQueueInterval);
18009
+ this.featureQueueInterval = null;
18010
+ }
18011
+ if (this.handoffRestyleInterval) {
18012
+ clearInterval(this.handoffRestyleInterval);
18013
+ this.handoffRestyleInterval = null;
18014
+ }
18015
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
18016
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
18017
+ this.tileLoadRemoval = null;
18018
+ this.tileUnloadRemoval = null;
18019
+ const payload = {
18020
+ cTileset: this.cTileset,
18021
+ tileset: this.tileset,
18022
+ typeId: this.typeId,
18023
+ loadedCesiumEntities: this.loadedCesiumEntities,
18024
+ sourceMenuItemId: this.item.id
18025
+ };
18026
+ // Not routed through Dispose() - that would remove the tileset/regos just handed off.
18027
+ this.cTileset = null;
18028
+ this.disposed = true;
18029
+ return payload;
18030
+ }
18031
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
18032
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
18033
+ AdoptHandoff(payload) {
18034
+ var _a;
18035
+ this.cTileset = payload.cTileset;
18036
+ this.tileset = payload.tileset;
18037
+ this.typeId = payload.typeId;
18038
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
18039
+ this.renderPriority = this.item.renderPriority;
18040
+ if (this.renderPriority == null) {
18041
+ this.renderPriority = 0;
18042
+ }
18043
+ this.visualsManager.ReassignMenuItem({
18044
+ fromMenuItemId: payload.sourceMenuItemId,
18045
+ toMenuItemId: this.item.id,
18046
+ toMenuItemType: this.item.Type,
18047
+ toPriority: this.renderPriority,
18048
+ requestRender: false
18049
+ });
18050
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
18051
+ try {
18052
+ this.queueTile(tile, true);
18053
+ }
18054
+ catch (e) {
18055
+ console.error(e);
18056
+ }
18057
+ });
18058
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
18059
+ try {
18060
+ this.queueTile(tile, false);
18061
+ }
18062
+ catch (e) {
18063
+ console.error(e);
18064
+ }
18065
+ });
18066
+ if (this.item.ApplyStyles) {
18067
+ this.styler.Init({
18068
+ viewer: this.viewer,
18069
+ api: this.getters.GetBruceApi(),
18070
+ cTileset: this.cTileset,
18071
+ fallbackStyleId: this.item.styleId,
18072
+ styleMapping: [],
18073
+ expandSources: false,
18074
+ menuItemId: this.item.id,
18075
+ register: this.visualsManager,
18076
+ historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
18077
+ });
18078
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
18079
+ }
18080
+ this.onCTilesetLoad();
18081
+ let attenuation = this.item.attenuation;
18082
+ if (!attenuation && attenuation != false) {
18083
+ attenuation = true;
18084
+ }
18085
+ let attenuationMax = this.item.attenuationMax;
18086
+ if (isNaN(attenuationMax)) {
18087
+ attenuationMax = 20;
18088
+ }
18089
+ exports.TilesetRenderEngine.ApplySettings({
18090
+ cTileset: this.cTileset,
18091
+ settings: {
18092
+ attenuation: attenuation,
18093
+ maximumAttenuation: attenuationMax
18094
+ }
18095
+ });
18096
+ this.viewer.scene.requestRender();
18097
+ }
18098
+ queueHandoffRestyle(regos) {
18099
+ if (!regos.length) {
18100
+ return;
18101
+ }
18102
+ const BATCH_SIZE = 5000;
18103
+ let index = 0;
18104
+ this.handoffRestyleInterval = setInterval(() => {
18105
+ if (this.disposed) {
18106
+ clearInterval(this.handoffRestyleInterval);
18107
+ this.handoffRestyleInterval = null;
18108
+ return;
18109
+ }
18110
+ const end = Math.min(index + BATCH_SIZE, regos.length);
18111
+ this.styler.QueueEntities(regos.slice(index, end), true);
18112
+ index = end;
18113
+ if (index >= regos.length) {
18114
+ clearInterval(this.handoffRestyleInterval);
18115
+ this.handoffRestyleInterval = null;
18116
+ }
18117
+ }, 10);
18118
+ }
17608
18119
  mapTilesetFeature(feature) {
17609
18120
  var _a, _b, _c;
17610
18121
  this.evaluateFeatureProps(feature);
@@ -18439,7 +18950,7 @@
18439
18950
  }
18440
18951
  for (let i = 0; i < content.featuresLength; i++) {
18441
18952
  const feature = content.getFeature(i);
18442
- if (!this.featureQueue.includes(feature)) {
18953
+ if (!this.featureQueueStates.has(feature)) {
18443
18954
  this.featureQueue.push(feature);
18444
18955
  }
18445
18956
  this.featureQueueStates.set(feature, load);
@@ -21153,6 +21664,7 @@
21153
21664
  }
21154
21665
  constructor(params) {
21155
21666
  this.items = [];
21667
+ this.pendingHandoffPool = [];
21156
21668
  this.onUpdate = null;
21157
21669
  let { viewer, visualsRegister, getters } = params;
21158
21670
  this.viewer = viewer;
@@ -21176,7 +21688,7 @@
21176
21688
  * This will dispose all render managers and unregister all menu items.
21177
21689
  */
21178
21690
  Dispose(params) {
21179
- var _a;
21691
+ var _a, _b;
21180
21692
  const { disposeRegister } = params !== null && params !== void 0 ? params : {};
21181
21693
  for (let i = 0; i < this.items.length; i++) {
21182
21694
  const item = this.items[i];
@@ -21188,7 +21700,9 @@
21188
21700
  }
21189
21701
  }
21190
21702
  this.items = [];
21703
+ this.FlushPendingHandoffs();
21191
21704
  this.tilesetInitQueue.Dispose();
21705
+ (_b = this.sharedMonitor) === null || _b === void 0 ? void 0 : _b.Dispose();
21192
21706
  if (this.visualsRegister && disposeRegister != false) {
21193
21707
  this.visualsRegister.Dispose();
21194
21708
  }
@@ -21537,8 +22051,14 @@
21537
22051
  console.error("Menu item type is not implemented.", params.item.Type);
21538
22052
  }
21539
22053
  if (rItem.renderManager && !rItem.renderManager.Disposed) {
22054
+ const handoffPayload = this.tryClaimHandoffPayload(params.item, rItem.renderManager);
21540
22055
  try {
21541
- rItem.renderManager.Init();
22056
+ if (handoffPayload) {
22057
+ rItem.renderManager.AdoptHandoff(handoffPayload);
22058
+ }
22059
+ else {
22060
+ rItem.renderManager.Init();
22061
+ }
21542
22062
  }
21543
22063
  catch (e) {
21544
22064
  rItem.errors.push(e);
@@ -21569,7 +22089,7 @@
21569
22089
  */
21570
22090
  RemoveItemById(params) {
21571
22091
  var _a, _b;
21572
- let { menuItemId: id, recursive } = params;
22092
+ let { menuItemId: id, recursive, allowHandoff } = params;
21573
22093
  if (this.viewer.isDestroyed()) {
21574
22094
  return;
21575
22095
  }
@@ -21583,19 +22103,79 @@
21583
22103
  const child = this.items.find(x => x.id === item.childIds[i]);
21584
22104
  if (child) {
21585
22105
  this.RemoveItemById({
21586
- menuItemId: child.id
22106
+ menuItemId: child.id,
22107
+ allowHandoff
21587
22108
  });
21588
22109
  }
21589
22110
  }
21590
22111
  }
22112
+ if (allowHandoff && this.isHandoffCapable(item.renderManager)) {
22113
+ this.pendingHandoffPool.push(item);
22114
+ }
22115
+ else {
22116
+ try {
22117
+ (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
22118
+ }
22119
+ catch (e) {
22120
+ console.error(e);
22121
+ }
22122
+ }
22123
+ this.items = this.items.filter(x => x.id !== id);
22124
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
22125
+ }
22126
+ }
22127
+ isHandoffCapable(renderManager) {
22128
+ return Boolean(renderManager) && typeof renderManager.PrepareHandoff === "function";
22129
+ }
22130
+ // Resolves the hand-off key a menu item's data would have.
22131
+ computeHandoffKey(item) {
22132
+ if (item.Type === BModels.MenuItem.EType.CadTileset) {
22133
+ return exports.TilesetCadRenderManager.Manager.GetHandoffKey(item);
22134
+ }
22135
+ if (item.Type === BModels.MenuItem.EType.EntityTileset) {
22136
+ return exports.TilesetEntitiesRenderManager.Manager.GetHandoffKey(item);
22137
+ }
22138
+ return null;
22139
+ }
22140
+ // Claims a pooled manager for the same resource, if one exists and both sides opt in.
22141
+ // Returns null (meaning: construct normally via Init()) otherwise.
22142
+ tryClaimHandoffPayload(item, newManager) {
22143
+ if (typeof newManager.AdoptHandoff !== "function") {
22144
+ return null;
22145
+ }
22146
+ const key = this.computeHandoffKey(item);
22147
+ if (!key) {
22148
+ return null;
22149
+ }
22150
+ const poolIndex = this.pendingHandoffPool.findIndex(pooled => pooled.type === item.Type &&
22151
+ this.isHandoffCapable(pooled.renderManager) &&
22152
+ pooled.renderManager.HandoffKey === key);
22153
+ if (poolIndex < 0) {
22154
+ return null;
22155
+ }
22156
+ const pooled = this.pendingHandoffPool[poolIndex].renderManager;
22157
+ const payload = pooled.PrepareHandoff();
22158
+ if (!payload) {
22159
+ return null;
22160
+ }
22161
+ this.pendingHandoffPool.splice(poolIndex, 1);
22162
+ return payload;
22163
+ }
22164
+ // Disposes everything left unclaimed in the pending-hand-off pool.
22165
+ FlushPendingHandoffs() {
22166
+ var _a;
22167
+ if (!this.pendingHandoffPool.length) {
22168
+ return;
22169
+ }
22170
+ const pool = this.pendingHandoffPool;
22171
+ this.pendingHandoffPool = [];
22172
+ for (const item of pool) {
21591
22173
  try {
21592
22174
  (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
21593
22175
  }
21594
22176
  catch (e) {
21595
22177
  console.error(e);
21596
22178
  }
21597
- this.items = this.items.filter(x => x.id !== id);
21598
- (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
21599
22179
  }
21600
22180
  }
21601
22181
  GetEnabledItemIds() {
@@ -24605,7 +25185,8 @@
24605
25185
  if (newItemIds.indexOf(id) === -1 ||
24606
25186
  id == RELATION_MENU_ITEM_ID) {
24607
25187
  params.manager.RemoveItemById({
24608
- menuItemId: id
25188
+ menuItemId: id,
25189
+ allowHandoff: true
24609
25190
  });
24610
25191
  }
24611
25192
  }
@@ -24620,6 +25201,8 @@
24620
25201
  if (!assertIteration$1(params.viewer, iteration)) {
24621
25202
  return;
24622
25203
  }
25204
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25205
+ manager.FlushPendingHandoffs();
24623
25206
  }
24624
25207
  if ((_e = bSettings === null || bSettings === void 0 ? void 0 : bSettings.drawnRelationEntityIDs) === null || _e === void 0 ? void 0 : _e.length) {
24625
25208
  const menuItem = {
@@ -25144,7 +25727,8 @@
25144
25727
  }
25145
25728
  if (shouldRemove) {
25146
25729
  params.manager.RemoveItemById({
25147
- menuItemId: id
25730
+ menuItemId: id,
25731
+ allowHandoff: true
25148
25732
  });
25149
25733
  }
25150
25734
  }
@@ -25158,6 +25742,8 @@
25158
25742
  if (!assertIteration$1(params.viewer, iteration)) {
25159
25743
  return;
25160
25744
  }
25745
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25746
+ params.manager.FlushPendingHandoffs();
25161
25747
  }
25162
25748
  if (legacyRelationIds.length || relations.length) {
25163
25749
  if (relations.length) {
@@ -36474,7 +37060,7 @@
36474
37060
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
36475
37061
  })(exports.StyleUtils || (exports.StyleUtils = {}));
36476
37062
 
36477
- const VERSION = "6.7.9";
37063
+ const VERSION = "6.8.0";
36478
37064
  /**
36479
37065
  * Updates the environment instance used by bruce-cesium to one specified.
36480
37066
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.