Class DatabaseEngine
The main database engine class providing transaction-based access to component data.
public class DatabaseEngine : ResourceNode, IResource, IDisposable, IMetricSource, IDebugPropertiesProvider
- Inheritance
-
DatabaseEngine
- Implements
- Inherited Members
- Extension Methods
Remarks
DatabaseEngine registers itself under the DataEngine subsystem in the resource tree. ComponentTables are registered as children of this engine.
Properties
DBD
Registry of the component and archetype schema definitions registered on this engine instance.
public DatabaseDefinitions DBD { get; }
Property Value
EpochManager
Epoch manager coordinating safe, lock-free memory reclamation across concurrent readers and writers.
public EpochManager EpochManager { get; }
Property Value
- EpochManager
IsDisposed
true once the engine has been disposed; further operations on it are invalid.
public bool IsDisposed { get; }
Property Value
IsNewlyCreated
true when this open created the database bundle (it did not exist before); false when it reopened an existing one. Use it to gate one-time bootstrap work (initial data, first-run migrations). Note the engine already offers a crash-safe, declarative alternative via Seed(int, Action<Transaction>); prefer that for seeding, and this flag for cheaper conditional logic. It reflects file existence at open, so after a crash mid-initialisation it reads false — it is not, by itself, a "seeding completed" signal.
public bool IsNewlyCreated { get; }
Property Value
MMF
Backing paged memory-mapped file store holding all persisted segments of this database.
public ManagedPagedMMF MMF { get; }
Property Value
- ManagedPagedMMF
PageBodyReadCount
Number of page bodies read through TryReadPageBody(int, Span<byte>) since the engine opened. The Database File Map's detail tier is viewport-scoped, never a full-file scan — the A2 tests assert this counter grows only by the size of the requested region.
public long PageBodyReadCount { get; }
Property Value
Methods
BeginBulkLoad(BulkLoadOptions)
Open a new BulkLoadSession. Acquires the engine-wide exclusive bulk gate; throws BulkSessionAlreadyActiveException if a session is already open.
public BulkLoadSession BeginBulkLoad(BulkLoadOptions options = null)
Parameters
optionsBulkLoadOptionsOptional configuration. Defaults to a new BulkLoadOptions if null.
Returns
- BulkLoadSession
A fresh, open bulk session. Caller must CompleteBulkLoad() or Dispose() when done; the engine-wide gate is held until then.
Remarks
See claude/design/Durability/BulkLoad/01-api.md for the API reference and lifecycle. A bulk session is thread-affine — only the thread
that opened it may call methods on it. Regular transactions can run concurrently and see the pre-bulk snapshot.
Mutate through the returned session's Spawn<TArch>(params ReadOnlySpan<ComponentValue>), Update<T>(EntityId, in T), and Destroy(EntityId), then call CompleteBulkLoad() to commit and checkpoint the bulk durably. Disposing without completing discards it — none of the bulk's revisions become visible (UR-03).
Exceptions
- BulkSessionAlreadyActiveException
A bulk session is already open on this engine.
ClassifyAllPages(Span<StoragePageType>)
Classifies every file page by semantic type into dest (length ≥ file page count).
Built entirely from in-memory structures — the occupancy bitmap and the segment registry — with no data-page I/O. A page owned by no enumerated segment
and not a reserved root page resolves to
Unknown.
public void ClassifyAllPages(Span<StoragePageType> dest)
Parameters
destSpan<StoragePageType>
ConfigureSpatialGrid(SpatialGridConfig)
Sets the spatial grid configuration for this engine. Must be called before InitializeArchetypes(). Only required when at least one cluster-eligible archetype has a spatial component — non-spatial engines never need this call.
public void ConfigureSpatialGrid(SpatialGridConfig config)
Parameters
configSpatialGridConfig
Exceptions
- InvalidOperationException
Thrown if called after InitializeArchetypes().
CreateUnitOfWork(DurabilityMode, TimeSpan)
Creates a new Unit of Work — the durability boundary for user operations. All transactions must be created through a UoW.
public UnitOfWork CreateUnitOfWork(DurabilityMode durabilityMode = DurabilityMode.Deferred, TimeSpan timeout = default)
Parameters
durabilityModeDurabilityModeControls when WAL records become crash-safe. Default is Deferred.
timeoutTimeSpanLifetime timeout for this UoW. Default uses DefaultUowTimeout.
Returns
- UnitOfWork
A new UnitOfWork in Pending state.
Exceptions
- ResourceExhaustedException
All UoW registry slots are in use and the deadline expired.
Dispose(bool)
Releases engine resources following the standard dispose pattern. Idempotent — a no-op once IsDisposed is set. Runs the core teardown inside a try/finally so an owned service provider is still released even if a teardown step throws.
protected override void Dispose(bool disposing)
Parameters
EnumerateStorageSegments()
Enumerates every live logical segment's on-disk footprint — the per-ComponentTable segments plus the occupancy-bitmap segment.
Authoritative: walks the component-table registry rather than the page cache's lazy segment cache. Read-only; consumes only in-memory structures.
public IReadOnlyList<StorageSegmentDescriptor> EnumerateStorageSegments()
Returns
ForceCheckpoint()
Triggers an immediate checkpoint cycle. Flushes all dirty data pages, advances CheckpointLSN, and recycles WAL segments. No-op if WAL/checkpoint is not configured.
public void ForceCheckpoint()
GetAllComponentTables()
Returns all registered ComponentTables. Used by Typhon.Engine.DatabaseEngine.StatisticsWorker to iterate tables, and by external tooling (e.g., the Workbench Schema Inspector) to enumerate the schema. Values returns a stable snapshot, so concurrent registration is safe.
public IEnumerable<ComponentTable> GetAllComponentTables()
Returns
GetArchetypeClusterChunkCount(ushort)
Number of active cluster chunks for the given archetype in this engine. Returns 0 for legacy archetypes (non-cluster storage) or if the archetype has
no cluster state yet. Paired with GetArchetypeEntityCount(ushort) the caller can derive occupancy for cluster archetypes:
entityCount / (chunkCount * ArchetypeClusterInfo.ClusterSize).
public int GetArchetypeClusterChunkCount(ushort archetypeId)
Parameters
archetypeIdushort
Returns
GetArchetypeEntityCount(ushort)
Current entity count for the given archetype in this engine. Returns 0 if the archetype has no state in this engine (not registered or not yet initialized). Used by external tooling (Workbench Schema Inspector) to populate the Archetype panel — intentionally a scalar accessor so the internal Typhon.Engine.Internals.ArchetypeEngineState type does not need to leak into the public surface.
public long GetArchetypeEntityCount(ushort archetypeId)
Parameters
archetypeIdushort
Returns
GetComponentTable(Type)
Returns the ComponentTable registered for type, or null if it is not registered.
public ComponentTable GetComponentTable(Type type)
Parameters
typeTypeThe registered component type.
Returns
GetComponentTable<T>()
Returns the ComponentTable registered for T, or null if none is registered.
public ComponentTable GetComponentTable<T>() where T : unmanaged
Returns
Type Parameters
TThe registered unmanaged component type.
GetDebugProperties()
Gets detailed debug properties for diagnostic drill-down.
public IReadOnlyDictionary<string, object> GetDebugProperties()
Returns
- IReadOnlyDictionary<string, object>
A dictionary of property names to values. Values should be primitives, strings, or types that have meaningful
ToString()implementations.
Remarks
This method may allocate (returns new dictionary) since it's called infrequently. Callers should cache results if needed across multiple accesses.
Property naming convention: Use dot-notation for nested concepts (e.g., "ComponentSegment.Allocated", "Contention.MaxWaitUs").
GetIndexRef<T, TKey>(Expression<Func<T, TKey>>)
Returns an IndexRef for a secondary indexed field of component T.
Resolve once (cold path), reuse many times at zero cost (hot path).
public IndexRef GetIndexRef<T, TKey>(Expression<Func<T, TKey>> keySelector) where T : unmanaged
Parameters
keySelectorExpression<Func<T, TKey>>
Returns
Type Parameters
TTKey
GetPKIndexRef<T>()
Returns an IndexRef for the primary key index of component T.
Resolve once (cold path), reuse many times at zero cost (hot path).
public IndexRef GetPKIndexRef<T>() where T : unmanaged
Returns
Type Parameters
T
GetPageResidency(int, out bool, out bool)
Reports whether file page filePageIndex is currently resident in the page cache and, if so, whether it is dirty. Non-faulting — it
never triggers page I/O — so it must be called before TryReadPageBody(int, Span<byte>) for that page if pre-introspection residency is wanted (the
read faults the page in). An out-of-range index yields resident = false.
public void GetPageResidency(int filePageIndex, out bool resident, out bool dirty)
Parameters
GetRequiredAssemblies()
The schema-assembly manifest persisted in this database: the identity of every .NET assembly that declares a stored component or archetype. Read from the AssemblyR1 catalog, which is loaded on every open (including schemaless), so this is available without any user schema DLL. The core engine assembly is intentionally excluded — it is always loaded. Consumed by tooling (the Workbench) to locate and load the schema assemblies a file depends on.
public IReadOnlyList<AssemblyName> GetRequiredAssemblies()
Returns
GetSchemaHistory()
Returns all schema history entries from the audit trail, ordered by primary key (chronological).
public IReadOnlyList<SchemaHistoryR1> GetSchemaHistory()
Returns
GetWalTotalBytes()
Total byte size of the write-ahead log across all segment files (0 when no WAL is active).
public long GetWalTotalBytes()
Returns
InitializeArchetypes()
Initialize ECS archetype storage. For each registered archetype, allocates a per-archetype RawValueHashMap and connects component slots to their ComponentTables. Must be called after all components are registered.
public void InitializeArchetypes()
MarkClusterSlotDirty(int, int, int)
Mark a single entity slot as dirty in the cluster dirty bitmap. Call from game systems that use the direct
cluster iteration path (GetSpan<T>(Comp<T>)) and need migration detection or WAL
tracking. The chunkId comes from ChunkId.
Thread-safe (uses Or(ref int, int) internally via SetDirty).
public void MarkClusterSlotDirty(int archetypeId, int chunkId, int slotIndex)
Parameters
Open(string, Action<TyphonOptions>, ILoggerFactory)
Opens (or creates) a database backed by a self-contained service container — the standalone counterpart to the DI
AddTyphon(IServiceCollection, Action<TyphonOptions>) path. The returned engine owns the container and disposes
it as part of its own disposal, so using var dbe = DatabaseEngine.Open(...) is leak-free.
public static DatabaseEngine Open(string databaseFile, Action<TyphonOptions> configure = null, ILoggerFactory loggerFactory = null)
Parameters
databaseFilestringThe database path; the canonical extension is
.typhon. See DatabaseFile(string).configureAction<TyphonOptions>Optional further configuration — component/archetype registrations and advanced options.
loggerFactoryILoggerFactoryOptional logger factory. When null, logging resolves to a no-op (no output) while still satisfying the engine's required loggers.
Returns
- DatabaseEngine
A ready-to-use engine that owns and disposes its private container.
ReadMetrics(IMetricWriter)
Writes current metric values to the provided writer.
public void ReadMetrics(IMetricWriter writer)
Parameters
writerIMetricWriterThe writer to report metrics to. Call one or more Write* methods.
Remarks
Called during snapshot collection (every 1-5 seconds). Implementations should:
- Read fields and write to writer quickly (target < 100ns)
- Not allocate (no new objects, no LINQ, no string formatting)
- Not acquire locks (read fields directly, accept slight inconsistency)
- Not call other IMetricSource.ReadMetrics() — the graph handles recursion
Only report metric kinds that are relevant to this resource. For example, a TransactionPool might only report Capacity and Throughput, not Memory or DiskIO.
RegisterArchetype(Type)
Finalizes an [Archetype] into the process-global catalog (feature #514 Phase 5). Called by the per-assembly generated [ModuleInitializer] for
every archetype at assembly load — the barrier that replaces the manual Archetype<T>.Touch() startup calls. Runs the archetype's static ctor
(declaring its Comp<T> components) then finalizes it and its parent chain. Idempotent — an already-finalized archetype is a no-op. Public
because the generated registrar lives in the consumer's own assembly and cannot reach engine internals.
public static void RegisterArchetype(Type archetypeType)
Parameters
archetypeTypeTypeThe
[Archetype]class type to finalize.
RegisterByteMigration(string, int, int, int, int, ByteMigrationFunc)
Registers a byte-level migration function for scenarios where the old struct type is no longer available in code. Must be called before RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode) / RegisterComponentByType(Type, ChangeSet, SchemaValidationMode) for the target component.
public void RegisterByteMigration(string componentName, int fromRevision, int toRevision, int oldSize, int newSize, ByteMigrationFunc func)
Parameters
RegisterComponentByType(Type, ChangeSet, SchemaValidationMode)
Non-generic entry point for registering a component when the type is only known at runtime — e.g. types discovered via reflection from a plugin or
user-supplied schema DLL, as the Workbench does when loading *.schema.dll into a collectible AssemblyLoadContext.
public bool RegisterComponentByType(Type componentType, ChangeSet changeSet = null, SchemaValidationMode schemaValidation = SchemaValidationMode.Enforce)
Parameters
componentTypeTypeA closed unmanaged value type tagged with
[Component]. The unmanaged constraint from the generic overload is verified at runtime by the CLR when the method is specialized; non-blittable or reference-type inputs will throw from deep inside MakeGenericMethod(params Type[]).changeSetChangeSetOptional transactional change set. See RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode).
schemaValidationSchemaValidationModeSchema validation policy (default: Enforce).
Returns
- bool
Forwarded from RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode) — true on success.
Remarks
Internally invokes the generic RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode) via MakeGenericMethod(params Type[]).
Any TargetInvocationException raised by reflection is unwrapped with ExceptionDispatchInfo
so callers observe the real underlying exception — e.g. SchemaValidationException, SchemaDowngradeException,
SchemaMigrationException — with its original stack trace preserved.
Exceptions
- ArgumentNullException
componentTypeis null.- ArgumentException
componentTypeis not a closed value type.
- See Also
RegisterComponentCollectionFactory<T>()
Registers the AOT-safe backing-store factory for a ComponentCollection<T> element type. Called by source-generated component schema
providers (feature #514) for each collection field — the element type is a compile-time generic argument, so no MakeGenericType/Activator
is needed. Idempotent.
public static void RegisterComponentCollectionFactory<T>() where T : unmanaged
Type Parameters
TThe collection's element type.
RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode)
Registers component type T with the engine: builds its schema definition from the component accessor, validates it against any
persisted schema for the same component name, and creates or loads the backing ComponentTable.
public bool RegisterComponentFromAccessor<T>(ChangeSet changeSet = null, SchemaValidationMode schemaValidation = SchemaValidationMode.Enforce) where T : unmanaged
Parameters
changeSetChangeSetOptional change set to enlist the registration writes in.
schemaValidationSchemaValidationModeHow a persisted schema is reconciled with the runtime type; default Enforce.
Returns
Type Parameters
TA closed unmanaged value type tagged with
[Component].
Remarks
When a persisted schema exists for this component, schemaValidation governs how differences are reconciled. A Transient component may
not declare a ComponentCollection field — that combination is rejected at registration.
Exceptions
- InvalidOperationException
A Transient component declares a
ComponentCollectionfield.
RegisterMigration<TOld, TNew>(MigrationFunc<TOld, TNew>)
Registers a strongly-typed migration function that transforms component data from TOld to TNew.
Both types must have [Component] attributes with the same Name but different Revisions.
Must be called before RegisterComponentFromAccessor<T>(ChangeSet, SchemaValidationMode) / RegisterComponentByType(Type, ChangeSet, SchemaValidationMode) for the target component.
public void RegisterMigration<TOld, TNew>(MigrationFunc<TOld, TNew> func) where TOld : unmanaged where TNew : unmanaged
Parameters
funcMigrationFunc<TOld, TNew>
Type Parameters
TOldTNew
ResetPeaks()
Resets all high-water mark metrics (PeakBytes, MaxWaitUs, MaxUs, etc.) to current values.
public void ResetPeaks()
Remarks
Called by ResetAllPeaks() to enable windowed peak measurements. After displaying or exporting peak values, this method resets them so subsequent peaks represent new maxima since the last reset.
If this node has no high-water marks, implement as an empty method. Plain writes are sufficient — snapshots are taken every 1-5 seconds, so eventual visibility is acceptable.
RunStorageIntegrityCheck()
Audits storage-level invariants and returns every violation found. Pure read-only — touches the occupancy bitmap, the segment registry, and each segment's forward header chain; no data-page mutation, no allocation beyond a small issue list. Safe to call at any time on a live engine.
public StorageIntegrityReport RunStorageIntegrityCheck()
Returns
Remarks
Two classes of check, each independent:
- Popcount canary — the count of set bits in the occupancy bitmap must equal the sum of every registered segment's
Pages.Length, plus each segment's directory-map extension pages (the pages outside the root that hold the page-index list when the segment owns more than 500 data pages — they are bit-set but not part ofPages), plus each directory page's CK-05 twin (the alternate slot, bit-set but in no segment list), plus the reserved root pages (0..InitialReservedPageCount-1), plus the occupancy-reserve pages (data, map-extension, and the map-extension twin) held by the page-allocator machinery. Any orphan (bit set, no claimant) or phantom (claimant, bit clear) is reported as a hard durability/structural bug. - Chunk-segment capacity — for every Typhon.Engine.Internals.ChunkBasedSegment<TStore>,
AllocatedChunkCount + FreeChunkCountmust equalChunkCapacity. Desync indicates the segment's chunk free-list drifted from its on-page chunk bitmaps.
SetSpatialBarrierOnly<TArch>(bool)
Declare that TArch uses ClusterRef.WriteSpatial as the exclusive writer of its spatial component. Skips the fence-time
legacy scan for this archetype — both DetectClusterMigrations's dirtyBits sweep and RecomputeDirtyClusterAabbs's ActiveClusterIds
iteration are replaced with sparse iteration over Typhon.Engine.Internals.ArchetypeClusterState.ClusterProcessBitmap.
Only set when EVERY spatial-field write on this archetype goes through WriteSpatial. Mutations via raw GetSpan or OpenMut + Write
will be invisible to the engine's spatial maintenance after this is enabled. The TYPHON009 analyzer flags non-WriteSpatial mutation sites — once
those are zero, it's safe to opt in.
public void SetSpatialBarrierOnly<TArch>(bool value = true) where TArch : Archetype<TArch>
Parameters
valuebool
Type Parameters
TArch
TryReadPageBody(int, Span<byte>)
Copies the raw page (8 KiB — PageBaseHeader + metadata region + chunk data) of file page filePageIndex into
dest. The read goes through the page cache via the no-clock-sweep path (RequestPageEpochNoSweep): it does not bump the
eviction heuristic and does not satisfy the page's pending CRC verification — the caller recomputes the CRC itself.
Returns false on an out-of-range index or a lost eviction race; true with
dest filled otherwise.
public bool TryReadPageBody(int filePageIndex, Span<byte> dest)
Parameters
Returns
WriteTickFence(long, ChangeSet)
Serializes dirty SingleVersion component data to WAL at tick boundary. One TickFence chunk per SV ComponentTable. Called by the game loop at each tick boundary.
public long WriteTickFence(long tickNumber, ChangeSet changeSet = null)
Parameters
tickNumberlongMonotonic tick identifier.
changeSetChangeSetCaller-supplied ChangeSet for shared dirty-page tracking across the whole tick fence (typically the per-tick UoW's shared ChangeSet — see Typhon.Engine.UnitOfWork.ChangeSet). When null, a one-shot local ChangeSet is created and committed by this method itself (test/admin path: tests that invoke
WriteTickFencedirectly without a UoW retain their original behaviour).
Returns
- long
Highest LSN written, or 0 if nothing was serialized.
Events
OnMigrationProgress
Raised during schema migration to report progress to subscribers.
public event EventHandler<MigrationProgressEventArgs> OnMigrationProgress