|
group2 0.1.0
CSE 125 Group 2
|
TCP stream client — sends input to the server and receives state updates. More...
#include <Client.hpp>
Classes | |
| struct | DelayedOutbound |
| One outbound UDP datagram queued for delayed send. More... | |
| struct | DelayedInbound |
| One inbound payload queued for delayed dispatch. More... | |
Public Types | |
| using | SnapshotApplyCallback |
| Called by Client to apply a raw snapshot; the registry-owning caller performs the actual load. | |
| using | RawParticleEventCallback = std::function<void(const NetParticleEvent& evt)> |
| Called for each replicated particle event before entity mapping; caller is responsible for mapping. | |
| using | MatchStateUpdateFn = std::function<void(const MatchStatePacket&)> |
| using | KillEventCallback = std::function<void(const NetKillEvent&)> |
| using | TextChatCallback = std::function<void(const net::chat::ServerTextChat&)> |
| using | RosterEventCallback = std::function<void(const PlayerRosterEvent&)> |
| Fired for server-authored mid-match join/leave notifications. | |
| using | VoiceFrameCallback = std::function<void(const net::voice::ServerVoiceFrame&)> |
| using | ShotDebugCallback = std::function<void(const net::shotdebug::ShotDebugCapture&)> |
| PR-20: callback for SHOT_DEBUG_REPORT. | |
| using | LobbyUpdateCallback = std::function<void(const LobbyUpdateEvent& update)> |
| Fired for each incremental lobby roster update broadcast from the server. | |
| using | LobbyStateCallback = std::function<void(const std::vector<LobbyPlayer>& players, ClientId localId)> |
| Fired once on join with the full lobby snapshot and this client's assigned ID. | |
| using | MatchConfigCallback = std::function<void(const MatchConfig& config)> |
Public Member Functions | |
| ConnectError | init (const char *addr, Uint16 port, const TransportConfig &transport={}, int timeoutMs=-1, const std::optional< net::UdpSessionTransport::RelayConfig > &relay=std::nullopt, const std::optional< net::UdpSessionTransport::PunchAssist > &punch=std::nullopt) |
| Create the TCP socket and connect to the server. | |
| void | shutdown () |
| Close the socket and release the resolved address. | |
| bool | isConnected () |
| True while a server connection is currently owned by this client. | |
| bool | send (const void *data, uint32_t size) |
| Send a raw message to the server. | |
| bool | sendInputSnapshot (const InputSnapshot &snap) |
| Push the latest input into the redundant ring and send to the server. | |
| void | resetInputHistory () |
| Clear redundant input history at the start of a new local match instance. | |
| bool | sendShotIntent (std::uint32_t shotInputTick, std::uint16_t targetClientId, const AnimSnapshot &targetAnim) |
| PR-27 (netsync): send a SHOT_INTENT packet describing the client's view of the target's animation state at fire time. | |
| bool | sendChatMessage (std::string_view message) |
| Send an all-chat message to the authoritative server. | |
| bool | sendPhysicsDiagRecording (bool enabled) |
| Ask the server to start/stop authoritative physics CSV recording. | |
| bool | sendVoiceFrame (std::uint16_t sequence, std::uint8_t frameMs, std::span< const std::uint8_t > opus) |
| Send one Opus-encoded voice frame. Voice rides unreliable sequenced UDP. | |
| bool | sendPlayerReady (bool ready) |
| Send a PLAYER_READY or PLAYER_UNREADY packet to the server. | |
| bool | sendStartMatch () |
| Send a START_MATCH packet to the server (host-only). | |
| bool | sendCancelStartMatch () |
| Send a CANCEL_START_MATCH packet to the server (host-only). | |
| bool | sendMatchConfig (const MatchConfig &config) |
| Send an updated match configuration to the server (host-only). | |
| bool | sendDiscoverySettings (const DiscoverySettings &settings) |
| Send updated discovery advertisement settings to the server. | |
| bool | sendServerShutdown () |
| Request server shutdown. The server accepts this only from the current host. | |
| void | sendPing () |
| Send a PING packet to the server for RTT measurement. | |
| void | updateStats (float dt) |
| Update bandwidth stats. Call once per frame with the frame delta time. | |
| void | onSnapshotApply (SnapshotApplyCallback fn) |
| Register the snapshot-apply callback; must be set before the first poll(). | |
| void | onRawParticleEvent (RawParticleEventCallback fn) |
| Register the raw particle-event callback. | |
| void | onMatchStateUpdate (MatchStateUpdateFn fn) |
| Register the match-state update callback, fired on every MATCH_STATE packet. | |
| void | onKillEvent (KillEventCallback fn) |
| Register the kill-event callback, fired for each replicated kill from the server. | |
| void | onTextChat (TextChatCallback fn) |
| void | onRosterEvent (RosterEventCallback fn) |
| Register the roster-event callback, fired for each mid-match join/leave. | |
| void | onVoiceFrame (VoiceFrameCallback fn) |
| void | onShotDebugReport (ShotDebugCallback fn) |
| Register the shot-debug callback (PR-20); fired for each SHOT_DEBUG_REPORT. | |
| void | onLobbyUpdate (LobbyUpdateCallback fn) |
| Register the incremental lobby-update callback. | |
| void | onLobbyState (LobbyStateCallback fn) |
| Register the full lobby-snapshot callback, fired once on join. | |
| void | onMatchConfig (MatchConfigCallback fn) |
| Register the match-config update callback. | |
| bool | poll () |
| Receive and process one pending message. | |
| const NetworkStats & | getNetStats () const |
| Access current network statistics. | |
| std::optional< MatchStatePacket > | getLatestMatchState () const |
| Return the latest match state packet received from the server, if any. | |
| std::optional< MatchConfig > | getLatestMatchConfig () const |
| Return the latest match configuration received from the server, if any. | |
| std::optional< std::pair< std::vector< LobbyPlayer >, ClientId > > | getLatestLobbyState () const |
| Return the latest lobby roster received from the server, if any. | |
| std::optional< std::string > | getLatestServerName () const |
| Return the latest server display name received from the server, if any. | |
| uint32_t | getServerAckedClientTick () const noexcept |
| Latest server-acked client predict tick. | |
| bool | consumeSnapshotApplied () noexcept |
| Whether a snapshot was applied since the last call to consumeSnapshotApplied(). | |
| float | getSnapshotAlpha () const |
| Render-time interpolation alpha based on snapshot timing. | |
| std::optional< entt::entity > | getServerLocalPlayerEntity () const |
| Server-assigned local-player entity before continuous_loader mapping. | |
| Uint64 | getInterpolationRenderTimeNs () const |
| Render time the renderer should display non-local entities at. | |
| Uint64 | getSnapshotIntervalNs () const |
| Approximate snapshot interval in nanoseconds. | |
| void | applyInterpolatedTransforms (Registry ®istry) |
| PR-19: overwrite Position.value (and InputSnapshot.yaw) for every non-local entity with an InterpolationBuffer to its interpolated render-time value. | |
| void | recordInterpolationSamples (Registry ®istry, Uint64 captureNs) |
| Record interpolation samples after the caller has applied a snapshot. | |
| void | setSimulatedLatencyMs (int totalMs) noexcept |
| Phase 6 testing: simulate added round-trip latency. | |
| int | getSimulatedLatencyMs () const noexcept |
| Get the currently-effective simulated total RTT. | |
| void | setSimulatedLossPercent (int percent) noexcept |
| Phase 6 testing: simulate UDP packet loss. | |
| int | getSimulatedLossPercent () const noexcept |
| Get the currently-effective simulated packet loss %. | |
| bool | sendGameplayReady () |
Static Public Attributes | |
| static constexpr size_t | k_inputRedundancy = 5 |
| Number of recent inputs included in each INPUT packet for redundancy. | |
Private Member Functions | |
| bool | acceptReliableSequence (std::uint32_t seq) |
| Sliding-window dedup helper. | |
| bool | shouldDropPacketLocked () |
| Roll the loss RNG. | |
| bool | sendUdpDelayed (net::PacketHeader hdr, const void *data, int len) |
| Send a UDP datagram immediately if the latency simulator is off, otherwise queue it for delayed send. | |
| void | recvUdpDelayed (std::vector< uint8_t > &&payload) |
| Enqueue an assembled UDP message into udpRecvQueue_ immediately if the simulator is off, otherwise hold it in the inbound delay queue. | |
| void | networkLoop () |
| Network-thread main loop body. | |
| void | dispatchMessage (const uint8_t *data, Uint32 size) |
| Decode and dispatch a single complete framed message. | |
| bool | applySnapshot (std::uint32_t snapshotTick, const std::uint8_t *bytes, Uint32 size, Uint32 wireSize) |
| Invoke snapshotApplyFn_ with raw snapshot bytes; updates delta-decode state on success. | |
Private Attributes | |
| MessageStream | msgStream {nullptr} |
| Framed message stream for server communication. | |
| NET_Address * | serverAddr = nullptr |
| Resolved server address. | |
| SnapshotApplyCallback | snapshotApplyFn_ |
| Applies snapshot bytes in the registry-owning caller. | |
| RawParticleEventCallback | rawParticleEventFn_ |
| Called for unmapped replicated particle events. | |
| MatchStateUpdateFn | matchStateUpdateFn_ |
| Called whenever a MATCH_STATE packet is received. | |
| KillEventCallback | killEventFn_ |
| Called for each replicated kill event from server. | |
| TextChatCallback | textChatFn_ |
| Called for server-broadcast all-chat messages. | |
| RosterEventCallback | rosterEventFn_ |
| Called for mid-match player join/leave updates. | |
| VoiceFrameCallback | voiceFrameFn_ |
| Called for proximity-routed Opus voice frames. | |
| ShotDebugCallback | shotDebugFn_ |
| PR-20: called for each SHOT_DEBUG_REPORT from server. | |
| LobbyUpdateCallback | lobbyUpdateFn_ |
| Called for each lobby update received from server. | |
| LobbyStateCallback | lobbyStateFn_ |
| Called once on join with the full lobby snapshot. | |
| MatchConfigCallback | matchConfigFn_ |
| Called whenever a MATCH_CONFIG packet is received. | |
| std::optional< entt::entity > | localPlayerEntity |
| The local player's entity, once assigned by the server. | |
| std::optional< MatchStatePacket > | latestMatchState_ |
| Most-recent MATCH_STATE packet; populated by dispatchMessage. | |
| std::optional< MatchConfig > | latestMatchConfig_ |
| Most-recent MATCH_CONFIG packet; populated by dispatchMessage. | |
| std::optional< std::vector< LobbyPlayer > > | latestLobbyPlayers_ |
| Most-recent lobby roster received from the server. | |
| std::optional< ClientId > | latestLobbyLocalId_ |
| This client's ID as reported in the LOBBY_STATE packet. | |
| std::optional< std::string > | latestServerName_ |
| Server display name reported by the latest LOBBY_STATE packet. | |
| std::vector< uint8_t > | keyframePayload_ |
| std::uint32_t | keyframeTick_ = 0 |
| NetworkStats | stats |
| Live network metrics. | |
| uint64_t | bytesSentWindow = 0 |
| uint64_t | bytesRecvWindow = 0 |
| uint64_t | udpSessionLastBytesSent_ = 0 |
| uint64_t | udpSessionLastBytesRecv_ = 0 |
| uint32_t | registryUpdatesWindow = 0 |
| float | statsAccumulator = 0.0f |
| std::array< InputSnapshot, k_inputRedundancy > | inputRing_ {} |
| size_t | inputRingHead_ = 0 |
| Next write index, wraps mod k_inputRedundancy. | |
| size_t | inputRingCount_ = 0 |
| Valid entries in ring; saturates at k_inputRedundancy. | |
| OutboundQueue | outbound_ |
| std::mutex | stateMutex_ |
| std::thread | networkThread_ |
| std::atomic< bool > | shouldStop_ {false} |
| std::atomic< bool > | socketDead_ {false} |
| Latched-true once the network thread observes a socket error. | |
| Uint64 | lastSnapshotApplyNs_ = 0 |
| Uint64 | prevSnapshotApplyNs_ = 0 |
| int | interpDelaySnapshots_ = 2 |
| Uint64 | snapshotIntervalEmaNs_ = k_defaultSnapshotIntervalNs |
| uint32_t | serverAckedClientTick_ = 0 |
| bool | snapshotAppliedFlag_ = false |
| TransportConfig | transportConfig_ |
| bool | usingUdpSession_ = false |
| net::UdpSessionTransport | session_ |
| net::UdpEndpoint | udpEndpoint_ |
| net::UdpEndpointAddr | serverUdpAddr_ |
| std::uint64_t | connectionId_ = 0 |
| uint16_t | udpInputSequence_ = 0 |
| Per-channel sequence for INPUT datagrams. | |
| std::uint16_t | chatClientSeq_ = 0 |
| std::vector< std::vector< uint8_t > > | udpRecvQueue_ |
| UDP-received payloads waiting for the game thread to dispatch. | |
| net::FragmentReassembler | unreliableReassembler_ |
| Phase 3d-4: reassembly buffer for fragmented snapshot datagrams on the Unreliable channel. | |
| std::uint32_t | reliableHighestSeen_ = 0 |
| Phase 3d-5: sliding-window bitset for ReliableOrdered channel dedup. | |
| uint64_t | reliableSeenBitmask_ = 0 |
| bool | reliableHasAny_ = false |
| False until the first reliable event arrives. | |
| std::atomic< int > | simulatedLatencyMs_ {0} |
| Total simulated RTT in ms (slider value, 0–200). | |
| std::atomic< int > | simulatedLossPercent_ {0} |
| Per-direction independent UDP-drop probability (slider value, 0–100). | |
| std::mt19937 | simLossRng_ {} |
| PRNG for the loss simulator. | |
| std::deque< DelayedOutbound > | simLatOutbound_ |
| std::deque< DelayedInbound > | simLatInbound_ |
Static Private Attributes | |
| static constexpr Uint64 | k_defaultSnapshotIntervalNs = 1'000'000'000ULL / 128ULL |
TCP stream client — sends input to the server and receives state updates.
| using Client::KillEventCallback = std::function<void(const NetKillEvent&)> |
| using Client::LobbyStateCallback = std::function<void(const std::vector<LobbyPlayer>& players, ClientId localId)> |
Fired once on join with the full lobby snapshot and this client's assigned ID.
| using Client::LobbyUpdateCallback = std::function<void(const LobbyUpdateEvent& update)> |
Fired for each incremental lobby roster update broadcast from the server.
| using Client::MatchConfigCallback = std::function<void(const MatchConfig& config)> |
| using Client::MatchStateUpdateFn = std::function<void(const MatchStatePacket&)> |
| using Client::RawParticleEventCallback = std::function<void(const NetParticleEvent& evt)> |
Called for each replicated particle event before entity mapping; caller is responsible for mapping.
| using Client::RosterEventCallback = std::function<void(const PlayerRosterEvent&)> |
Fired for server-authored mid-match join/leave notifications.
| using Client::ShotDebugCallback = std::function<void(const net::shotdebug::ShotDebugCapture&)> |
PR-20: callback for SHOT_DEBUG_REPORT.
Fired on the game thread inside dispatchMessage after the bytes have been parsed back into a ShotDebugCapture. The DebugUI registers this and pairs the report with its own client-side fire-time snapshot by shotInputTick.
Called by Client to apply a raw snapshot; the registry-owning caller performs the actual load.
Returns true on success; ackedTick is populated with the server-acked client predict tick.
| using Client::TextChatCallback = std::function<void(const net::chat::ServerTextChat&)> |
| using Client::VoiceFrameCallback = std::function<void(const net::voice::ServerVoiceFrame&)> |
|
private |
Sliding-window dedup helper.
Returns true if the caller should dispatch this sequence (i.e. it's new); false if it's a duplicate or too old to track.
| void Client::applyInterpolatedTransforms | ( | Registry & | registry | ) |
PR-19: overwrite Position.value (and InputSnapshot.yaw) for every non-local entity with an InterpolationBuffer to its interpolated render-time value.
Runs once per frame, BEFORE any renderer / particle / sfx / tracer code reads pos.value — every visual consumer thereafter sees a single, consistent interpolated source of truth.
Pre-PR-19 the renderer interpolated at 3 specific call sites while tracers, ribbon trails, smoke emitters, and beam endpoints kept reading raw pos.value. At 128 Hz × 2-snapshot delay (~16 ms) that's ~6-unit visible separation between the body and effects originating from "where the body really is right now".
Why mutate Position.value in place rather than ship a separate RenderPosition component? Two reasons: (1) every consumer already reads pos.value, no per-call-site touch-up needed; (2) the next snapshot apply unconditionally overwrites pos.value with the server-authoritative value (entt's continuous_loader), so the mutation has no lasting effect on registry state — it's effectively a per-frame derived view. Concrete cycle:
No-op when render-delay interp is disabled (interpDelaySnapshots_ == 0) or no buffered playback yet (renderTimeNs == 0). Excludes local player (which has no InterpolationBuffer because recordInterpolationSamples filters local out, and which is driven by client-side prediction anyway).
|
private |
Invoke snapshotApplyFn_ with raw snapshot bytes; updates delta-decode state on success.
|
inlinenodiscardnoexcept |
Whether a snapshot was applied since the last call to consumeSnapshotApplied().
Phase 5b: the game thread reads this each iterate() to know when to trigger reconciliation. Self-resets so a single snapshot only triggers a single reconciliation pass.
|
private |
Decode and dispatch a single complete framed message.
Called by poll() after pulling the bytes out of recvBuf.
|
nodiscard |
Render time the renderer should display non-local entities at.
PR-11 (server-perf): Valorant / Fortnite / Source-engine cl_interp style render-delay interpolation. Returns SDL_GetTicksNS() − delayTicks × snapshotIntervalNs() where delayTicks is read from GROUP2_CLIENT_INTERP_DELAY_SNAPSHOTS (default 2) and snapshotIntervalNs is the EMA of the last two snapshot apply times.
Returns 0 until two snapshots have been applied — callers treat 0 as "no buffered playback yet, fall back to the Phase-5a alpha path" (see entity_interpolation::sample).
Why N=2? At 32 Hz snapshot rate, 2 ticks ≈ 62.5 ms — enough that the renderer always has at least one buffered "future" sample to interpolate toward, so a single dropped snapshot is invisible. Trade-off: visual feedback for remote players is delayed by 62.5 ms from server truth, but lag-comp on the server already accounts for the client's display-time-to-fire-time gap (Phase 6 lag comp).
| std::optional< std::pair< std::vector< LobbyPlayer >, ClientId > > Client::getLatestLobbyState | ( | ) | const |
Return the latest lobby roster received from the server, if any.
|
inline |
Return the latest match configuration received from the server, if any.
|
inline |
Return the latest match state packet received from the server, if any.
|
inline |
Return the latest server display name received from the server, if any.
|
inline |
Access current network statistics.
|
inlinenodiscardnoexcept |
Latest server-acked client predict tick.
Phase 5b: when the server applies an INPUT packet stamped with client-tick T, then later sends a snapshot, the snapshot's local- player position represents state-after-applying-input-T. The client uses this value to know where to start replaying stored inputs from for reconciliation. 0 if no snapshot has been applied yet, or if the local player wasn't in the most recent snapshot.
|
inlinenodiscard |
Server-assigned local-player entity before continuous_loader mapping.
The registry-owning caller maps this through its snapshot loader.
|
inlinenodiscardnoexcept |
Get the currently-effective simulated total RTT.
|
inlinenodiscardnoexcept |
Get the currently-effective simulated packet loss %.
|
nodiscard |
Render-time interpolation alpha based on snapshot timing.
Phase 5a: with the snapshot rate decoupled from the physics tick rate (Phase 4a default = 32 Hz vs 128 Hz physics), the renderer can no longer use accumulator / k_physicsDt as the lerp alpha — that span is ~7.8 ms while two consecutive snapshots are ~31 ms apart. The result was the entity stepping in 7.8 ms bursts every 31 ms.
This helper returns alpha as (now - lastSnapshotApplyNs) / (lastSnapshotApplyNs - prevSnapshotApplyNs) clamped to [0, 1]. Self-correcting if the server changes its snapshot rate; freezes at 1.0 (entity at "current" pos, no extrapolation) when a snapshot is overdue. Returns 1.0 before two snapshots have arrived (no interpolation reference yet).
|
nodiscard |
Approximate snapshot interval in nanoseconds.
EMA over the last two snapshot apply times. Falls back to the default 32 Hz period (~31.25 ms) before two snapshots have arrived.
| ConnectError Client::init | ( | const char * | addr, |
| Uint16 | port, | ||
| const TransportConfig & | transport = {}, | ||
| int | timeoutMs = -1, | ||
| const std::optional< net::UdpSessionTransport::RelayConfig > & | relay = std::nullopt, | ||
| const std::optional< net::UdpSessionTransport::PunchAssist > & | punch = std::nullopt ) |
Create the TCP socket and connect to the server.
| addr | Hostname or IP address of the server. |
| port | TCP port the server is listening on. The UDP sidecar (Phase 3d) connects to the same port. |
| transport | Phase 3d: which UDP features to enable. |
| timeoutMs | Maximum time to wait for DNS resolution and TCP connection, in milliseconds. Negative waits forever. |
| bool Client::isConnected | ( | ) |
True while a server connection is currently owned by this client.
|
private |
Network-thread main loop body.
|
inline |
Register the kill-event callback, fired for each replicated kill from the server.
|
inline |
Register the full lobby-snapshot callback, fired once on join.
|
inline |
Register the incremental lobby-update callback.
|
inline |
Register the match-config update callback.
|
inline |
Register the match-state update callback, fired on every MATCH_STATE packet.
|
inline |
Register the raw particle-event callback.
|
inline |
Register the roster-event callback, fired for each mid-match join/leave.
|
inline |
Register the shot-debug callback (PR-20); fired for each SHOT_DEBUG_REPORT.
|
inline |
Register the snapshot-apply callback; must be set before the first poll().
|
inline |
|
inline |
| bool Client::poll | ( | ) |
Receive and process one pending message.
| void Client::recordInterpolationSamples | ( | Registry & | registry, |
| Uint64 | captureNs ) |
Record interpolation samples after the caller has applied a snapshot.
|
private |
Enqueue an assembled UDP message into udpRecvQueue_ immediately if the simulator is off, otherwise hold it in the inbound delay queue.
Caller MUST already hold stateMutex_.
| void Client::resetInputHistory | ( | ) |
Clear redundant input history at the start of a new local match instance.
| bool Client::send | ( | const void * | data, |
| uint32_t | size ) |
Send a raw message to the server.
| data | Pointer to the payload bytes. |
| size | Payload length in bytes. |
| bool Client::sendCancelStartMatch | ( | ) |
Send a CANCEL_START_MATCH packet to the server (host-only).
| bool Client::sendChatMessage | ( | std::string_view | message | ) |
Send an all-chat message to the authoritative server.
| bool Client::sendDiscoverySettings | ( | const DiscoverySettings & | settings | ) |
Send updated discovery advertisement settings to the server.
The server applies this only when the sender is the current lobby host. The packet controls whether the server publishes to the global directory and whether it responds to LAN discovery requests.
| bool Client::sendGameplayReady | ( | ) |
| bool Client::sendInputSnapshot | ( | const InputSnapshot & | snap | ) |
Push the latest input into the redundant ring and send to the server.
Each call appends snap to a small ring buffer (capacity k_inputRedundancy) and emits one INPUT packet containing the last N stored snapshots in tick order, oldest-first. The server dedups by InputSnapshot.tick against lastAppliedInputTick, so resending the last few inputs costs ~5x bandwidth on this packet type while making the input stream resilient to single-packet loss or reorder. Caller is responsible for stamping snap.tick with the current clientPredictTick before calling.
| bool Client::sendMatchConfig | ( | const MatchConfig & | config | ) |
Send an updated match configuration to the server (host-only).
| bool Client::sendPhysicsDiagRecording | ( | bool | enabled | ) |
Ask the server to start/stop authoritative physics CSV recording.
| void Client::sendPing | ( | ) |
Send a PING packet to the server for RTT measurement.
| bool Client::sendPlayerReady | ( | bool | ready | ) |
Send a PLAYER_READY or PLAYER_UNREADY packet to the server.
| bool Client::sendServerShutdown | ( | ) |
Request server shutdown. The server accepts this only from the current host.
| bool Client::sendShotIntent | ( | std::uint32_t | shotInputTick, |
| std::uint16_t | targetClientId, | ||
| const AnimSnapshot & | targetAnim ) |
PR-27 (netsync): send a SHOT_INTENT packet describing the client's view of the target's animation state at fire time.
Server pairs this with the corresponding INPUT (by (shooterClientId, shotInputTick)) and computes the anim-state delta against its own historical state at the rewound tick. Sent once per rising-edge of input.shooting. targetClientId = 0xFFFF when the client wasn't aiming at any specific target.
| bool Client::sendStartMatch | ( | ) |
Send a START_MATCH packet to the server (host-only).
|
private |
Send a UDP datagram immediately if the latency simulator is off, otherwise queue it for delayed send.
Caller MUST already hold stateMutex_ (matching the existing UDP send call sites).
| bool Client::sendVoiceFrame | ( | std::uint16_t | sequence, |
| std::uint8_t | frameMs, | ||
| std::span< const std::uint8_t > | opus ) |
Send one Opus-encoded voice frame. Voice rides unreliable sequenced UDP.
|
noexcept |
Phase 6 testing: simulate added round-trip latency.
Setting this to N causes outbound UDP datagrams to be held for N/2 ms before the kernel sees them, and incoming UDP messages to be held for N/2 ms before being delivered to the game-thread dispatch queue. The two halves combined produce an extra N ms of round-trip on top of whatever the real network has.
Range: 0–200 ms (slider-bounded; values outside the range are clamped on entry). 0 disables the simulator entirely — packets take the same fast path they did before this feature existed, no per-packet allocation, no extra mutex contention.
Why split into outbound + inbound halves? It models a symmetric real network: client→server and server→client each take half the RTT. With outbound-only delay, the server would see stale inputs but reply at full speed, leaving lag-comp's RTT/2 rewind formula systematically under-correcting by the inbound half. Symmetric delay matches the formula and gives the same hit-feel as a real WAN player at the slider's RTT.
|
noexcept |
Phase 6 testing: simulate UDP packet loss.
Setting this to N makes each outbound and each inbound UDP datagram an independent N% Bernoulli drop. With redundancy disabled (PING/PONG) you'll see N% loss directly. With redundancy on (5-input INPUT packets, 3x reliable events, fragmented snapshots) effective loss is much lower:
Range: 0–100. 0 disables. Higher values are accepted but pin the connection (the slider in the debug UI caps at 50 %).
|
private |
Roll the loss RNG.
Caller MUST already hold stateMutex_.
| void Client::shutdown | ( | ) |
Close the socket and release the resolved address.
| void Client::updateStats | ( | float | dt | ) |
Update bandwidth stats. Call once per frame with the frame delta time.
|
private |
|
private |
|
private |
|
private |
|
private |
|
private |
Valid entries in ring; saturates at k_inputRedundancy.
|
private |
Next write index, wraps mod k_inputRedundancy.
|
private |
|
staticconstexprprivate |
|
staticconstexpr |
Number of recent inputs included in each INPUT packet for redundancy.
At 128 Hz client tick rate, 5 inputs covers ~40 ms of redundancy — enough to recover from single-packet loss without retransmission, at the cost of ~5x INPUT-packet payload (still tiny: ~200 bytes/packet).
|
private |
|
private |
|
private |
Called for each replicated kill event from server.
|
private |
|
private |
This client's ID as reported in the LOBBY_STATE packet.
|
private |
Most-recent lobby roster received from the server.
|
private |
Most-recent MATCH_CONFIG packet; populated by dispatchMessage.
|
private |
Most-recent MATCH_STATE packet; populated by dispatchMessage.
|
private |
Server display name reported by the latest LOBBY_STATE packet.
|
private |
Called once on join with the full lobby snapshot.
|
private |
Called for each lobby update received from server.
|
private |
The local player's entity, once assigned by the server.
|
private |
Called whenever a MATCH_CONFIG packet is received.
|
private |
Called whenever a MATCH_STATE packet is received.
|
private |
Framed message stream for server communication.
|
private |
|
private |
|
private |
|
private |
Called for unmapped replicated particle events.
|
private |
|
private |
False until the first reliable event arrives.
|
private |
Phase 3d-5: sliding-window bitset for ReliableOrdered channel dedup.
Each event arrives k_reliableRedundancy times; only the first occurrence triggers dispatch. The window is 64 sequences wide, enough to cover RTT × redundancy at any reasonable network speed. Sequences older than that get dropped (very rare — would require 64 events to arrive during one RTT).
|
private |
|
private |
Called for mid-match player join/leave updates.
|
private |
|
private |
Resolved server address.
|
private |
|
private |
|
private |
PR-20: called for each SHOT_DEBUG_REPORT from server.
|
private |
|
private |
|
private |
|
private |
PRNG for the loss simulator.
Always accessed under stateMutex_ (every loss-roll site already holds it for other reasons). Seeded once in init() so behaviour varies run-to-run; not cryptographically secure, but the simulator is a debug aid, not a security boundary.
|
private |
Total simulated RTT in ms (slider value, 0–200).
Atomic so the UI thread can write while the network thread reads.
|
private |
Per-direction independent UDP-drop probability (slider value, 0–100).
Each outbound and each inbound datagram rolls against this; rolls below the threshold are dropped silently.
|
private |
|
private |
Applies snapshot bytes in the registry-owning caller.
|
private |
|
private |
Latched-true once the network thread observes a socket error.
poll() checks this and reports false to the game thread, so the existing "server died" disconnect path still works.
|
private |
|
private |
Live network metrics.
|
private |
|
private |
Called for server-broadcast all-chat messages.
|
private |
|
private |
|
private |
Per-channel sequence for INPUT datagrams.
|
private |
UDP-received payloads waiting for the game thread to dispatch.
Filled by the network thread under stateMutex_; drained by Client::poll. Format of each entry is [PacketType][rest] — same as a complete framed message off the TCP path so the same dispatchMessage() handles both.
|
private |
|
private |
|
private |
Phase 3d-4: reassembly buffer for fragmented snapshot datagrams on the Unreliable channel.
Tracks one in-progress reassembly per client (the most-recent sequence). Older fragments dropped via the FragmentReassembler's drop-stale rule.
|
private |
|
private |
Called for proximity-routed Opus voice frames.