Table of Contents

Class TyphonRuntime

Namespace
Typhon.Engine
Assembly
Typhon.Engine.dll

Top-level runtime for Typhon game servers. Wraps DatabaseEngine and DagScheduler, managing the per-tick UoW/Transaction lifecycle so game developers never handle commits manually.

public sealed class TyphonRuntime : IDisposable
Inheritance
TyphonRuntime
Implements
Inherited Members

Remarks

Each tick: creates a UoW (Deferred) → for each CallbackSystem/QuerySystem, creates a Transaction on the executing worker thread (respecting thread affinity) → commits after each system → flushes UoW at tick end.

Pipeline systems do not receive Transactions — their entity access goes through Gather/Scatter pipelines.

Properties

CurrentOverloadLevel

Current overload response level.

public OverloadLevel CurrentOverloadLevel { get; }

Property Value

OverloadLevel

CurrentTickNumber

Number of ticks executed so far.

public long CurrentTickNumber { get; }

Property Value

long

Engine

The underlying database engine.

public DatabaseEngine Engine { get; }

Property Value

DatabaseEngine

EntityCommands

This tick's deferred entity commands (#1099) — what ctx.Commands queues into. Null for a runtime with no database engine.

public EntityCommandBuffer EntityCommands { get; }

Property Value

EntityCommandBuffer

Remarks

Exposed for telemetry and for tests: Count, PendingEntities, OverflowCount and RejectedCount are the numbers that say whether a tick lost anything. Systems must queue through ctx.Commands, which binds the handle to their own worker slot.

LastTickOutcome

Outcome of the most recently completed tick. Refreshed every tick under every SystemExceptionPolicy, so it is never stale.

public TickOutcome LastTickOutcome { get; }

Property Value

TickOutcome

Remarks

This is the primary surface for a host that gates its own publication on tick success: it is pull-checkable, so the host's next loop iteration can ask "did that tick succeed?" without having subscribed to anything, and a host that wires up late does not silently miss the answer. OnTickAborted is the push counterpart.

Options

The runtime options this runtime was created with. The profiler derives session metadata from these.

public RuntimeOptions Options { get; }

Property Value

RuntimeOptions

Scheduler

The DAG scheduler driving tick execution.

public DagScheduler Scheduler { get; }

Property Value

DagScheduler

Subscriptions

What clients may see, send and hear: projections, interest profiles, session kinds, commands, events and metrics. Configure it before Start(); afterwards it is frozen and every declaring call throws.

public SubscriptionsRegistry Subscriptions { get; }

Property Value

SubscriptionsRegistry

Remarks

Replication belongs to the runtime, not to DatabaseEngine, because it rides the tick. The declarations here are compiled exactly once, at Start(), into the plan the subscriptions track walks and the catalog clients negotiate against — which is why a late declaration is refused loudly rather than silently ignored: it would be a declaration no client had ever been told about.

SubscriptionsCatalogJson

The canonical catalog this runtime compiled, as UTF-8 JSON. Empty before Start, and on a runtime that declares nothing.

public ReadOnlyMemory<byte> SubscriptionsCatalogJson { get; }

Property Value

ReadOnlyMemory<byte>

Remarks

Exactly the bytes WELCOME carries, which is the point: a host serving them at /typhon/catalog.json for build-time codegen and for tools must not be serving a second rendering of the same declarations, because the hash a client echoes is computed over these bytes. Never mutated after Start, so it is handed out directly rather than copied.

Systems

All registered systems including engine-internal ones (e.g. FenceExec). Use this when you need the full system list, indexed by canonical system index. For "the systems the user registered" use UserSystems instead.

public SystemDefinition[] Systems { get; }

Property Value

SystemDefinition[]

Telemetry

Telemetry ring buffer for diagnostic inspection.

public TickTelemetryRing Telemetry { get; }

Property Value

TickTelemetryRing

UserSystems

User-registered systems only (filters out engine-tagged-track systems such as the Fence DAG).

public SystemDefinition[] UserSystems { get; }

Property Value

SystemDefinition[]

Methods

Create(DatabaseEngine, Action<RuntimeSchedule>, RuntimeOptions, IResource, ILogger, IServiceProvider)

Creates a new TyphonRuntime from a DatabaseEngine and a schedule configuration.

public static TyphonRuntime Create(DatabaseEngine engine, Action<RuntimeSchedule> configure, RuntimeOptions options = null, IResource parent = null, ILogger logger = null, IServiceProvider serviceProvider = null)

Parameters

engine DatabaseEngine

The database engine for entity storage.

configure Action<RuntimeSchedule>

Action to register systems on the RuntimeSchedule.

options RuntimeOptions

Runtime options. If null, defaults are used.

parent IResource

Parent resource node. If null, uses the registry's Runtime node.

logger ILogger

Optional logger.

serviceProvider IServiceProvider

Optional DI container. When supplied, a profiler-launch override registered via AddTyphonProfiler is resolved from it. The zero-host-code profiler path works without it — pass it only when a host wants to override config in code.

Returns

TyphonRuntime

CreateSideTransaction(DurabilityMode, CommitDiscipline)

Creates a side-transaction with the specified durability mode. The caller owns the returned Transaction and must Commit + Dispose it. Side-transactions are NOT visible to the current tick's main Transactions (snapshot isolation).

public Transaction CreateSideTransaction(DurabilityMode durability = DurabilityMode.Immediate, CommitDiscipline discipline = CommitDiscipline.TickFence)

Parameters

durability DurabilityMode
discipline CommitDiscipline

Returns

Transaction

Remarks

Commit and dispose it before the system that created it returns (rule EW-01). This is an ordinary transaction — it can write an indexed field and therefore mutate a B+Tree — and nothing joins it to the tick. Held past the system's epilogue and committed later, it can land while the tick fence is rewriting those same structures, which is the one path inside the runtime that can violate the fence's exclusivity. Per-system transactions carry no such risk: the runtime commits and disposes those itself.

Dispose()

public void Dispose()

FatalStop()

Stops the runtime without running the OnShutdown hook — the fatal path, for a host reacting to OnTickAborted (issue #567).

public void FatalStop()

Remarks

OnShutdown deliberately runs its handlers inside an Immediate transaction, which is the right thing for an orderly stop and the wrong thing after a fatal tick: the abort means the simulation is logically incomplete, so committing a final round of shutdown writes on top of it would persist state derived from a tick that never finished. Everything else — subscription server, profiler, scheduler — is torn down exactly as in Shutdown().

This is not a quiescence point. It stops new ticks and does not wait for the tick in flight, which Shutdown() does when called off the tick thread. The expected caller is an OnTickAborted handler, which runs on the tick thread — so this typically returns into the very tick it is stopping, which then finishes and posts its accounting. Dispose the runtime when you need "nothing is running": Dispose() joins the tick thread.

ReadStats(int)

Reads the numbers an operator watches: tick percentiles against the target, overruns, the durability wait, per-system cost, entities per archetype, and the session figures when replication is running.

public RuntimeStatsSnapshot ReadStats(int windowTicks = 0)

Parameters

windowTicks int

How many recorded ticks to compute the percentiles over. 0 (the default) means one second's worth at the configured tick rate, which is the window the STATS wire block uses — so a caller comparing the two reads the same thing. Clamped to what the ring still holds.

Returns

RuntimeStatsSnapshot

A snapshot. Never null; every figure is zero on an engine that has not ticked.

Remarks

This is the read the STATS block could not be. Those values are computed once a second inside the subscriptions runtime, held in a private array, and written into a game client's frame — so nothing else could see one of them, and an engine with replication off, or whose application declared no metrics, computed none at all. This reads the ring and the engine directly: it answers on any running engine, with or without replication, whatever the application declared.

Not for the tick path. It allocates a snapshot and two arrays and walks the window. Call it from an HTTP endpoint, a CLI verb or a log timer, at human rate. It takes no lock and blocks no tick: the ring is a single-writer diagnostic structure, so a sample read while the driver is writing it may be torn, perturbing one sample in a percentile — the same trade the encoder makes, for the same reason.

RegisterContext<TContext>(TContext)

Bind an ambient context onto every registered system deriving from ChunkedCallbackSystem<TContext>. Must be called after Configure (systems must already be registered) and before Start().

public void RegisterContext<TContext>(TContext context) where TContext : class

Parameters

context TContext

Type Parameters

TContext

Shutdown()

Gracefully shuts down the runtime. Stops starting ticks and waits for the one in flight to finish — its systems, its fence, its flush — then stops the subscription server, fires OnShutdown, and stops the scheduler.

public void Shutdown()

Remarks

The wait is what makes the world read after this a fenced one. Without it a stop landing between a tick's systems and its fence left their writes committed and unfenced — moves the spatial index had not caught up with, realm changes not migrated — until a later fence or a reopen fixed them. Called on the tick thread (an OnTickAborted handler) it cannot wait for the tick it is part of; a tick that hangs past Typhon.Engine.TyphonRuntime.ShutdownDrainTimeout is logged and abandoned, as before.

Start()

Starts the scheduler (worker threads + tick driver).

public void Start()

StartSubscriptionTransport(ISubscriptionTransport)

Starts transport against this runtime's replication.

public void StartSubscriptionTransport(ISubscriptionTransport transport)

Parameters

transport ISubscriptionTransport

The listener. It is started once and handed the acceptor; stopping it is the caller's, through its own StopAsync.

Remarks

Both refusals are loud, and deliberately: a transport bound to a runtime that can never admit anyone is a listener that accepts connections and closes every one of them, which reads to an operator as a network fault rather than as a missing declaration.

Exceptions

InvalidOperationException

The runtime has not started, or it declares no subscriptions.

Events

OnCriticalOverload

Fires when overload reaches PlayerShedding. Game code decides what to do (migrate, disconnect, split).

public event Action<TyphonRuntime> OnCriticalOverload

Event Type

Action<TyphonRuntime>

OnFirstTick

Fired once on the first tick. Use to rebuild transient state after crash recovery. The callback receives a valid Transaction for entity operations.

public event Action<TickContext> OnFirstTick

Event Type

Action<TickContext>

OnShutdown

Fired during Shutdown(). Use for cleanup (save player state, etc.). The callback receives a dedicated Transaction (Immediate durability).

public event Action<TickContext> OnShutdown

Event Type

Action<TickContext>

OnTickAborted

Fires once when a tick is aborted under AbortTickAndStop, on the tick thread, after the tick has fully drained. Never fires for a successful tick, and never fires twice — the runtime is terminal after an abort. The handler should stop the runtime (FatalStop()) and let the host decide whether to exit or rebuild the engine.

"Drained" means every system has run or been skipped — it does NOT mean the tick is over. The handler runs INSIDE the tick, on the tick thread, and the tick's own end-of-tick accounting still runs after the handler returns. So FatalStop() called from here takes effect after the current tick completes, and CurrentTickNumber advances once more once it does. Do not block in this handler: you are holding up the tick thread.

public event Action<TyphonRuntime, TickOutcome> OnTickAborted

Event Type

Action<TyphonRuntime, TickOutcome>