Table of Contents

Interface ISubscriptionLink

Namespace
Typhon.Engine
Assembly
Typhon.Engine.dll

One connection's bytes, as the engine uses them: a reliable ordered message channel, an optional unreliable one, and a close.

public interface ISubscriptionLink

Implemented by the transport, one per connection. A WebSocket link wraps a WebSocket, a TCP link wraps a Socket and adds its own length prefix, and an in-process link wraps a queue — an embedded application, a test, or the differential oracle receives real frames over one and nothing in the engine can tell the difference (design/Subscriptions/04-transport.md § 2).

The engine pushes; it never polls. Send pumps walk the sessions that produced a frame this tick and call SendAsync(ReadOnlyMemory<byte>, CancellationToken) for each published frame, strictly in order, at most one in flight per session. That guarantee is the engine's, so a link needs no queue of its own and no ordering logic: it may assume the previous send completed before the next one starts.

Properties

Whether this link has an unreliable channel beside the reliable one.

bool SupportsUnreliable { get; }

Property Value

bool

false on TCP and WebSocket, which have no such thing. It exists for the WebTransport path, whose datagrams would carry per-tick motion snapshots beside — never instead of — the reliable blocks.

Methods

Closes the link.

void Close(ushort code, string reason)

Parameters

code ushort

The close code, from CloseCodes. It is a number: a WebSocket close frame carries it big-endian while a KICK carries it little-endian, so a link passes the value to its own API and never copies bytes between the two.

reason string

Why, at most KickReasonMaxBytes UTF-8 bytes — already truncated at a code-point boundary by the engine, so a link hands it straight to a close frame. May be null.

Called after the engine has already sent whatever the client needs to understand the close — a KICK carrying the same code, except for a protocol-major mismatch, which is never a KICK. A link must therefore let queued sends reach the peer before it closes, and must still deliver exactly one OnClosed(ushort, Exception) afterwards. Calling it twice is a no-op.

Sends one complete server-to-client message, reliably and in order.

ValueTask SendAsync(ReadOnlyMemory<byte> message, CancellationToken ct)

Parameters

message ReadOnlyMemory<byte>

The message's bytes. Borrowed for the duration of the returned task and not a byte longer — it is engine-owned native memory that is recycled the moment the task completes, so a link that keeps it, or that completes the task before the bytes have been handed to the socket, reads freed memory.

ct CancellationToken

Cancels the send. A cancelled send closes the link: a partially written message cannot be un-sent.

Returns

ValueTask

A task that completes when the link no longer needs message.

Back-pressure belongs here. A link that completes the task as soon as the bytes reach a kernel buffer tells the engine a slow client is keeping up, and the lag skip then has nothing to see; that is why the TCP transport bounds its send buffer and the WebSocket adapter caps kernel queueing. A failure is reported by faulting the task, never by throwing synchronously after the message was partly written.

A faulted task also means the bytes are released. Completing and faulting are the same promise about message: once the task has ended, whichever way, the link no longer reads it. The engine frees the slot on both paths — it has no third state to hold it in — so a link that faulted while a write was still reading the buffer would be handing one buffer to two writers. A link that cannot guarantee this must not fault until its write has finished.

Sends a datagram, best effort.

bool TrySendUnreliable(ReadOnlySpan<byte> datagram)

Parameters

datagram ReadOnlySpan<byte>

The bytes; copied by the link before this returns, or not sent at all.

Returns

bool

false when the link has no unreliable channel or the datagram could not be queued. Never an exception, never a wait.