How to Manage a Store for Scene Context in a CesiumJS Application

The Gods-Eye-View codebase maintains a single, window-scoped store that holds all scene context records, enabling any module—whether voice commands, data layers, or UI panels—to share a common view of selected entities without tight coupling.

Managing scene context in a CesiumJS application requires a centralized state solution that persists across asynchronous updates and module boundaries. The bilawalsidhu/gods-eye-view repository solves this by implementing a global context store in src/data/contextStore.js that lives on window.__gevContextStore, providing a unified API for registering entities, handling selections, and managing tracked subjects across dynamic data layers.

Architecture of the Scene Context Store

The store acts as a singleton container attached to the global window object, ensuring consistent state across the entire application lifecycle.

Store Creation and Host Guard

At lines 3‑9 in src/data/contextStore.js, the createStore() function initializes the store object containing a Map of entity records, a selectedEntityId field, and a selectedAt timestamp. The store is lazily instantiated and assigned to window.__gevContextStore to guarantee a single instance across all modules.

To support unit testing environments where window may be undefined, the code implements hasContextHost() (lines 3‑9), which checks typeof window !== 'undefined' before attempting to access the global scope. This guard enables the store logic to be tested in Node.js environments without modification.

// Access the shared store singleton
import { getContextStore } from './data/contextStore.js';
const store = getContextStore();   
// Returns: { entities: Map, selectedEntityId: null, selectedAt: null, ... }

Working with Entity Contexts

Entities from Cesium data sources must be registered before they can participate in selection and tracking workflows.

Registering New Entities

The registerEntityContext(entity, metadata) function (lines 31‑42) adds or updates records in store.entities. It accepts a Cesium entity object and a metadata object containing id, layerId, and coordinate properties. The function merges metadata, timestamps the entry, and stores it using the unique id as the Map key.

import { registerEntityContext } from './data/contextStore.js';

const fireEntity = { /* Cesium Entity instance */ };
const metadata = { 
  id: 'fire-123', 
  layerId: 'fire-anchors', 
  latitude: 38.9, 
  longitude: -77.0 
};

registerEntityContext(fireEntity, metadata);
// store.entities now contains the record keyed by 'fire-123'

Handling User Selection

When a user interacts with an entity—such as clicking a marker in the 3D view—selectEntityContext(entity) (lines 44‑53) updates the store state and notifies the rest of the application. This function sets store.selectedEntityId and store.selectedAt, then dispatches the gev:entity-selected custom event at line 51, allowing UI panels and voice command systems to react to selection changes.

import { selectEntityContext } from './data/contextStore.js';

selectEntityContext(fireEntity);
// Updates store.selectedEntityId to 'fire-123'
// Dispatches window.dispatchEvent(new CustomEvent('gev:entity-selected'))

Managing Tracked Subjects Separately

Dynamic tracking layers—such as live aircraft or satellite feeds—require a specialized API that updates positional data without triggering UI selection events.

The Tracked Subject API

The selectTrackedSubjectContext(metadata) function (lines 73‑89) stores volatile subject data separately from the main UI selection. Unlike selectEntityContext, this method does not emit selection events, ensuring that polling updates from tracking layers do not steal focus from the user's current selection.

For refresh cycles, refreshTrackedSubjectContext(metadata) (lines 99‑107) updates the metadata for an existing tracked subject while maintaining its identity. When a tracking layer is disabled, clearTrackedSubjectContext(layerId) (lines 117‑127) removes the subject from the store.

import { 
  selectTrackedSubjectContext, 
  refreshTrackedSubjectContext 
} from './data/contextStore.js';

// Initial tracking registration (no UI event emitted)
selectTrackedSubjectContext({ 
  id: 'ac-456', 
  layerId: 'aircraft', 
  latitude: 40.0, 
  longitude: -73.0 
});

// Poll update (lines 99-107)
refreshTrackedSubjectContext({ 
  id: 'ac-456', 
  layerId: 'aircraft', 
  latitude: 40.1, 
  longitude: -73.1 
});

Querying and Validating State

Modules consume the store through query functions that validate record freshness and visibility.

Retrieving Selected Entities

The getSelectedEntityContext({ dataManager }) function (lines 29‑38) returns the currently selected record only if it passes activity validation. It accepts an optional dataManager parameter to filter records by their parent data source state.

import { getSelectedEntityContext } from './data/contextStore.js';

const selected = getSelectedEntityContext({ dataManager });
if (selected) {
  console.log('Active selection:', selected.id);
}

Validating Active Records

Internally, the store uses isContextRecordActive(record, dataManager) (lines 79‑85) to determine if a stored record should be considered valid. This function inspects entity.show, dataSource.show, and dataManager.isEnabled properties, ensuring that hidden or disabled entities are not returned as active selections.

Cleanup and Event Lifecycle

Viewport-scoped layers must clean up their context records when refreshing or unloading to prevent memory leaks and stale selections.

Removing Layer-Specific Contexts

The removeEntityContextsForLayer(layerId) function purges all entities associated with a specific layer ID from the store's Map. When a selection is cleared—either manually or due to layer removal—clearSelectedEntityContextForLayer(layerId, options) (referenced in cleanup workflows) resets store.selectedEntityId to null and emits the gev:entity-selection-cleared event.

import { clearSelectedEntityContextForLayer } from './data/contextStore.js';

// Clear selection when aircraft layer is removed
clearSelectedEntityContextForLayer('aircraft', { evicted: false });
// Emits: gev:entity-selection-cleared

Real-World Integration Patterns

The context store serves as the backbone for cross-module communication in the Gods-Eye-View application.

  • Data layers: Files like src/data/militaryInstallations.js and src/data/aisLiveVessels.js call registerEntityContext when loading new features, binding Cesium entities to metadata records.
  • Voice commands: The src/voice/gevActions.js module queries getSelectedEntityContext to resolve pronouns like "this target" or "that aircraft," enabling natural language interaction with the 3D scene.
  • CCTV focus management: src/data/cctvFocusPolicy.js utilizes the tracked-subject API to maintain camera focus on moving subjects without interfering with the user's manual entity selection.

Summary

  • Single window-scoped store: The createStore() function in src/data/contextStore.js attaches the state container to window.__gevContextStore, ensuring one consistent instance across the CesiumJS application.
  • Dual selection model: Use selectEntityContext for UI-driven selections that emit events, and selectTrackedSubjectContext for background tracking layers that update silently.
  • Activity validation: The getSelectedEntityContext function filters results through isContextRecordActive, checking entity visibility and data source state before returning records.
  • Event-driven updates: The store dispatches gev:entity-selected and gev:entity-selection-cleared events, decoupling state management from UI components.
  • Layer-scoped cleanup: Functions like removeEntityContextsForLayer and clearTrackedSubjectContext prevent memory leaks by purging stale records when data layers refresh or unload.

Frequently Asked Questions

How does the store handle server-side rendering or unit testing?

The hasContextHost() guard checks typeof window !== 'undefined' before accessing the global scope, allowing the store logic to execute in Node.js testing environments without throwing reference errors. When window is absent, the store creation is safely skipped or mocked.

What is the difference between selectEntityContext and selectTrackedSubjectContext?

selectEntityContext designates an entity as the user's active selection, updates store.selectedEntityId, and fires the gev:entity-selected event for UI consumption. selectTrackedSubjectContext stores volatile tracking data—such as live aircraft positions—without changing the UI selection state or emitting events, ensuring polling updates do not disrupt the user experience.

How can I check if a stored entity is still visible in the Cesium scene?

Use isContextRecordActive(record, dataManager), which validates that entity.show !== false, dataSource.show !== false, and dataManager.isEnabled !== false. This function is automatically invoked by getSelectedEntityContext to ensure only visible, enabled entities are returned as active selections.

When should I use refreshTrackedSubjectContext versus re-registering the entity?

Call refreshTrackedSubjectContext when updating positional coordinates or metadata for an existing tracked subject during polling cycles. This maintains the same record identity and timestamp continuity. Only use registerEntityContext when initially creating the entity record or when the entity's fundamental identity (such as its id or layerId) changes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →