fix(call-to-play): resync after live delivery failure
A failed fire-and-forget event left one peer's active call state divergent until some later discovery or reconnect happened. During a LAN party that could leave different players looking at different rosters or chat. Fall back to the existing bidirectional handshake whenever a live Call to Play send fails. Its active-history exchange heals both sides immediately when the failure was transient. Document the related wall-clock synchronization assumption for deadline and countdown presentation. Test Plan: - `just fmt` -- passed - `just clippy` -- passed - `just test` -- passed, 147 peer tests - `just peer-cli-tests S48` -- passed - `git diff --cached --check` -- passed
This commit is contained in:
@@ -63,6 +63,11 @@ local action is applied to that history, sent to the UI, and broadcast to every
|
||||
currently known peer. An incoming live event is applied once and sent to the UI
|
||||
without being rebroadcast, which prevents forwarding loops.
|
||||
|
||||
If a live Call to Play delivery fails, the sender immediately falls back to a
|
||||
normal `Hello` / `HelloAck` exchange with that peer. The handshake carries the
|
||||
full active history in both directions, so a transient request failure heals
|
||||
without waiting for mDNS rediscovery or a later reconnect.
|
||||
|
||||
Actors are keyed by the peer's stable ID and carry a separate display name. The
|
||||
origin peer overwrites the actor ID on local actions, and live-event envelopes
|
||||
must match the known sending peer. This prevents duplicate default usernames
|
||||
@@ -74,6 +79,10 @@ uses the project's trusted-LAN identity model.
|
||||
join after a call was created reconstruct the same nominations, responses,
|
||||
RSVPs, chat, and terminal actions. The launcher reducer sorts the event stream
|
||||
deterministically and derives deadlines and check-in phases from timestamps.
|
||||
Those phases compare creator-supplied wall-clock timestamps with each viewer's
|
||||
local wall clock, so LAN machines are assumed to be synchronized closely enough
|
||||
for human-scale minute countdowns; clock skew shifts the displayed boundary by
|
||||
the same amount.
|
||||
There is deliberately no compatibility path for older protocol versions.
|
||||
|
||||
### 4) Shutdown
|
||||
|
||||
@@ -8,7 +8,13 @@ use std::{
|
||||
use lanspread_proto::{CallToPlayAction, CallToPlayEvent};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
use crate::{PeerEvent, context::Ctx, events, network::send_call_to_play_events};
|
||||
use crate::{
|
||||
PeerEvent,
|
||||
context::Ctx,
|
||||
events,
|
||||
network::send_call_to_play_events,
|
||||
services::{HandshakeCtx, perform_handshake_with_peer},
|
||||
};
|
||||
|
||||
const MAX_EVENTS: usize = 4_096;
|
||||
const MAX_ID_CHARS: usize = 128;
|
||||
@@ -173,15 +179,24 @@ pub(crate) async fn publish(
|
||||
|
||||
let peer_addresses = ctx.peer_game_db.read().await.get_peer_addresses();
|
||||
let peer_id = ctx.peer_id.clone();
|
||||
let handshake_ctx = HandshakeCtx::from_ctx(ctx, tx_notify_ui);
|
||||
ctx.task_tracker.spawn(async move {
|
||||
let deliveries = peer_addresses.into_iter().map(|peer_addr| {
|
||||
let event = event.clone();
|
||||
let peer_id = peer_id.clone();
|
||||
let handshake_ctx = handshake_ctx.clone();
|
||||
async move {
|
||||
if let Err(err) =
|
||||
send_call_to_play_events(peer_addr, peer_id.as_ref(), vec![event]).await
|
||||
{
|
||||
log::warn!("Failed to send Call to Play event to {peer_addr}: {err}");
|
||||
if let Err(resync_err) =
|
||||
perform_handshake_with_peer(handshake_ctx, peer_addr, None).await
|
||||
{
|
||||
log::warn!(
|
||||
"Failed to resync Call to Play history with {peer_addr}: {resync_err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user