Interface ISubscriptionLink
One connection's bytes, as the engine uses them: a reliable ordered message channel, an optional unreliable one, and a close.
public interface ISubscriptionLink
Remarks
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
SupportsUnreliable
Whether this link has an unreliable channel beside the reliable one.
bool SupportsUnreliable { get; }
Property Value
Remarks
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
Close(ushort, string)
Closes the link.
void Close(ushort code, string reason)
Parameters
codeushortThe close code, from CloseCodes. It is a number: a WebSocket close frame carries it big-endian while a
KICKcarries it little-endian, so a link passes the value to its own API and never copies bytes between the two.reasonstringWhy, 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.
Remarks
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.
SendAsync(ReadOnlyMemory<byte>, CancellationToken)
Sends one complete server-to-client message, reliably and in order.
ValueTask SendAsync(ReadOnlyMemory<byte> message, CancellationToken ct)
Parameters
messageReadOnlyMemory<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.
ctCancellationTokenCancels 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.
Remarks
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.
TrySendUnreliable(ReadOnlySpan<byte>)
Sends a datagram, best effort.
bool TrySendUnreliable(ReadOnlySpan<byte> datagram)
Parameters
datagramReadOnlySpan<byte>The bytes; copied by the link before this returns, or not sent at all.