How DataLayerManager Manages Data Layers in God's Eye View: Architecture & Lifecycle
The DataLayerManager in bilawalsidhu/gods-eye-view is a centralized orchestrator that registers, toggles, and refreshes real-time data overlays on a CesiumJS globe using an intent-based state machine with deterministic epoch tracking.
The DataLayerManager serves as the control plane for all data visualization layers in God's Eye View. Located in src/data/manager.js, this class bridges the gap between raw data modules and the CesiumJS viewer, handling everything from initial registration to periodic refresh cycles. By separating absolute intent from relative toggles, the manager ensures predictable layer behavior even during rapid user interactions or network failures.
Core Architecture and State Management
At initialization, the DataLayerManager establishes a private Map structure to track layer entries by their stable string id. Each entry maintains the layer's enabled state, lifecycle status, refresh timers, and intent bookkeeping.
Construction and the Layer Registry
The manager requires a Cesium viewer reference upon instantiation. According to the source code in src/data/manager.js (lines 18–26), the constructor initializes:
- A
viewerreference for scene integration - A
_layersMap storingid → {module, ...}mappings - Internal tracking for QA-specific registrations
import { DataLayerManager } from './data/manager.js';
// Instantiate with the Cesium viewer
const manager = new DataLayerManager(viewer);
Layer Registration and Validation
The register(layerModule) method (lines 31–38) validates that incoming modules provide a stable string id. The manager rejects duplicate IDs and throws errors if registration occurs after finalizeRegistrations() has sealed the registry.
For development environments, registerForQa() (lines 39–47) allows injection of synthetic test layers when the manager is constructed with {allowQaRegistration: true}. These QA layer IDs are tracked separately in _qaLayerIds for cleanup isolation.
import trafficLayer from './data/traffic.js';
// Register production layers
manager.register(trafficLayer);
// Seal registrations and validate serialization disposition
manager.finalizeRegistrations([
{ id: trafficLayer.id, disposition: 'enabled-only' }
]);
The finalizeRegistrations() method ensures every layer has a defined serialization disposition (e.g., 'enabled-only' or 'enabled+options'), preventing accidental runtime additions that could destabilize the visualization state.
Intent-Based Visibility Control
The manager implements a sophisticated visibility intent model using an epoch counter (visibilityIntentEpoch) to guarantee deterministic ordering of state changes.
Absolute vs. Relative State Changes
setEnabled(layerId, shouldEnable, ...) invokes the private _setEnabledWithIntent method (lines 74–82) to execute a full lifecycle: optional init, enable, immediate update, then periodic refresh scheduling. Each absolute request increments the epoch and stores an intent record, ensuring newer intents supersede stale requests even when rapid toggles occur.
toggle(layerId, {...}) flips the effective state by calling _setEnabledWithIntent with !isEffectivelyEnabled. This method also supports a notifyWillChangeBeforeEffective flag (lines 40–55) that emits a "will-change" event before the actual transition, allowing UI components to prepare for state shifts.
// Absolute enable (intent-based)
await manager.setEnabled('traffic', true, { origin: 'user' });
// Relative toggle (swaps current state)
await manager.toggle('satellites');
Lifecycle State Presentation
The manager maintains explicit lifecycleState values: 'enabled', 'disabled', 'enabling', or 'disabling'. Through _setLifecycleTransition and _syncModuleLifecyclePresentation (lines 62–71), these states propagate to layer modules implementing setLifecyclePresentation, keeping UI toggles synchronized with underlying data fetching operations.
Refresh Loops and Error Resilience
When a layer specifies a refreshInterval or updateInterval greater than zero, _armUpdateLoop creates a setInterval that triggers _runPeriodicUpdate (lines 22–31). This loop respects:
- Current enabled state (aborting if disabled)
- Abort signals for in-flight requests
- Automatic cancellation upon layer destruction
Errors during any lifecycle step (init, enable, update) are captured centrally. Rather than hiding data unintentionally, the manager may retain layers in an uncertain state, preserving the last known good visualization while logging failures for debugging.
Event Notification and Subscription
All state transitions broadcast through an internal listener set (_listeners). Consumers subscribe via manager.subscribe(callback) and receive notifications for:
visibility-will-changevisibilityupdates- Refresh completions
- Error conditions
After each successful update, the manager triggers governorRequestRender to force a Cesium render pass and marks detection sources as changed (lines 101–108).
const unsubscribe = manager.subscribe(change => {
console.log('Layer change:', change.layerId, change.newState);
});
// Cleanup subscription
unsubscribe();
Practical Implementation Examples
Manual Refresh and Camera Synchronization
Force immediate data refresh before camera movements or screenshot capture:
// Trigger immediate update bypassing the interval
await manager.refreshLayer('traffic');
Working with Production Layer Modules
Concrete layer implementations like src/data/traffic.js and src/data/satellites.js expose standard interfaces (init, enable, update, disable) that the manager orchestrates. The src/data/localLayers.js file lists default production layers registered at application startup.
import { DataLayerManager } from './data/manager.js';
import trafficLayer from './data/traffic.js';
import satellitesLayer from './data/satellites.js';
const manager = new DataLayerManager(viewer);
manager.register(trafficLayer);
manager.register(satellitesLayer);
manager.finalizeRegistrations([
{ id: trafficLayer.id, disposition: 'enabled+options' },
{ id: satellitesLayer.id, disposition: 'enabled+mirrored-options' }
]);
Summary
- Centralized Registration: The
DataLayerManagerinsrc/data/manager.jsmaintains a strict registry of layer modules with validation viafinalizeRegistrations(). - Deterministic State Machine: Uses
visibilityIntentEpochto handle rapid toggle sequences without race conditions. - Lifecycle Orchestration: Manages complete layer lifecycles from
initthroughenable, periodicupdate, anddisable. - Fault Tolerance: Captures errors at each lifecycle stage and supports "uncertain" states to prevent data loss.
- Event-Driven Architecture: Provides subscription-based notifications for all visibility and refresh changes.
Frequently Asked Questions
What is the difference between toggle() and setEnabled() in DataLayerManager?
setEnabled() makes an absolute state request (explicitly on or off) and increments the intent epoch to ensure the request takes precedence over concurrent operations. toggle() queries the current effective state and requests the opposite, making it ideal for checkbox or button interactions where the current state may be unknown to the caller.
How does DataLayerManager handle rapid visibility changes?
The manager implements an intent epoch counter (visibilityIntentEpoch) in _setEnabledWithIntent (lines 74–90). Each visibility request receives a monotonically increasing epoch number. If multiple requests fire in rapid succession, only the highest epoch (most recent intent) executes, preventing visual flicker and inconsistent data states.
Why is finalizeRegistrations() required before enabling layers?
This method seals the layer registry and validates that each layer has a serialization disposition (e.g., 'enabled-only'). It prevents accidental runtime registrations that could destabilize the serialization state or introduce untracked layers during production use, ensuring the manager maintains a deterministic, serializable configuration.
How are refresh intervals managed for active layers?
When a layer module specifies refreshInterval or updateInterval, _armUpdateLoop creates a setInterval timer that calls _runPeriodicUpdate. The loop automatically canels if the layer disables, the viewer destroys, or an abort signal fires, preventing memory leaks and unnecessary network requests.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →