bruce-cesium 6.7.9 → 6.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/bruce-cesium.es5.js +749 -159
  2. package/dist/bruce-cesium.es5.js.map +1 -1
  3. package/dist/bruce-cesium.umd.js +747 -157
  4. package/dist/bruce-cesium.umd.js.map +1 -1
  5. package/dist/lib/bruce-cesium.js +1 -1
  6. package/dist/lib/rendering/menu-item-manager.js +75 -6
  7. package/dist/lib/rendering/menu-item-manager.js.map +1 -1
  8. package/dist/lib/rendering/render-managers/render-manager.js.map +1 -1
  9. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js +1 -1
  10. package/dist/lib/rendering/render-managers/tilesets/tileset-arb-render-manager.js.map +1 -1
  11. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js +151 -3
  12. package/dist/lib/rendering/render-managers/tilesets/tileset-cad-render-manager.js.map +1 -1
  13. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js +151 -3
  14. package/dist/lib/rendering/render-managers/tilesets/tileset-entities-render-manager.js.map +1 -1
  15. package/dist/lib/rendering/view-render-engine.js +8 -2
  16. package/dist/lib/rendering/view-render-engine.js.map +1 -1
  17. package/dist/lib/rendering/visuals-register.js +360 -141
  18. package/dist/lib/rendering/visuals-register.js.map +1 -1
  19. package/dist/types/bruce-cesium.d.ts +1 -1
  20. package/dist/types/rendering/menu-item-manager.d.ts +6 -0
  21. package/dist/types/rendering/render-managers/render-manager.d.ts +30 -0
  22. package/dist/types/rendering/render-managers/tilesets/tileset-cad-render-manager.d.ts +10 -1
  23. package/dist/types/rendering/render-managers/tilesets/tileset-entities-render-manager.d.ts +10 -1
  24. package/dist/types/rendering/visuals-register.d.ts +39 -3
  25. package/package.json +8 -2
@@ -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,141 @@
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
+ // Model-space vs. geospatial, and historic vs. live, position the tileset via entirely
14319
+ // different code paths (baked in once at Init() time), so either changing must never
14320
+ // match an existing key and must instead force a real reload.
14321
+ get HandoffKey() {
14322
+ return Manager.GetHandoffKey(this.item, this.modelSpace);
14323
+ }
14324
+ static GetHandoffKey(item, modelSpace) {
14325
+ var _a, _b, _c;
14326
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
14327
+ if (!tilesetId) {
14328
+ return null;
14329
+ }
14330
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
14331
+ const historic = Boolean((_c = item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic);
14332
+ return `${accountId}:${tilesetId}:${Boolean(modelSpace)}:${historic}`;
14333
+ }
14334
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
14335
+ PrepareHandoff() {
14336
+ var _a, _b;
14337
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
14338
+ return null;
14339
+ }
14340
+ // Drain anything mid-flight, once handed off, this instance no longer owns the tile events.
14341
+ while (this.featureQueue.length) {
14342
+ this.processFeatureQueueBatch();
14343
+ }
14344
+ if (this.featureQueueInterval) {
14345
+ clearInterval(this.featureQueueInterval);
14346
+ this.featureQueueInterval = null;
14347
+ }
14348
+ if (this.handoffRestyleInterval) {
14349
+ clearInterval(this.handoffRestyleInterval);
14350
+ this.handoffRestyleInterval = null;
14351
+ }
14352
+ // Not transferred, the receiving manager sets up its own fresh copy if needed.
14353
+ this.viewerDateTimeDispose();
14354
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
14355
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
14356
+ this.tileLoadRemoval = null;
14357
+ this.tileUnloadRemoval = null;
14358
+ const payload = {
14359
+ cTileset: this.cTileset,
14360
+ tileset: this.tileset,
14361
+ rootId: this.rootId,
14362
+ modelTree: this.modelTree,
14363
+ treeNodeByGeomId: this.treeNodeByGeomId,
14364
+ treeNodeByEntityId: this.treeNodeByEntityId,
14365
+ loadedCesiumEntities: this.loadedCesiumEntities,
14366
+ sourceMenuItemId: this.item.id
14367
+ };
14368
+ // Not routed through Dispose(), that would remove the tileset/regos just handed off.
14369
+ this.cTileset = null;
14370
+ this.disposed = true;
14371
+ return payload;
14372
+ }
14373
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
14374
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
14375
+ AdoptHandoff(payload) {
14376
+ var _a, _b, _c, _d;
14377
+ this.cTileset = payload.cTileset;
14378
+ this.tileset = payload.tileset;
14379
+ this.rootId = payload.rootId;
14380
+ this.modelTree = payload.modelTree;
14381
+ this.treeNodeByGeomId = payload.treeNodeByGeomId;
14382
+ this.treeNodeByEntityId = payload.treeNodeByEntityId;
14383
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
14384
+ this.renderPriority = this.item.renderPriority;
14385
+ if (this.renderPriority == null) {
14386
+ this.renderPriority = 0;
14387
+ }
14388
+ // Same visuals, same registrations - just re-keyed onto this menu item.
14389
+ this.visualsManager.ReassignMenuItem({
14390
+ fromMenuItemId: payload.sourceMenuItemId,
14391
+ toMenuItemId: this.item.id,
14392
+ toMenuItemType: this.item.Type,
14393
+ toPriority: this.renderPriority,
14394
+ requestRender: false
14395
+ });
14396
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
14397
+ try {
14398
+ this.queueTile(tile, true);
14399
+ }
14400
+ catch (e) {
14401
+ console.error(e);
14402
+ }
14403
+ });
14404
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
14405
+ try {
14406
+ this.queueTile(tile, false);
14407
+ }
14408
+ catch (e) {
14409
+ console.error(e);
14410
+ }
14411
+ });
14412
+ this.onCTilesetLoad();
14413
+ this.styler.Init({
14414
+ viewer: this.viewer,
14415
+ api: this.getters.GetBruceApi(),
14416
+ cTileset: this.cTileset,
14417
+ fallbackStyleId: this.item.styleId,
14418
+ styleMapping: this.item.StyleMapping,
14419
+ expandSources: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.ExpandSources,
14420
+ menuItemId: this.item.id,
14421
+ register: this.visualsManager,
14422
+ scenario: (_b = this.item.BruceEntity) === null || _b === void 0 ? void 0 : _b.Scenario,
14423
+ historic: (_c = this.item.BruceEntity) === null || _c === void 0 ? void 0 : _c.historic,
14424
+ });
14425
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
14426
+ this.viewer.scene.requestRender();
14427
+ if (((_d = this.item.BruceEntity) === null || _d === void 0 ? void 0 : _d.historic) && this.tileset) {
14428
+ this.viewerDateTimeSub(this.tileset);
14429
+ }
14430
+ }
14431
+ queueHandoffRestyle(regos) {
14432
+ if (!regos.length) {
14433
+ return;
14434
+ }
14435
+ const BATCH_SIZE = 5000;
14436
+ let index = 0;
14437
+ this.handoffRestyleInterval = setInterval(() => {
14438
+ if (this.disposed) {
14439
+ clearInterval(this.handoffRestyleInterval);
14440
+ this.handoffRestyleInterval = null;
14441
+ return;
14442
+ }
14443
+ const end = Math.min(index + BATCH_SIZE, regos.length);
14444
+ this.styler.QueueEntities(regos.slice(index, end), true);
14445
+ index = end;
14446
+ if (index >= regos.length) {
14447
+ clearInterval(this.handoffRestyleInterval);
14448
+ this.handoffRestyleInterval = null;
14449
+ }
14450
+ }, 10);
14451
+ }
14089
14452
  /**
14090
14453
  * @param tile
14091
14454
  * @param load indicates if we are loading or unloading the tile.
@@ -14098,7 +14461,7 @@
14098
14461
  }
14099
14462
  for (let i = 0; i < content.featuresLength; i++) {
14100
14463
  const feature = content.getFeature(i);
14101
- if (!this.featureQueue.includes(feature)) {
14464
+ if (!this.featureQueueStates.has(feature)) {
14102
14465
  this.featureQueue.push(feature);
14103
14466
  }
14104
14467
  this.featureQueueStates.set(feature, load);
@@ -14635,6 +14998,10 @@
14635
14998
  this.featureQueueInterval = null;
14636
14999
  this.featureQueue = [];
14637
15000
  }
15001
+ if (this.handoffRestyleInterval) {
15002
+ clearInterval(this.handoffRestyleInterval);
15003
+ this.handoffRestyleInterval = null;
15004
+ }
14638
15005
  if (this.cTileset) {
14639
15006
  const viewer = this.viewer;
14640
15007
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17282,6 +17649,14 @@
17282
17649
  this.initCounter = 0;
17283
17650
  this.disposed = false;
17284
17651
  this.cTileset = null;
17652
+ // Kept around so a hand-off (see PrepareHandoff/AdoptHandoff) can pass it on without a re-fetch.
17653
+ this.tileset = null;
17654
+ // Removal callbacks for this instance's own tile stream listeners.
17655
+ this.tileLoadRemoval = null;
17656
+ this.tileUnloadRemoval = null;
17657
+ // Chunks AdoptHandoff()'s restyle of handed-off content across ticks instead of one
17658
+ // synchronous pass over potentially millions of regos.
17659
+ this.handoffRestyleInterval = null;
17285
17660
  this.styler = new exports.TilesetRenderEngine.Styler();
17286
17661
  // Entity ID -> rego.
17287
17662
  // We retain this information as a quick look-up on what has been registered.
@@ -17369,6 +17744,7 @@
17369
17744
  if (!tileset || this.disposed || counter !== this.initCounter) {
17370
17745
  return;
17371
17746
  }
17747
+ this.tileset = tileset;
17372
17748
  this.typeId = tileset.settings.entityTypeId;
17373
17749
  // Other account so we'll prefer the fallback entity-type ID if it's set.
17374
17750
  if (accountId != this.getters.GetAccountId()) {
@@ -17417,7 +17793,7 @@
17417
17793
  console.error(e);
17418
17794
  }
17419
17795
  });
17420
- cTileset.tileLoad.addEventListener((tile) => {
17796
+ this.tileLoadRemoval = cTileset.tileLoad.addEventListener((tile) => {
17421
17797
  try {
17422
17798
  this.queueTile(tile, true);
17423
17799
  }
@@ -17425,7 +17801,7 @@
17425
17801
  console.error(e);
17426
17802
  }
17427
17803
  });
17428
- cTileset.tileUnload.addEventListener((tile) => {
17804
+ this.tileUnloadRemoval = cTileset.tileUnload.addEventListener((tile) => {
17429
17805
  try {
17430
17806
  this.queueTile(tile, false);
17431
17807
  }
@@ -17466,7 +17842,7 @@
17466
17842
  }
17467
17843
  for (let i = 0; i < content.featuresLength; i++) {
17468
17844
  const feature = content.getFeature(i);
17469
- if (!this.featureQueue.includes(feature)) {
17845
+ if (!this.featureQueueStates.has(feature)) {
17470
17846
  this.featureQueue.push(feature);
17471
17847
  }
17472
17848
  this.featureQueueStates.set(feature, load);
@@ -17585,6 +17961,10 @@
17585
17961
  this.featureQueueInterval = null;
17586
17962
  this.featureQueue = [];
17587
17963
  }
17964
+ if (this.handoffRestyleInterval) {
17965
+ clearInterval(this.handoffRestyleInterval);
17966
+ this.handoffRestyleInterval = null;
17967
+ }
17588
17968
  if (this.cTileset) {
17589
17969
  const viewer = this.viewer;
17590
17970
  if (!(viewer === null || viewer === void 0 ? void 0 : viewer.isDestroyed()) && this.viewer.scene.primitives.contains(this.cTileset)) {
@@ -17605,6 +17985,141 @@
17605
17985
  this.viewer.zoomTo(this.cTileset, new Cesium.HeadingPitchRange(0.0, -0.5, this.cTileset.boundingSphere.radius / 4.0));
17606
17986
  }
17607
17987
  }
17988
+ // Two items resolving to the same key are eligible to hand off, regardless of styling.
17989
+ get HandoffKey() {
17990
+ return Manager.GetHandoffKey(this.item);
17991
+ }
17992
+ static GetHandoffKey(item) {
17993
+ var _a, _b;
17994
+ const tilesetId = (_a = item === null || item === void 0 ? void 0 : item.tileset) === null || _a === void 0 ? void 0 : _a.TilesetID;
17995
+ if (!tilesetId) {
17996
+ return null;
17997
+ }
17998
+ const accountId = ((_b = item.tileset) === null || _b === void 0 ? void 0 : _b.ClientAccountID) || "";
17999
+ return `${accountId}:${tilesetId}`;
18000
+ }
18001
+ // Returns null (fall back to a normal Dispose()) if the tileset hasn't finished loading yet.
18002
+ PrepareHandoff() {
18003
+ var _a, _b;
18004
+ if (this.disposed || !this.cTileset || this.cTileset.isDestroyed()) {
18005
+ return null;
18006
+ }
18007
+ // Drain anything mid-flight - once handed off, this instance no longer owns the tile events.
18008
+ while (this.featureQueue.length) {
18009
+ this.processFeatureQueueBatch();
18010
+ }
18011
+ if (this.featureQueueInterval) {
18012
+ clearInterval(this.featureQueueInterval);
18013
+ this.featureQueueInterval = null;
18014
+ }
18015
+ if (this.handoffRestyleInterval) {
18016
+ clearInterval(this.handoffRestyleInterval);
18017
+ this.handoffRestyleInterval = null;
18018
+ }
18019
+ (_a = this.tileLoadRemoval) === null || _a === void 0 ? void 0 : _a.call(this);
18020
+ (_b = this.tileUnloadRemoval) === null || _b === void 0 ? void 0 : _b.call(this);
18021
+ this.tileLoadRemoval = null;
18022
+ this.tileUnloadRemoval = null;
18023
+ const payload = {
18024
+ cTileset: this.cTileset,
18025
+ tileset: this.tileset,
18026
+ typeId: this.typeId,
18027
+ loadedCesiumEntities: this.loadedCesiumEntities,
18028
+ sourceMenuItemId: this.item.id
18029
+ };
18030
+ // Not routed through Dispose() - that would remove the tileset/regos just handed off.
18031
+ this.cTileset = null;
18032
+ this.disposed = true;
18033
+ return payload;
18034
+ }
18035
+ // Takes ownership of a PrepareHandoff() payload instead of loading via Init().
18036
+ // Then re-parents every already-registered rego to this menu item and re-runs styling.
18037
+ AdoptHandoff(payload) {
18038
+ var _a;
18039
+ this.cTileset = payload.cTileset;
18040
+ this.tileset = payload.tileset;
18041
+ this.typeId = payload.typeId;
18042
+ this.loadedCesiumEntities = payload.loadedCesiumEntities;
18043
+ this.renderPriority = this.item.renderPriority;
18044
+ if (this.renderPriority == null) {
18045
+ this.renderPriority = 0;
18046
+ }
18047
+ this.visualsManager.ReassignMenuItem({
18048
+ fromMenuItemId: payload.sourceMenuItemId,
18049
+ toMenuItemId: this.item.id,
18050
+ toMenuItemType: this.item.Type,
18051
+ toPriority: this.renderPriority,
18052
+ requestRender: false
18053
+ });
18054
+ this.tileLoadRemoval = this.cTileset.tileLoad.addEventListener((tile) => {
18055
+ try {
18056
+ this.queueTile(tile, true);
18057
+ }
18058
+ catch (e) {
18059
+ console.error(e);
18060
+ }
18061
+ });
18062
+ this.tileUnloadRemoval = this.cTileset.tileUnload.addEventListener((tile) => {
18063
+ try {
18064
+ this.queueTile(tile, false);
18065
+ }
18066
+ catch (e) {
18067
+ console.error(e);
18068
+ }
18069
+ });
18070
+ if (this.item.ApplyStyles) {
18071
+ this.styler.Init({
18072
+ viewer: this.viewer,
18073
+ api: this.getters.GetBruceApi(),
18074
+ cTileset: this.cTileset,
18075
+ fallbackStyleId: this.item.styleId,
18076
+ styleMapping: [],
18077
+ expandSources: false,
18078
+ menuItemId: this.item.id,
18079
+ register: this.visualsManager,
18080
+ historic: (_a = this.item.BruceEntity) === null || _a === void 0 ? void 0 : _a.historic
18081
+ });
18082
+ this.queueHandoffRestyle(this.visualsManager.GetRegos({ menuItemId: this.item.id }));
18083
+ }
18084
+ this.onCTilesetLoad();
18085
+ let attenuation = this.item.attenuation;
18086
+ if (!attenuation && attenuation != false) {
18087
+ attenuation = true;
18088
+ }
18089
+ let attenuationMax = this.item.attenuationMax;
18090
+ if (isNaN(attenuationMax)) {
18091
+ attenuationMax = 20;
18092
+ }
18093
+ exports.TilesetRenderEngine.ApplySettings({
18094
+ cTileset: this.cTileset,
18095
+ settings: {
18096
+ attenuation: attenuation,
18097
+ maximumAttenuation: attenuationMax
18098
+ }
18099
+ });
18100
+ this.viewer.scene.requestRender();
18101
+ }
18102
+ queueHandoffRestyle(regos) {
18103
+ if (!regos.length) {
18104
+ return;
18105
+ }
18106
+ const BATCH_SIZE = 5000;
18107
+ let index = 0;
18108
+ this.handoffRestyleInterval = setInterval(() => {
18109
+ if (this.disposed) {
18110
+ clearInterval(this.handoffRestyleInterval);
18111
+ this.handoffRestyleInterval = null;
18112
+ return;
18113
+ }
18114
+ const end = Math.min(index + BATCH_SIZE, regos.length);
18115
+ this.styler.QueueEntities(regos.slice(index, end), true);
18116
+ index = end;
18117
+ if (index >= regos.length) {
18118
+ clearInterval(this.handoffRestyleInterval);
18119
+ this.handoffRestyleInterval = null;
18120
+ }
18121
+ }, 10);
18122
+ }
17608
18123
  mapTilesetFeature(feature) {
17609
18124
  var _a, _b, _c;
17610
18125
  this.evaluateFeatureProps(feature);
@@ -18439,7 +18954,7 @@
18439
18954
  }
18440
18955
  for (let i = 0; i < content.featuresLength; i++) {
18441
18956
  const feature = content.getFeature(i);
18442
- if (!this.featureQueue.includes(feature)) {
18957
+ if (!this.featureQueueStates.has(feature)) {
18443
18958
  this.featureQueue.push(feature);
18444
18959
  }
18445
18960
  this.featureQueueStates.set(feature, load);
@@ -21153,6 +21668,7 @@
21153
21668
  }
21154
21669
  constructor(params) {
21155
21670
  this.items = [];
21671
+ this.pendingHandoffPool = [];
21156
21672
  this.onUpdate = null;
21157
21673
  let { viewer, visualsRegister, getters } = params;
21158
21674
  this.viewer = viewer;
@@ -21176,7 +21692,7 @@
21176
21692
  * This will dispose all render managers and unregister all menu items.
21177
21693
  */
21178
21694
  Dispose(params) {
21179
- var _a;
21695
+ var _a, _b;
21180
21696
  const { disposeRegister } = params !== null && params !== void 0 ? params : {};
21181
21697
  for (let i = 0; i < this.items.length; i++) {
21182
21698
  const item = this.items[i];
@@ -21188,7 +21704,9 @@
21188
21704
  }
21189
21705
  }
21190
21706
  this.items = [];
21707
+ this.FlushPendingHandoffs();
21191
21708
  this.tilesetInitQueue.Dispose();
21709
+ (_b = this.sharedMonitor) === null || _b === void 0 ? void 0 : _b.Dispose();
21192
21710
  if (this.visualsRegister && disposeRegister != false) {
21193
21711
  this.visualsRegister.Dispose();
21194
21712
  }
@@ -21537,8 +22055,14 @@
21537
22055
  console.error("Menu item type is not implemented.", params.item.Type);
21538
22056
  }
21539
22057
  if (rItem.renderManager && !rItem.renderManager.Disposed) {
22058
+ const handoffPayload = this.tryClaimHandoffPayload(params.item, rItem.renderManager, params.modelSpace);
21540
22059
  try {
21541
- rItem.renderManager.Init();
22060
+ if (handoffPayload) {
22061
+ rItem.renderManager.AdoptHandoff(handoffPayload);
22062
+ }
22063
+ else {
22064
+ rItem.renderManager.Init();
22065
+ }
21542
22066
  }
21543
22067
  catch (e) {
21544
22068
  rItem.errors.push(e);
@@ -21569,7 +22093,7 @@
21569
22093
  */
21570
22094
  RemoveItemById(params) {
21571
22095
  var _a, _b;
21572
- let { menuItemId: id, recursive } = params;
22096
+ let { menuItemId: id, recursive, allowHandoff } = params;
21573
22097
  if (this.viewer.isDestroyed()) {
21574
22098
  return;
21575
22099
  }
@@ -21583,19 +22107,79 @@
21583
22107
  const child = this.items.find(x => x.id === item.childIds[i]);
21584
22108
  if (child) {
21585
22109
  this.RemoveItemById({
21586
- menuItemId: child.id
22110
+ menuItemId: child.id,
22111
+ allowHandoff
21587
22112
  });
21588
22113
  }
21589
22114
  }
21590
22115
  }
22116
+ if (allowHandoff && this.isHandoffCapable(item.renderManager)) {
22117
+ this.pendingHandoffPool.push(item);
22118
+ }
22119
+ else {
22120
+ try {
22121
+ (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
22122
+ }
22123
+ catch (e) {
22124
+ console.error(e);
22125
+ }
22126
+ }
22127
+ this.items = this.items.filter(x => x.id !== id);
22128
+ (_b = this.onUpdate) === null || _b === void 0 ? void 0 : _b.Trigger({ isEnabling: false, itemId: item.id });
22129
+ }
22130
+ }
22131
+ isHandoffCapable(renderManager) {
22132
+ return Boolean(renderManager) && typeof renderManager.PrepareHandoff === "function";
22133
+ }
22134
+ // Resolves the hand-off key a menu item's data would have.
22135
+ computeHandoffKey(item, modelSpace) {
22136
+ if (item.Type === BModels.MenuItem.EType.CadTileset) {
22137
+ return exports.TilesetCadRenderManager.Manager.GetHandoffKey(item, modelSpace);
22138
+ }
22139
+ if (item.Type === BModels.MenuItem.EType.EntityTileset) {
22140
+ return exports.TilesetEntitiesRenderManager.Manager.GetHandoffKey(item);
22141
+ }
22142
+ return null;
22143
+ }
22144
+ // Claims a pooled manager for the same resource, if one exists and both sides opt in.
22145
+ // Returns null (meaning: construct normally via Init()) otherwise.
22146
+ tryClaimHandoffPayload(item, newManager, modelSpace) {
22147
+ if (typeof newManager.AdoptHandoff !== "function") {
22148
+ return null;
22149
+ }
22150
+ const key = this.computeHandoffKey(item, modelSpace);
22151
+ if (!key) {
22152
+ return null;
22153
+ }
22154
+ const poolIndex = this.pendingHandoffPool.findIndex(pooled => pooled.type === item.Type &&
22155
+ this.isHandoffCapable(pooled.renderManager) &&
22156
+ pooled.renderManager.HandoffKey === key);
22157
+ if (poolIndex < 0) {
22158
+ return null;
22159
+ }
22160
+ const pooled = this.pendingHandoffPool[poolIndex].renderManager;
22161
+ const payload = pooled.PrepareHandoff();
22162
+ if (!payload) {
22163
+ return null;
22164
+ }
22165
+ this.pendingHandoffPool.splice(poolIndex, 1);
22166
+ return payload;
22167
+ }
22168
+ // Disposes everything left unclaimed in the pending-hand-off pool.
22169
+ FlushPendingHandoffs() {
22170
+ var _a;
22171
+ if (!this.pendingHandoffPool.length) {
22172
+ return;
22173
+ }
22174
+ const pool = this.pendingHandoffPool;
22175
+ this.pendingHandoffPool = [];
22176
+ for (const item of pool) {
21591
22177
  try {
21592
22178
  (_a = item.renderManager) === null || _a === void 0 ? void 0 : _a.Dispose();
21593
22179
  }
21594
22180
  catch (e) {
21595
22181
  console.error(e);
21596
22182
  }
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
22183
  }
21600
22184
  }
21601
22185
  GetEnabledItemIds() {
@@ -24605,7 +25189,8 @@
24605
25189
  if (newItemIds.indexOf(id) === -1 ||
24606
25190
  id == RELATION_MENU_ITEM_ID) {
24607
25191
  params.manager.RemoveItemById({
24608
- menuItemId: id
25192
+ menuItemId: id,
25193
+ allowHandoff: true
24609
25194
  });
24610
25195
  }
24611
25196
  }
@@ -24620,6 +25205,8 @@
24620
25205
  if (!assertIteration$1(params.viewer, iteration)) {
24621
25206
  return;
24622
25207
  }
25208
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25209
+ manager.FlushPendingHandoffs();
24623
25210
  }
24624
25211
  if ((_e = bSettings === null || bSettings === void 0 ? void 0 : bSettings.drawnRelationEntityIDs) === null || _e === void 0 ? void 0 : _e.length) {
24625
25212
  const menuItem = {
@@ -25144,7 +25731,8 @@
25144
25731
  }
25145
25732
  if (shouldRemove) {
25146
25733
  params.manager.RemoveItemById({
25147
- menuItemId: id
25734
+ menuItemId: id,
25735
+ allowHandoff: true
25148
25736
  });
25149
25737
  }
25150
25738
  }
@@ -25158,6 +25746,8 @@
25158
25746
  if (!assertIteration$1(params.viewer, iteration)) {
25159
25747
  return;
25160
25748
  }
25749
+ // Deferred removals not claimed via a hand-off can now be disposed for real.
25750
+ params.manager.FlushPendingHandoffs();
25161
25751
  }
25162
25752
  if (legacyRelationIds.length || relations.length) {
25163
25753
  if (relations.length) {
@@ -36474,7 +37064,7 @@
36474
37064
  StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
36475
37065
  })(exports.StyleUtils || (exports.StyleUtils = {}));
36476
37066
 
36477
- const VERSION = "6.7.9";
37067
+ const VERSION = "6.8.1";
36478
37068
  /**
36479
37069
  * Updates the environment instance used by bruce-cesium to one specified.
36480
37070
  * This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.