Struct TickContext
Context passed to CallbackSystem and QuerySystem delegates during tick execution. Provides a valid Transaction for entity operations and a factory for side-transactions.
public struct TickContext
- Inherited Members
Remarks
Each CallbackSystem/QuerySystem receives its own TickContext with a dedicated Transaction created on the worker thread (respecting Transaction's single-thread affinity). The Transaction is committed automatically after the system completes — systems must NOT commit or dispose it.
Pipeline systems do NOT receive TickContext — they use an Action<T1, T2> and access entity data through Gather/Scatter pipelines (separate mechanism).
Fields
NonWorkerId
Sentinel WorkerId for a context that is not executing on a scheduler worker — the runtime lifecycle hooks
(OnFirstTick, OnShutdown), which run on the tick thread or the caller's thread rather than on a dispatched worker.
public const int NonWorkerId = -1
Field Value
Remarks
Deliberately negative so that indexing a per-worker array with it throws instead of silently aliasing worker 0 (#860). Code that indexes per-worker storage by WorkerId must either be unreachable from a lifecycle hook or handle this value explicitly.
Properties
Accessor
Per-worker EntityAccessor for parallel QuerySystems that do NOT write Versioned components. Provides Open/OpenMut with warm ChunkAccessor caches, zero per-entity dictionary overhead. Null when the system uses Transaction-based access (WritesVersioned=true or non-parallel systems).
public EntityAccessor Accessor { readonly get; init; }
Property Value
AmortizedDeltaTime
Elapsed time in seconds since the last tick this system processed this cell bucket (issue #231). Equal to DeltaTime when the system has
no cellAmortize. For amortized systems, AmortizedDeltaTime = DeltaTime × CellAmortize, which is the effective integration step for
movement, decay, or state-machine updates that happen once per amortization cycle.
public float AmortizedDeltaTime { readonly get; init; }
Property Value
ChunkCount
Total number of chunks for chunked-parallel systems. For non-chunked systems, always 1. Equal to the value passed to ChunkedParallel(int).
public int ChunkCount { readonly get; init; }
Property Value
ChunkIndex
Zero-based chunk index for chunked-parallel systems (e.g. ChunkedCallbackSystem).
Range: [0, ChunkCount). For non-chunked systems, always 0. Use to compute the per-chunk slice of arbitrary work:
start = ChunkIndex × totalSize / ChunkCount.
public int ChunkIndex { readonly get; init; }
Property Value
ClusterIds
Source array for the StartClusterIndex / EndClusterIndex partition (issue #231).
Points at Typhon.Engine.Internals.ArchetypeClusterState.ActiveClusterIds for systems with no tier filter, or at a per-tier (or per-bucket, for cellAmortize)
cluster list for tier-filtered systems. Null when the system has no cluster partition (non-parallel, non-cluster-eligible, or empty view).
public int[] ClusterIds { readonly get; init; }
Property Value
- int[]
ConsumedQueues
Event queues this system consumes. Null if the system has no consumed queues.
Cast to EventQueue<T> and call Drain(span) to read events; size the span from Count, which a short span throws on.
public EventQueueBase[] ConsumedQueues { readonly get; init; }
Property Value
CreateSideTransaction
Creates a side-transaction with the specified durability mode. Side-transactions commit independently and are NOT visible to the main tick Transaction (snapshot isolation — the main Transaction's TSN is fixed at creation). The caller owns the returned Transaction and must Dispose it.
public SideTransactionFactory CreateSideTransaction { readonly get; init; }
Property Value
Remarks
Use for economy-critical operations (trades, purchases, progression) that must be durable immediately, independent of the main tick's commit. Pass Commit to make SingleVersion-layout writes zero-loss, atomic and commit-scoped (no revision chain). Null when running without a DatabaseEngine.
DeltaTime
Elapsed time in seconds since the previous tick. Zero on the first tick.
public float DeltaTime { readonly get; init; }
Property Value
EndClusterIndex
Exclusive end index into ClusterIds for this worker's assigned cluster range.
public int EndClusterIndex { readonly get; init; }
Property Value
Remarks
Default 0. Check EndClusterIndex > StartClusterIndex for validity — a zero range means not applicable.
Entities
Filtered entity set for this system's execution.
- CallbackSystem: empty (no entity input)
- QuerySystem/PipelineSystem without changeFilter: full View entity set
- QuerySystem/PipelineSystem with changeFilter: dirty entities ∪ Added (only entities whose filtered components were written since last tick)
public IReadOnlyCollection<EntityId> Entities { readonly get; init; }
Property Value
SpatialGrid
Game-facing accessor for the engine's spatial grid (issue #232). Provides cell tier assignment, coordinate conversion, and multi-observer helpers (SetCellTierMin(int, int, SimTier), ResetAllTiers(SimTier), SetTierInAABB(float, float, float, float, SimTier)). Check IsValid before use — false when no grid is configured.
public SpatialGridAccessor SpatialGrid { readonly get; init; }
Property Value
StartClusterIndex
Inclusive start index into ClusterIds for this worker's assigned cluster range. Used by cluster-native systems that iterate
via ctx.Accessor.GetClusterEnumerator<TArch>(ctx.ClusterIds, ctx.StartClusterIndex, ctx.EndClusterIndex) for 2-3 ns/entity performance.
Default 0.
public int StartClusterIndex { readonly get; init; }
Property Value
Remarks
Before issue #231 this range indexed directly into ArchetypeClusterState.ActiveClusterIds. After #231 it indexes
into ClusterIds, which points at either the full ActiveClusterIds (for All systems) or a per-tier cluster
list (for tier-filtered systems). Game code that passed ctx.StartClusterIndex / ctx.EndClusterIndex to the old two-argument
GetClusterEnumerator(int, int) overload must migrate to the new three-argument overload that takes ClusterIds explicitly.
Default 0 (not -1) due to struct constraint. Check EndClusterIndex > StartClusterIndex for validity — a zero range means not applicable
(non-parallel, non-cluster, or entity-level dispatch).
TickNumber
Monotonically increasing tick number (0-based).
public long TickNumber { readonly get; init; }
Property Value
TierBudgetMetrics
Per-tier cost and entity count metrics from the previous tick (issue #234). Available to all systems — primarily consumed by TierAssignment
CallbackSystem for adaptive tier boundary adjustment. Zero on the first tick (no previous-tick data).
public TierBudgetMetrics TierBudgetMetrics { readonly get; init; }
Property Value
Transaction
Transaction for this system's entity operations (Spawn, Open, OpenMut, Query, etc.). Created on the current worker thread. Valid only during this system's execution. Do NOT Commit or Dispose — the scheduler manages the Transaction lifecycle. Null when running without a DatabaseEngine (standalone scheduler tests).
public Transaction Transaction { readonly get; init; }
Property Value
WorkerId
Worker slot for the thread executing this system chunk. Use it to index per-worker data structures (per-worker scratch buffers, accumulators) without any synchronization.
public int WorkerId { readonly get; init; }
Property Value
Remarks
Range is [0, WorkerCount] — inclusive of the upper bound, so per-worker arrays must be sized
WorkerSlotCount, not WorkerCount. Slots [0, WorkerCount) are the pool's worker threads; the extra slot
DispatcherWorkerId belongs to the dispatcher (timer) thread, which runs a system body when a skipped root chains its
successor into ExecuteInline. The dispatcher slot is disjoint from every worker slot by construction, which is what makes it safe to write
without synchronization — see DispatcherWorkerId for why disjointness, not quiescence, is the guarantee.
Single-worker runtimes never produce the dispatcher slot. WorkerCount == 1 takes ExecuteTickSingleThreaded, which runs the
whole tick inline on the tick thread and stamps slot 0 — truthfully, since that thread is the one and only worker. The dispatcher slot arises
only from multi-worker track dispatch. Size per-worker arrays by WorkerSlotCount regardless, so the same code is
correct under both.
Contexts handed to the runtime lifecycle hooks (OnFirstTick, OnShutdown) carry NonWorkerId instead — those run on the
tick thread or on whichever thread called Shutdown, outside system dispatch entirely, so no slot belongs to them.
Withdrawn in #860: this used to be documented as "for non-parallel systems, always 0", and four of the six construction sites left it at
the default 0 — so every thread claimed slot 0 and per-worker partitioning aliased instead of separating. A non-parallel system now reports
whichever pool worker picked it up that tick, which varies from tick to tick. Code that read perWorker[0] on the strength of the old
wording must aggregate across all slots instead.
Methods
Writer<T>(EventQueue<T>)
Opens a writer on queue bound to this context's worker slot (#861). Resolve it once per system body and push through it.
public readonly EventWriter<T> Writer<T>(EventQueue<T> queue)
Parameters
queueEventQueue<T>The queue to produce into, or null for a no-op writer — the
queue?.Push(...)idiom, kept working. The system should declare the queue viaWritesEvents/produces:.
Returns
- EventWriter<T>
Type Parameters
TThe event type.
Remarks
This is the only supported way to produce events: it supplies WorkerId for you, so a producer cannot accidentally write another worker's segment. Calling it from a lifecycle hook throws — those contexts carry NonWorkerId and own no segment.