feat(client): standardize connection outcomes (#13)
quality-gate / quality (push) Successful in 59s

This commit is contained in:
KyuubiYoru
2026-07-16 10:18:41 +02:00
parent b4b6072fe1
commit 94aba8a3bb
47 changed files with 2727 additions and 178 deletions
+51 -13
View File
@@ -29,6 +29,12 @@ RendezvousClientResult<PublishedSession> registered = await publisher.RegisterAs
DisplayName = "My server",
Visibility = ListingVisibility.Public,
Capacity = new() { CurrentPlayers = 1, MaximumPlayers = 8 },
DedicatedFallback = new()
{
AddressFamily = AddressFamilyKind.Ipv4,
Address = "203.0.113.40",
Port = 7777,
},
},
publisherCredential,
cancellationToken);
@@ -97,14 +103,22 @@ the accepted peer as connected. Register ordinary gameplay callbacks on
`networkEvents.GameplayEvents`; the routing listener reserves Rendezvous direct
requests for ticket validation and forwards every other callback normally.
The joining game first creates the HTTP attempt, then uses its own already-started
gameplay manager in the same frame loop:
The joining game first requests an attempt through the typed start API. It returns
exactly one issued attempt or one terminal service outcome, so service authority
is not confused with a later locally observed traversal failure:
```csharp
CreateJoinAttemptResponse attempt = (await joins.CreateAsync(
RendezvousConnectionStartResult start = await joins.CreateConnectionAttemptAsync(
createJoinRequest,
cancellationToken)).Value
?? throw new InvalidOperationException("Join issuance failed.");
cancellationToken: cancellationToken);
if (start.Outcome is { } serviceOutcome)
{
ShowConnectionFailure(serviceOutcome.Kind, serviceOutcome.Category);
return;
}
CreateJoinAttemptResponse attempt = start.Attempt
?? throw new InvalidOperationException("The typed start result was invalid.");
using RendezvousClientCoordinator client = new(
gameplayNetManager,
networkEvents,
@@ -116,12 +130,35 @@ client.Poll();
```
NAT introduction changes the client state to `Connecting`; it is not success.
Only `Connected` supplies `ConnectedPeer`. Call `Cancel()` and then `Poll()` for
local cancellation, or `CancelAsync(joins, cancellationToken)` to also revoke the
service attempt. Terminal client paths release all event subscriptions. Disposing
a coordinator never stops or disposes the caller-owned manager and does not touch
an in-flight peer; call `Cancel()` followed by `Poll()` first when that peer must
also be disconnected.
Only a `Connected` outcome supplies `Peer`. Completion exposes a stable kind,
source, category, phase, and elapsed duration. The default HTTP silence, punch,
and direct-connect budgets are five, ten, and five seconds respectively; configure
them through `RendezvousClientOptions` and `RendezvousCoordinatorOptions` when a
game has measured reasons to do so. The signed attempt expiry is always the
absolute upper bound.
Call `Cancel()` and then `Poll()` for local cancellation, or
`CancelAsync(joins, cancellationToken)` to also revoke the service attempt.
Terminal client paths complete exactly once and release all event subscriptions,
so late packets and callbacks are inert. Disposing a coordinator never stops or
disposes the caller-owned manager and does not touch an in-flight peer; call
`Cancel()` followed by `Poll()` first when that peer must also be disconnected.
After terminal completion, reporting is explicit and safe to retry. It sends only
the authenticated outcome enum and a coarse elapsed bucket—never the endpoint,
exact duration, diagnostic text, metadata, or player identity:
```csharp
RendezvousClientResult<ReportConnectionOutcomeResponse> report =
await client.ReportOutcomeAsync(joins, cancellationToken);
```
An optional `DedicatedFallback` is copied from the authoritative listing into the
issued attempt and terminal outcome. A local deployment may replace it with
`RendezvousCoordinatorOptions.DedicatedFallbackOverride`. The SDK only returns
the endpoint; it never connects automatically. The game must explicitly decide
whether to use it and then connect and authenticate through its own gameplay
transport. If the outcome has no fallback, v1 offers no relay.
Lease renewal is explicit and caller-controlled:
@@ -155,5 +192,6 @@ apply its own player identity, capacity, ban, and gameplay admission rules. Revo
the attempt on cancellation and dispose the validator during host shutdown so its
keyed ticket digests are zeroed.
See the repository's ADR 0007 for HTTP ownership/retry semantics and ADR 0008 for
join-capability and connection-ticket security semantics.
See the repository's ADR 0007 for HTTP ownership/retry semantics, ADR 0008 for
join-capability and connection-ticket security semantics, and ADR 0010 for typed
outcomes, deadlines, reporting, and caller-owned fallback.