Understanding the Lifecycle Contract for Data Layers in God's Eye View
God's Eye View treats every real-time overlay as a data layer managed by the DataLayerManager in src/data/manager.js, enforcing a deterministic state machine that requires modules to expose specific async methods and return boolean promises to transition through registration, initialization, enabling, periodic refreshes, and teardown.
The bilawalsidhu/gods-eye-view repository implements a rigorous lifecycle contract to ensure reliable real-time data visualization. This contract governs how overlay modules integrate with the core viewer, handling everything from initial asset loading to graceful degradation when network conditions fluctuate.
Core Architecture and State Management
At the heart of the system lies the DataLayerManager class, which maintains an internal map of registered layers and tracks their progression through discrete lifecycle states: disabled, enabling, enabled, and disabling. The manager also maintains a lifecycleUncertain flag to handle failure scenarios where cleanup operations fail.
The contract demands that every layer module expose a stable string id property. Beyond this identifier, modules may optionally implement init, enable, update, disable, destroy, and getStats methods. All async methods must return boolean promises—true signals successful transition, while false or thrown exceptions trigger error handling protocols.
Phase 1: Registration and Validation
Registration occurs when manager.register(layerModule) is invoked. The manager validates that the module provides a unique id string and checks for collisions in the internal registry. No initialization occurs during this phase; the manager simply catalogs the module's capabilities for subsequent lifecycle operations.
Phase 2: Initialization
When manager.setEnabled(layerId, true) is first called, the manager transitions the layer to the enabling state and invokes module.init(viewer, {signal}). This method must complete any asynchronous startup tasks, such as fetching GeoJSON assets or establishing WebSocket connections.
According to the source in src/data/manager.js, if init returns false or throws an exception, the enable operation aborts immediately, preventing partial initialization states.
Phase 3: Enable and First Update
Following successful initialization, the manager calls module.enable(viewer, {signal}). This method should activate rendering primitives and attach data sources. Only after the promise resolves to true does the manager set entry.enabled and transition to the enabled state.
Immediately upon enabling, the manager performs a mandatory module.update(viewer, {signal}) call. This guarantees that data are present before the first render cycle, preventing empty or flickering overlays.
Phase 4: Periodic Refresh
If a layer declares a refreshInterval or updateInterval property, the manager arms a setInterval timer that repeatedly invokes module.update. Each tick is wrapped in the internal _runPeriodicUpdate method, which emits refresh-transition, refresh, or refresh-failed events to registered listeners.
The update method must be safe to call repeatedly and should return false to signal a rejected refresh attempt, allowing the manager to update the feed state accordingly without crashing the manager itself.
Phase 5: Disable and Cleanup
When visibility is toggled off via manager.setEnabled(layerId, false), the manager calls module.disable(viewer, {signal}). This method must clean up resources and hide primitives. If disable returns false or throws, the manager sets lifecycleUncertain = true and keeps the refresh interval alive—a fail-closed strategy that prevents resource leaks while signaling the degraded state to the UI.
Phase 6: Destroy and Teardown
The final phase invokes module.destroy(viewer) when destroyLayer(layerId) is called or during internal cleanup. This optional method performs final tear-down, after which the manager removes the entry from its internal map. No further calls are made to the module after destruction.
Error Handling and Intent Epochs
The manager implements robust error isolation through custom error types defined in src/data/manager.js lines 33-43. Exceptions from lifecycle methods are caught and converted to LifecycleRejectedError or LayerParamsRejectedError instances. These are recorded in entry.managerRefreshError and surfaced via visibility-failed or refresh-failed events.
To handle rapid visibility toggles deterministically, the manager implements an intent epoch system (lines 84-132). Every absolute visibility change creates a new epoch; superseded intents are cancelled with superseded-visibility-intent reasons. This guarantees that only the most recent request prevails, preventing race conditions in asynchronous operations.
Feed State and Health Monitoring
The helper function layerFeedState(stats) (lines 72-112) normalizes heterogeneous getStats() output into six UI-friendly labels: nominal, loading, degraded, stale, fallback, and unavailable. Layers may optionally implement getStats() to return diagnostic objects containing status, error, count, and lastUpdate fields, enabling the UI to reflect real-time health metrics.
Implementation Example
The following example demonstrates a minimal compliant data layer implementation:
// src/data/exampleLayer.js
export const id = 'example';
export const refreshInterval = 5000; // Manager polls every 5s
export async function init(viewer, { signal }) {
const resp = await fetch('https://example.com/data.geojson', { signal });
const geojson = await resp.json();
viewer.scene.primitives.add(Cesium.GeoJsonDataSource.load(geojson));
return true;
}
export async function enable(viewer, { signal }) {
viewer.scene.primitives.show = true;
return true;
}
export async function update(viewer, { signal }) {
// Refresh data; return false to reject this refresh cycle
return true;
}
export async function disable(viewer, { signal }) {
viewer.scene.primitives.show = false;
return true;
}
export function getStats() {
return { status: 'ok', count: 42, lastUpdate: Date.now() };
}
To register and control this layer:
import { DataLayerManager } from './src/data/manager.js';
import * as exampleLayer from './src/data/exampleLayer.js';
const manager = new DataLayerManager(viewer);
manager.register(exampleLayer);
await manager.setEnabled('example', true); // init → enable → first update
await manager.refreshLayer('example'); // manual refresh
await manager.setEnabled('example', false'); // disable
Summary
- The DataLayerManager in
src/data/manager.jsorchestrates a deterministic state machine for all overlay modules. - Layers must expose a unique
idstring and optionally implementinit,enable,update,disable,destroy, andgetStatsmethods. - All lifecycle methods return boolean promises; failures trigger
LifecycleRejectedErrorand setlifecycleUncertainflags without crashing the manager. - The intent epoch system (lines 84-132) guarantees deterministic resolution of rapid visibility toggles by cancelling obsolete work.
- Periodic refreshes are manager-owned; layers simply implement
updateand may declarerefreshIntervalfor automatic polling. - Health states are normalized through
layerFeedStateinto six categories:nominal,loading,degraded,stale,fallback, andunavailable.
Frequently Asked Questions
What methods must a data layer implement to satisfy the lifecycle contract?
A layer must expose a stable string id property. All other methods—init, enable, update, disable, destroy, and getStats—are optional but recommended for full functionality. The manager checks for method existence before invocation, allowing minimal implementations that only handle registration and manual updates.
How does the manager handle errors thrown during lifecycle transitions?
Any exception from lifecycle methods is caught in src/data/manager.js and converted to a LifecycleRejectedError (lines 33-37) or LayerParamsRejectedError (lines 39-43). The manager records the error in entry.managerRefreshError, emits a visibility-failed or refresh-failed event, and sets the lifecycleUncertain flag. This fail-safe approach prevents manager crashes while surfacing degradation states to the UI through the feed state system.
Why does the manager keep the refresh interval alive if disable() fails?
This fail-closed strategy prevents resource leaks when cleanup operations fail. If disable() returns false or throws, the manager sets lifecycleUncertain = true and maintains the setInterval rather than risking orphaned timers or unreleased memory. The layer remains in a known, trackable state until manual intervention or a subsequent successful disable operation.
How can I implement manual refresh controls for a data layer?
While the manager automatically calls update() at refreshInterval if declared, you can trigger manual refreshes via manager.refreshLayer(layerId). This method invokes the same update(viewer, {signal}) method used in periodic refreshes, allowing users to force data synchronization on demand without waiting for the next interval tick.
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 →