Class SystemDefinition
Immutable definition of a system node in the system DAG. Created by DagBuilder and consumed by DagScheduler.
public sealed class SystemDefinition
- Inheritance
-
SystemDefinition
- Inherited Members
Constructors
SystemDefinition()
public SystemDefinition()
Fields
AutoTickOffset
The value of TickOffset that asks the schedule to choose one. Negative so no real phase can collide with it.
public const int AutoTickOffset = -1
Field Value
Properties
Access
Declared read/write access for this system (RFC 07 — Unit 2). Populated from SystemBuilder declaration methods. Storage only at the Unit 2 stage — Unit 3 consumes this for conflict detection and DAG-edge derivation. Public read so tooling (Workbench RFC 07 Unit 6 surfacing) can project declarations into wire records; only the engine sets it.
public SystemAccessDescriptor Access { get; }
Property Value
CallbackAction
Delegate for CallbackSystem and QuerySystem systems. Invoked once per tick on a single worker. Receives TickContext with tick-level information.
public Action<TickContext> CallbackAction { get; init; }
Property Value
CanShed
Whether this system can be shed (dropped entirely) under severe overload.
public bool CanShed { get; set; }
Property Value
CellAmortize
Cell-level amortization denominator (issue #231). When greater than 0, this system processes only 1/N of the tier's clusters per run,
rotating through buckets as runCount % N — the system's own run count, so a TickDivisor cannot alias it (DSEL-02).
The callback's AmortizedDeltaTime carries this factor so integrations over the full elapsed time happen in one step — but
only when the run's selection actually strided, and multiplied by the rate divisors as well (DSEL-03). Must be paired with a
non-All TierFilter; amortizing the full cluster set without tier scoping is rejected at
RuntimeSchedule.Build.
public int CellAmortize { get; }
Property Value
ChangeFilterTypes
Component types for change-filtered reactive input. Null/empty means no filter (process all). OR logic: entity included if any filtered component was written.
public Type[] ChangeFilterTypes { get; set; }
Property Value
- Type[]
ChunksPerWorker
Oversubscription factor for parallel chunk dispatch. The effective worker-cap on chunk count becomes
round(WorkerCount × ChunksPerWorker) instead of WorkerCount; the cost rule
(CostBasedChunking) can go to twice that. Default 1.0f preserves the
pre-knob behaviour (one chunk per worker).
Use values above 1.0 (e.g. 1.5, 2.0) on parallel systems where worker efficiency suffers because a single slow chunk
holds back the critical path — extra chunks let fast workers steal more work via the existing dynamic chunk-claim
loop in DagScheduler. Values must be in the range [1.0, 64.0]; Build(IResource, ILogger)
rejects values outside that band. The upper bound also guards against the (int)MathF.Round overflow that would
silently collapse the chunk cap to 1 for absurd factors.
The final chunk count is also capped by ceil(entityCount / minChunkSize), so small populations won't
proliferate trivial chunks. Cost trade-off: every extra chunk pays its own prepare/dispatch overhead — the
Versioned path also creates an extra Transaction per chunk.
Under the entity rule that second cap is frequently the binding one, and this knob cannot lift it. A system
walking 320 entities against the default 64-entity floor gets ceil(320 / 64) = 5 chunks however high this
factor is set — which makes this knob inert on exactly the small-population systems its guidance above describes.
The cost rule (CostBasedChunking, the default from a system's second dispatch) has no
entity floor: it spreads the measured cost over round(WorkerCount × ChunksPerWorker) chunks, and up to twice
that when each would carry more than 100 µs.
public float ChunksPerWorker { get; }
Property Value
ConsumesQueueIndices
Indices of event queues this system consumes (reads from).
public int[] ConsumesQueueIndices { get; }
Property Value
- int[]
DagId
Flat global id of the Dag this system belongs to. Set by Build(IResource, ILogger). The DAG's Track determines whether the system is engine-internal (track carries EngineTag).
public int DagId { get; }
Property Value
ExplicitChunkCount
Explicit chunk count for chunked-parallel CallbackSystems (set via ChunkedParallel(int)). Zero means "derive from entity count" (the QuerySystem path); positive means "this is a chunked callback — dispatch this many chunks regardless of any entity input, and skip entity-prep infrastructure". The runtime fast-path in TyphonRuntime reads this and routes accordingly.
public int ExplicitChunkCount { get; }
Property Value
Index
Position in the systems array. Set by Build().
public int Index { get; }
Property Value
InputFactory
Factory returning the View that provides this system's entity input. Null for CallbackSystems.
public Func<ViewBase> InputFactory { get; set; }
Property Value
Instance
Back-pointer to the system instance backing this definition (class-based systems only — null for lambda-style registrations). Used by the scheduler to invoke Typhon.Engine.ChunkedCallbackSystem.OnShouldRun() and Typhon.Engine.ChunkedCallbackSystem.OnPrepare() polymorphically on typed systems.
public ISystem Instance { get; init; }
Property Value
IsCheckerboard
When true, this parallel QuerySystem uses two-phase checkerboard dispatch (issue #234). Clusters are split into Red
((cellX + cellY) % 2 == 0) and Black sets, dispatched as two sequential parallel phases within one DAG node.
No two adjacent cells are processed simultaneously, enabling conflict-free cross-cell reads/writes (e.g. pheromone diffusion).
public bool IsCheckerboard { get; }
Property Value
IsParallelQuery
True if this QuerySystem uses parallel chunk dispatch across workers. When true, Execute is called once per chunk with a per-chunk Transaction and entity slice. Set by RuntimeSchedule from Parallel().
public bool IsParallelQuery { get; }
Property Value
MinChunkSize
Smallest entity count this system will accept in a parallel chunk, overriding
ParallelQueryMinChunkSize. 0 (the default) inherits the global value.
public int MinChunkSize { get; }
Property Value
Remarks
What it is for. The global floor is a bet that per-entity work is roughly uniform across the schedule, because it is the same number of entities for every system. That bet fails whenever one system is far more expensive per entity than its neighbours — measured in the SWG Tatooine workload (#906) at 3.5 µs/entity for interest management against 9.3 ns/entity for creature AI in the same DAG, a spread of 375x. One floor cannot serve both: at 64 it starves the expensive system of workers, and lowered globally it splits the cheap ones into chunks whose dispatch costs more than their work.
How to choose one. It is a physical quantity, so it is checkable rather than felt: divide a system's
SystemTelemetry.WorkUs by its EntitiesProcessed and pick a chunk that holds enough work to be
worth a dispatch. Setting it because a system "feels heavy" is how this becomes a number nobody can justify.
Setting it opts the system out of the cost rule (CostBasedChunking), which does that division itself every tick and needs no floor.
Name
Unique name identifying this system in the DAG.
public string Name { get; init; }
Property Value
Phase
The DAG-local phase this system belongs to (RFC 07 / Q3). Set by Build(IResource, ILogger) from Phase(Phase), or the owning DAG's default phase when no phase was declared.
public Phase Phase { get; }
Property Value
PhaseIndex
Index into the owning ResolvedPhases for the resolved phase. DAG-local — every system has a valid phase.
public int PhaseIndex { get; }
Property Value
PipelineChunkAction
Delegate for Pipeline systems. Called per chunk with (chunkIndex, totalChunks). Multiple workers call this concurrently with distinct chunk indices. No TickContext — Pipeline's entity access goes through Gather/Scatter pipelines.
public Action<int, int> PipelineChunkAction { get; init; }
Property Value
PredecessorCount
Number of predecessor systems that must complete before this system can execute.
public int PredecessorCount { get; }
Property Value
Priority
Priority for overload management. Defined but not enforced until #201.
public SystemPriority Priority { get; init; }
Property Value
ProducesQueueIndices
Indices of event queues this system produces (writes to).
public int[] ProducesQueueIndices { get; }
Property Value
- int[]
ReactiveSkip
Reactive skip predicate evaluated after ShouldRun passes. Returns true if the system should be skipped because its change-filtered input is empty (no dirty entities and no newly added entities). Set by TyphonRuntime at init — not configured by game code.
public Func<bool> ReactiveSkip { get; set; }
Property Value
RealmRate
How this QuerySystem runs over realms simulated at a divisor (Realms D4, RLM-05). See RealmRate.
public RealmRate RealmRate { get; }
Property Value
Realms
The realms this QuerySystem is narrowed to (InRealms(params RealmId[])), or null for every runnable realm.
public IReadOnlyList<RealmId> Realms { get; }
Property Value
RuntimeChunkCount
Per-dispatch chunk-count override for chunked-callback systems. When non-zero, the runtime dispatches this many chunks for the next invocation instead
of the static ExplicitChunkCount from ChunkedParallel(int). Used by the fence work-planner to size
FenceExec based on per-tick work volume. Resets are caller-managed; zero means "use ExplicitChunkCount as before".
public int RuntimeChunkCount { get; set; }
Property Value
ShouldRun
Optional predicate evaluated before dispatch. If it returns false, the system is skipped and its successors are dispatched immediately. Null means always run. Evaluated before input query to avoid View refresh cost on false predicate.
public Func<bool> ShouldRun { get; init; }
Property Value
SourceFilePath
Repo-relative or absolute path to the source file declaring this system's entry method. Resolved from the delegate's PDB at DagBuilder registration. Empty/null when no PDB was available — the Workbench falls back to "no Source row" for the chunk.
public string SourceFilePath { get; init; }
Property Value
SourceLine
1-based line number of the entry method's first sequence point. 0 when not resolved.
public int SourceLine { get; init; }
Property Value
SourceMethod
Containing-method short name (e.g. MoveAllAnts) for display in the Source row.
public string SourceMethod { get; init; }
Property Value
Successors
Indices of successor systems in the DAG.
public int[] Successors { get; }
Property Value
- int[]
ThrottledTickDivisor
System runs every M ticks under overload. Default: 1 (every tick).
public int ThrottledTickDivisor { get; }
Property Value
TickDivisor
System runs every N ticks, at normal load — this is a declared rate, not an overload response. Default: 1 (every tick).
public int TickDivisor { get; }
Property Value
Remarks
The mechanism for a system that is expensive and does not need to be current — an economy sweep, a slow AI pass — on a tick whose rate is set by what the latency-critical systems need. ThrottledTickDivisor is the different thing: a rate the system falls back to only under overload.
A divided system must integrate by AmortizedDeltaTime, which is scaled by this —
DeltaTime is the tick's own interval and stepping by it advances at 1/N of real time, silently (DSEL-03).
Do not divide a system that must do work on tick 0. A divided system first runs on the tick its divisor admits, which is not necessarily tick 0.
That is bounded and harmless for a sweep; for initialisation, use a CallbackSystem that runs every tick and returns, or
OnFirstTick.
Refused with a change filter: a filter delivers one tick's dirty set once, so a divided system would drop the changes of every tick it skipped. Use CellAmortize for "react to changes, but spread the cost".
TickOffset
Which of its divisor's ticks this system runs on: it runs when tick % TickDivisor == TickOffset. Assigned by the schedule.
public int TickOffset { get; }
Property Value
Remarks
Why it is not always 0. With every offset at 0, every system sharing a divisor fires on the same tick, so all the slow work in a schedule lands together — a periodic spike, which the overload detector then reacts to, making the cost self-inflicted throttling rather than mere jitter. CellAmortize staggers one system's clusters; this staggers systems against each other.
The unit of assignment is the dependency closure, not the system (DSEL-04). Two divided systems the DAG joins keep the same offset, because separating them would stop them sharing a tick — survivable for a component read, which the DAG only ever ordered rather than made fresh, and a silent break for an event queue, whose drain timing is load-bearing (EQ-04).
The offset is below the TIGHTEST of the system's gates, not just TickDivisor, so every gate can reach it and none has to reduce it. That is also what gives a system declaring only ThrottledTickDivisor a phase: without one, every throttle-only system would re-synchronise onto phase 0 the moment overload engaged, which is the spike this exists to remove.
TierFilter
Simulation-tier dispatch filter (issue #231). When set to anything other than All, the parallel cluster partition is computed over the per-tier cluster list instead of Typhon.Engine.Internals.ArchetypeClusterState.ActiveClusterIds, and this system only processes entities whose cluster lives in a cell with a matching tier. The default All preserves the pre-#231 fast path.
public SimTier TierFilter { get; }
Property Value
TotalChunks
Number of work chunks for Pipeline systems. Determines parallelism granularity. For CallbackSystem/QuerySystem systems this is always 1. Mutable because Pipeline chunk count may change per tick based on query result set size.
public int TotalChunks { get; set; }
Property Value
Type
Execution model: PipelineSystem (multi-worker chunks), QuerySystem (single-worker entities), or CallbackSystem (inline).
public SystemType Type { get; init; }
Property Value
WritesVersioned
True if this parallel QuerySystem writes Versioned components (copy-on-write MVCC). When true, per-chunk Transactions are used instead of the optimized PointInTimeAccessor path. Set by RuntimeSchedule from WritesVersioned().
public bool WritesVersioned { get; }