Table of Contents

Struct TickContext

Namespace
Typhon.Engine
Assembly
Typhon.Engine.dll

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

int

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

EntityAccessor

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

float

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

int

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

int

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

EventQueueBase[]

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

SideTransactionFactory

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

float

EndClusterIndex

Exclusive end index into ClusterIds for this worker's assigned cluster range.

public int EndClusterIndex { readonly get; init; }

Property Value

int

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)
The backing array is pooled — do not hold references beyond the system's Execute scope.
public IReadOnlyCollection<EntityId> Entities { readonly get; init; }

Property Value

IReadOnlyCollection<EntityId>

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

SpatialGridAccessor

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

int

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

long

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

TierBudgetMetrics

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

Transaction

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

int

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

queue EventQueue<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 via WritesEvents / produces:.

Returns

EventWriter<T>

Type Parameters

T

The 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.