diff --git a/src/core/mod.rs b/src/core/mod.rs index 489c590..5e0e490 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -535,15 +535,16 @@ async fn run_core_loop( error: identity_error.clone(), }) .await; - // Peers seen in the current/most-recent room, retained ACROSS leave so a - // rejoin can bootstrap to them. This is the fix for A8: the room creator's own - // ticket lists only themselves as host, so on rejoin these retained peers are - // their only dial targets. Reset when the joined ticket changes (different / - // brand-new room — see the Join handler). Keyed by id so updates refresh addr. - let known_peers: Arc>> = + // Peers seen per room, keyed by the room's join ticket, retained ACROSS leaves + // so a rejoin can bootstrap to them. This is the fix for A8: the room creator's + // own ticket lists only themselves as host, so on rejoin these retained peers + // are their only dial targets. Keyed by TICKET (not one flat set) so that + // revisiting a room after a detour through another one still remembers the + // first room's peers — the old single-set version cleared them on any ticket + // change, so an A→B→A bounce stranded the rejoiner with an empty bootstrap. + // Inner map keyed by peer id so updates refresh the address. + let known_peers: Arc>>> = Arc::new(std::sync::Mutex::new(HashMap::new())); - // The ticket of the room we last (re)joined, to detect room changes above. - let mut last_join_ticket: Option = None; let audio_backend = Arc::new(PipeWireBackend::new()); @@ -687,18 +688,16 @@ async fn run_core_loop( avatar: current_avatar.clone(), }; - // Retain peers across leave so a rejoin can dial them (A8). A - // different ticket (or a fresh "create", which mints a random - // ticket) shares no peers with the last room, so reset; rejoining - // the SAME ticket keeps the set — that's what gives the room - // creator a dial target on rejoin. Then snapshot it as the extra - // bootstrap set (resolution rides the persistent address book). - if last_join_ticket.as_deref() != Some(ticket_str.as_str()) { - known_peers.lock().unwrap().clear(); - } - last_join_ticket = Some(ticket_str.clone()); - let extra_bootstrap: Vec = - known_peers.lock().unwrap().values().cloned().collect(); + // Snapshot THIS room's retained peers (by ticket) as extra bootstrap + // targets so a rejoin can dial them (A8) — including after a detour + // through another room, since the per-ticket archive isn't cleared. + // Resolution rides the persistent address book. + let extra_bootstrap: Vec = known_peers + .lock() + .unwrap() + .get(&ticket_str) + .map(|peers| peers.values().cloned().collect()) + .unwrap_or_default(); crate::log_msg(&format!("Attempting room_state.join with self_state={:?}, extra_bootstrap={:?}", self_state, extra_bootstrap.iter().map(|a| a.id).collect::>())); if let Err(e) = room_state.join(&ticket_str, self_state.clone(), extra_bootstrap).await { @@ -1047,6 +1046,9 @@ async fn run_core_loop( let multitrack_events = multitrack.clone(); let is_multitrack_events = is_multitrack.clone(); let known_peers_events = known_peers.clone(); + // The ticket of the room this event loop serves, so peer add/remove + // updates the right per-ticket bucket in `known_peers` (A8 archive). + let ticket_events = ticket_str.clone(); let event_task = tokio::spawn(async move { while let Some(event) = room_events.recv().await { match event { @@ -1059,8 +1061,14 @@ async fn run_core_loop( // Hand over the full address so reconnects can dial // it directly rather than via the gossip lookup. transport_events.connect_peer(state.addr.clone()).await; - // Retain this peer as a future rejoin bootstrap target (A8). - known_peers_events.lock().unwrap().insert(peer_id, state.addr.clone()); + // Retain this peer under this room's ticket as a + // future rejoin bootstrap target (A8). + known_peers_events + .lock() + .unwrap() + .entry(ticket_events.clone()) + .or_default() + .insert(peer_id, state.addr.clone()); // If a multitrack recording is live, give this peer // its own stem track (silence-padded back to t=0). if is_multitrack_events.load(Ordering::Relaxed) @@ -1076,9 +1084,14 @@ async fn run_core_loop( cancel_grace_timer(&grace_timers_events, &peer_id); seen_connected_events.lock().unwrap().remove(&peer_id); // Graceful leave: drop them as a rejoin dial target - // (a transient PeerConnectionLost deliberately does - // NOT, so we can still re-dial a peer who's still up). - known_peers_events.lock().unwrap().remove(&peer_id); + // for this room (a transient PeerConnectionLost + // deliberately does NOT, so we can still re-dial a + // peer who's still up). + if let Some(peers) = + known_peers_events.lock().unwrap().get_mut(&ticket_events) + { + peers.remove(&peer_id); + } transport_events.disconnect_peer(peer_id).await; jitter_events.lock().await.remove(&peer_id); let _ = ui_tx_events.send(UiEvent::PeerLeft { id: peer_id }).await; @@ -1092,8 +1105,14 @@ async fn run_core_loop( // re-records the same address. cancel_grace_timer(&grace_timers_events, &peer_id); transport_events.connect_peer(state.addr.clone()).await; - // Refresh the retained rejoin target with the fresh addr (A8). - known_peers_events.lock().unwrap().insert(peer_id, state.addr.clone()); + // Refresh this room's retained rejoin target with the + // fresh addr (A8). + known_peers_events + .lock() + .unwrap() + .entry(ticket_events.clone()) + .or_default() + .insert(peer_id, state.addr.clone()); let _ = ui_tx_events.send(UiEvent::PeerUpdated { id: peer_id, state }).await; } RoomEvent::ChatMessage { from, name, text, .. } => {