Client Connections & Lifecycle
Every accepted socket becomes a stable
ClientContexthandle, with disconnect cleanup handled automatically.
Status: โ Implemented ยท Visibility: Public ยท Level: ๐ต Core ยท Category: Subscriptions
๐ฏ What it solves
Subscription delivery needs a server-side identity for "this specific client" that survives across ticks โ
something to hand to per-client View factories, to pass to SetSubscriptions, and to use for
application-level player identity. Without it, every feature would have to invent its own socket-to-player
mapping. Connections also need teardown: when a client disconnects, its View subscriptions and any per-client
View instances must be released or they leak. Typhon owns both the identity and the teardown โ game code only
ever touches a ClientContext, never a socket.
โ๏ธ How it works (in brief)
A dedicated listener thread runs a Socket.Accept() loop on a dual-stack TCP/IPv6 socket. Each accepted
connection gets TCP_NODELAY enabled and is assigned a process-lifetime-unique ConnectionId (monotonically
increasing). The connection's public face is a ClientContext carrying that ConnectionId plus a free-form
UserData slot the application can use for its own identity (player ID, auth token, anything). All
subscription APIs (SetSubscriptions, per-client View factories) take ClientContext, never the underlying
connection. When a client disconnects โ detected either by a failed send on the I/O thread or as a dead-socket
check during the Output phase โ the runtime removes it from every PublishedView's subscriber list, disposes
its per-client Views, and evicts it from the connection registry, all without game code intervention.
๐ป Usage
// The runtime hands a ClientContext to per-client View factories โ this is the main
// touchpoint where game code reads identity stashed on UserData.
runtime.PublishView("my_inventory", (ClientContext client) =>
{
using var tx = runtime.CreateSideTransaction();
int playerId = (int)client.UserData; // populated by application/auth code
return tx.Query<InventoryArch>()
.WhereField<InventoryItem>(i => i.OwnerId == playerId)
.ToView();
});
// The same ClientContext is the key for subscription management โ resolved internally
// by ConnectionId, so passing a stale (disconnected) context is a safe no-op.
runtime.SetSubscriptions(client, worldStatePub, inventoryPub);
Option (SubscriptionServerOptions) |
Default | Effect |
|---|---|---|
Port |
9000 | TCP listen port (dual-stack IPv6/IPv4) |
MaxClients |
0 (unlimited) | Connections beyond this are refused at accept time, before a ClientContext is created |
โ ๏ธ Guarantees & limits
ConnectionIdis a monotonically increasingint, unique for the server process's lifetime โ not reused, not persisted across restarts.UserDatais an untypedobjectslot Typhon never reads or interprets; populating, casting, and validating it is entirely the application's responsibility.- An accepted connection is implicitly trusted โ v1 has no built-in authentication or TLS (see TCP Transport & Wire Format).
- Disconnect cleanup (subscriber-list removal, per-client View disposal, registry eviction) runs exactly once
per connection โ
ClientConnection.Disposeis idempotent, so a race between the I/O thread and the Output phase detecting the same disconnect cannot double-clean. SetSubscriptions(client, ...)silently no-ops if the connection behindclientwas already torn down โ callers don't need to guard every call with a liveness check.- No public "client connected" notification exists yet in v1 โ a
ClientContextonly reaches game code via a per-client View factory call or as the argument to aSetSubscriptionscall already in flight. There is no hook to run application code exactly once at accept time.
๐งช Tests
- SubscriptionIntegrationTests
โ
Client_Connects_And_ReceivesTickDelta_WithSpawnedEntity: accept โClientContextโ subscribe โ receive, end to end over a real socket - SubscriptionStressTests โ
RapidConnectDisconnect_NoServerCrash: concurrent connect/disconnect churn exercises the idempotent cleanup path
๐ Related
- Related feature: Published Views, Server-driven subscriptions, TCP Transport & Wire Format