Connection and Reconnect
The realtime connection opens after sign-in, re-assigns itself a server when it loses one, and tells the game only when that fails. This page is what a game can read about the connection, what it should react to, and the two knobs that shape the traffic.
When you need this
A connection indicator in the HUD, a "reconnecting" overlay, a decision about what to show while the world is not being updated. Everything else on the view plane depends on the connection and needs nothing from this page.
The states
UCrowdySDKSubsystem::GetUDPConnectionState() (Blueprint: Get UDP Connection State) returns an EUDPConnectionState:
| State | Meaning |
|---|---|
Disconnected | Not connected, and no reconnect in progress. |
Connecting | The connection is being opened. |
Connected | Up and carrying traffic. |
Reconnecting | The connection lost its server and is assigning another. Reported, not acted on: it recovers on its own. |
GateKeep (shown as Gate Kept) | The server is gatekeeping this client because the app is full. Nothing retries; the player has already been told. |
GetUDPConnectionState is a pure node, safe to read every frame. Reserve the events below for the moments you must react to.
The events on UCrowdySDKSubsystem, all Blueprint-assignable: On UDP Connection Success (OnUDPConnectionSuccess, an FOnUDPConnectionSuccess) whenever the connection comes up, including after a re-assignment, since that is a new session; On UDP Timed Out (OnUDPTimedOut, an FOnUDPTimedOut) when the transport gave up on a re-assignment, with the state at Reconnecting; On UDP Address Notify (OnUDPAddressNotify, an FOnUDPAddressNotify with bSuccess and GateKeep) when the server answers the access request. RequestUDPAccess() asks for the connection; the Quickstart never calls it because the sign-in does. Underneath sits UCrowdyUDPSubsystem, whose GetConnectionState() is the C++ read that needs no game thread and whose GetUDPNetworkStats() returns an FUDPNetworkStatistics (bytes and datagrams each way, messages per second, Ping, the client-notify counters and loss percentage); ResetUDPNetworkStats() zeroes it.
Coming up does more than flip the state: the same handler that broadcasts On UDP Connection Success joins the reliable-RPC channels, which is what makes the session channel "always joined".
After the server assigns this client, it needs a moment to install the session before it accepts spatial traffic. The SDK waits that out itself: the state stays Connecting until the assignment plus the readiness wait have elapsed, and only then does it turn Connected and broadcast On UDP Connection Success. You never time anything. What the SDK does not do is hold your sends for that moment: an actor update or a spatial event sent while the state is still Connecting goes out, or is dropped, and is never replayed once connected; the next replication interval is the recovery. So gate the thing that starts sending, the pawn's spawn or the entity component's activation, on this event or on Get UDP Connection State reading Connected, as the Quickstart does. A Multicast event is the one exception, queued until the channel bootstrap finishes (see the Gotchas).
When it drops
The low-level connection re-assigns a server on its own; the SDK only surfaces it as Reconnecting. A failed re-assignment is where the game hears about it: the SDK marks the state Reconnecting, broadcasts On UDP Timed Out, and immediately asks for the connection again itself, so a handler that polls the state inside that event reads Reconnecting, never Disconnected. Disconnected is what you read only after that request fails too. If the failure was the app being full, the state is GateKeep instead and nothing retries. The timeout that starts a re-assignment is UDPTimeoutSeconds in the project settings, and the address family is UDPProtocol (ECrowdyUDPProtocol: Auto takes the IPv4 address, IPv4 records that choice deliberately, IPv6 takes the IPv6 address and does not fall back). Both are managed by Crowdy Studio and shown read-only in Project Settings; see Config Sync.
StopNetworkOperations() reports the session as disconnected for the UI to read; it does not close the socket or interrupt a recovery already under way, deliberately. ToggleNetworkMessageProcessing() discards received messages for development.
The connection belongs to the transport and recovers on its own. Asking the UI to show a disconnect must not be able to cancel that.
Reacting to a drop
There is no reconnect code to write. The SDK asks for the connection again itself, and the game hears two events: On UDP Timed Out when a re-assignment failed and the SDK is opening the connection again, and On UDP Connection Success when it is back. Bind both once, in your Game Instance's Init, and do the visible thing in each.
The SDK calls it itself as soon as the event has been broadcast. A second request from the game is a second reconnect in flight for the same client.
The lantern village dims every placed lantern while the connection is down and restores them when it is back: WatchConnection, called from Init, binds the two events to HandleConnectionLost and HandleConnectionRestored.
- C++
- Blueprint
// LanternGameInstance.h
void WatchConnection();
UFUNCTION()
void HandleConnectionLost();
UFUNCTION()
void HandleConnectionRestored();
// LanternGameInstance.cpp
#include "EngineUtils.h"
#include "Lantern.h"
void ULanternGameInstance::WatchConnection()
{
// The SDK reconnects by itself after a timeout; a game only reacts to the two events.
UCrowdySDKSubsystem* Sdk = GetSubsystem<UCrowdySDKSubsystem>();
Sdk->OnUDPTimedOut.AddDynamic(this, &ULanternGameInstance::HandleConnectionLost);
Sdk->OnUDPConnectionSuccess.AddDynamic(this, &ULanternGameInstance::HandleConnectionRestored);
}
void ULanternGameInstance::HandleConnectionLost()
{
for (TActorIterator<ALantern> It(GetWorld()); It; ++It)
{
It->Light->SetIntensity(0.f);
}
}
void ULanternGameInstance::HandleConnectionRestored()
{
for (TActorIterator<ALantern> It(GetWorld()); It; ++It)
{
It->Light->SetIntensity(5000.f);
}
}
In the Game Instance Blueprint, from Event Init, a Crowdy SDK Subsystem getter feeds Bind Event to On UDP Timed Out and then Bind Event to On UDP Connection Success; the custom events OnConnectionLost and OnConnectionRestored are theirs. Dimming every lantern needs a loop, which is more than a six-node figure shows, so the graph binds the events and stops.
Bundled sends
Outbound replication messages of one network pass are packed into bundle datagrams. crowdy.net.send.bundle (default 1) controls it; 0 sends one datagram per message, a diagnostic switch rather than a setting a shipping build needs. The variable is read when a connection opens, so flipping it takes effect on the next connect, not on the live socket.
Signed inbound bundles
Once the connection is up, the transport tells the replication server what this client can read (a CLIENT_CAPABILITIES message), and repeats it every 15 seconds so a token refresh or a server-side move, both of which reset the server's record without saying so, never leaves it stale. A server at v0.30.0 or later then packs the notifications it sends you into MESSAGE_BUNDLE_SIGNED datagrams: the members inside carry no signature of their own, and one HMAC at the end covers the whole datagram. The transport checks that one signature, drops the datagram if it fails, and delivers the members exactly as it delivers any other notification. Your handlers see no difference; a datagram that used to cost one verification per member costs one. An older server ignores the message and keeps signing every member, which is also what the tagged 2.14.0 plugin receives, since it never announces itself.
crowdy.net.recv.signedbundles (default 1) is the switch; 0 stops the announcement and keeps the per-member form, for a before-and-after comparison rather than for shipping. Like crowdy.net.send.bundle it is read when a connection opens.
The receive budget
Inbound messages are delivered on the game thread, per frame, under two budgets: crowdy.net.receive.maxmessages (default 3072) caps how many messages one frame may deliver, and crowdy.net.receive.maxdrainms (default 4.0) caps the milliseconds one frame may spend delivering them. Whichever binds first ends that frame's drain and the rest waits for the next frame. The budget covers every message the connection receives, channel notifications and video fragments included, not only actor updates.
If drains keep ending on the time budget instead, delivery is costing more per message than the frame can afford, and the count is not the lever.
crowdy.net.receive.poolactorupdates (default 1) reuses actor update message objects instead of allocating one per message; crowdy.net.routes prints the network router's subscription table; crowdy.net.trace 1 logs socket, send, and receive activity, and crowdy.serialize.trace 1 the encode and decode of each payload. crowdy.serialize.scopes adds CPU trace scopes inside decode for profiling.
Deprecated calls
Five functions on UCrowdySDKSubsystem are deprecated (the compiler warns on each call) and do nothing useful: StartUDPTimeoutMonitoring, StopUDPTimeoutMonitoring, TriggerUdpHeartbeat, DeregisterAllReceptionLayers, and SetQueryEndpoint. The connection tracks its own liveness now; remove the calls.
Gotchas
Reconnectingneeds nothing from you. OnlyDisconnectedafter a timeout does.GateKeepis an answer, not a failure. Retrying against a full app asks a server with no room whether it has room.- Coming up joins the channels. A Multicast event sent before On UDP Connection Success is queued and flushed when the bootstrap finishes; see Channels.
crowdy.net.send.bundleandcrowdy.net.recv.signedbundleschanges take effect on the next connect.
Related
- Channels: what connecting joins.
- Video frames: the largest consumer of the receive budget.
- Authentication: the sign-in that requests the connection.
- Console variables.