System designlayered · bounded · testable

Architecture

SceneDB is layered. The bottom is a paged storage engine and a full archetype ECS; spatial, streaming, and GPU layers sit above it; a compile-time phase machine enforces ordering; and the replication layer rides on top of the frame boundary.

Simulate A
Simulate B
Harvest
Boundary

FrameDriver: simulate → harvest → boundary → repeat

Storage

CPU · Layer 1

Paged SoA storage. 256 rows/page, 64B aligned columns, swap-and-pop compaction at frame boundaries. Handles stay stable; generation counters prevent dangling pointers.

CellStoragePagePageLayoutLivenessMaskHandleRegistry

ECS

CPU · Layer 1

World, an archetype ECS: entities grouped by component set into contiguous columns. An archetype-graph edge cache keeps repeated insert/remove transitions to two Vec reads. Bundle spawn resolves the destination archetype once; queries resolve per-archetype once and iterate rows by pointer arithmetic.

WorldBundleWorldQueryQueryIter

Spatial

CPU · Layer 1

Six dedicated f32 columns for AABB min/max per axis. AABB and frustum queries scan column arrays directly. No per-entity iteration, no allocation. Scalar reference must match SIMD bit-for-bit.

SpatialCellAabbFrustum

Streaming

CPU · Layer 1

Concentric classification into Outer / Margin / Inner domains with hysteresis bands. The bands damp boundary jitter. Multiple observer AABBs and persistent pins coexist on the same grid.

StreamingGridCellCoordDomainGridConfig

GPU Store

GPU · Layer 2

Region-partitioned SSBOs shared across every registered cell. Delta-sync uploads only dirty rows. Generation buffer + slot mirror in VRAM for GPU-side handle validation, with bulk rebuild for device loss.

SceneGpuStoreRegionPoolSceneBufferCellGpuState

GPU Buffers

GPU · Layer 2

Row buffers, texture arrays, and asset registries resolve through one keyed GpuBufferRegistry. DynamicGpuBuffer covers pipeline-owned data (cull outputs, draw batches) with transparent growth; GrowableSceneBuffer backs the World mirror.

GpuBufferRegistryDynamicGpuBufferGrowableSceneBufferGenerationMirror

World Mirror

CPU+GPU · entity storage

A second storage model alongside the paged cells: an archetype ECS (World / Entity / Component) with an opt-in GPU mirror. Build it wired via World::new_with_gpu_mirror and every #[gpu] field auto-registers on first insert and auto-flushes inside step(). Once-mode fields upload on first insert only; DirtyTracked fields batch into one flush per frame. get_mut returns a Mut guard that writes through on drop; scattered churn routes through a GPU scatter-write compute pass.

WorldGpuMirrorHandleMirrorModeMutGenerationMirror

Assets

GPU · Layer 2

GPU-side asset storage with suballocation, keyed through the buffer registry: GeometryArena, MeshRegistry, ClusterBuffer, TextureStore, MeshletBuffer, and MaterialRegistry. Each one carries corruption validation and rebuild gates.

GeometryArenaMeshRegistryClusterBufferTextureStoreMeshletBuffer

Harvest

CPU → GPU · Layer 2

Per-view spatial queries with one staging array per view (no shared state), routing hits into mesh-class buckets for indirect draw dispatch.

HarvestPipelineHarvestStagingViewMeshClass

Phase Machine

CPU · compile-time

FrameDriver owns one frame's progression: SimulateA → SimulateB → Harvest → Boundary (retire → compact → sync). Zero-sized witnesses gate every phase. SimulateA and SimulateB are sealed, so invalid transitions are unrepresentable.

FrameDriverSimulateASimulateBHarvestPhaseBoundaryPhase

SceneDb

CPU+GPU · facade

SceneDb owns a World, a SubsystemRegistry, and a FrameDriver. step() runs every subsystem's simulate hooks and flushes any attached mirror; step_gpu() drives Harvest → Boundary. Subsystems register once, hook only the phases they need, and stay callable by name from scripts via #[scenedb_subsystem] / #[subsystem_method].

SceneDbSubsystemSubsystemRegistryFrameDriver

Relations

CPU · columnar

For component patterns where one entity points at another (portals, multi-body attachments), RelationIndex builds a dense columnar view over World. Confirmed-reciprocal pairs only, with unmatched and conflict buffers for the caller to resolve. Rebuilt once per boundary; reads are zero-allocation slices.

RelationIndexRelationViewConflictEntry

Replication

CPU · C0

The full primitive suite: change tracking, delta encoding, interest management, authority, events/RPCs, snapshots, and client-side prediction. All graphics-free and always available.

ChangeTrackerDeltaRelevanceSetAuthorityTableSnapshotReconciler

ECS performance, measured

A Criterion bench in benches/vs_bevy_ecs.rs runs matched, single-threaded World/Query scenarios head-to-head against bevy_ecs on the exact same component shapes and entity counts.

What gets it there: an archetype-graph edge cache (repeated transitions cost two Vec reads), a WorldQuery init/fetch split (column resolution once per archetype, pointer arithmetic per row), Bundle spawn (one destination archetype, every column written directly), and a zero-allocation column-move path for migration.

1.8×

faster than bevy_ecs: archetype migration (add → add → remove, 10k entities)

parity

spawn with 4 components, 1k–10k entities (within measurement noise)

6–11%

behind bevy_ecs on 2-component query, 1k–50k entities, matched shapes

faster

than bevy_ecs on 4-component query, 10k entities

Concurrency without locks

SceneDB is built around single-writer, shared-reader discipline gated by the phase machine. Within Simulate, systems run in parallel on independent handles. SceneGpuStore::write_transform is &self-safe with interior atomics. Harvest scans are read-only on SpatialCell and safe to run per-view across a job system. The boundary phase is single-threaded.

LivenessMask stores each 64-row word as an AtomicU64 with Relaxed ordering. Set during Simulate (single writer), read during Harvest (concurrent readers with a lease hold). No CAS loops, no SeqCst, no shared-state concurrency inside the storage itself.

frame.rs
1// FrameDriver owns one frame's progression through the phase machine.
2//
3// driver.begin() → SimulateA (gameplay mutation)
4// ↓
5// SimulateB (physics writeback; Release fence)
6// ↓
7// HarvestPhase (read-only; Acquire fence)
8// ↓
9// BoundaryPhase (retire → compact → sync)
10// ↓
11// next driver.begin()
12//
13// SceneDb wraps the same chain: step() = SimulateA → SimulateB plus a
14// World-mirror flush; step_gpu() = Harvest → Boundary with your store.

Ready to write your first query?

Read the Docs