refactor(core): single SelfPresence self-state builder
Core reconstructed PeerState in five command branches (join, mute toggle, avatar change, screen-share start/stop), each repeating the full field list. Factor a SelfPresence struct holding the sticky identity fields (name + avatar) with a to_state(is_muted, addr, sharing) builder that folds in the volatile per-announce fields, so the PeerState literal lives in one place. This is the precondition for adding a broadcast game-presence field without editing every call site. No behavior change. +1 unit test (359 lib total path). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,43 @@ pub struct PeerState {
|
||||
pub avatar: crate::avatar::Avatar,
|
||||
}
|
||||
|
||||
/// The locally-owned, "sticky" pieces of our own presence: the identity fields
|
||||
/// that change only on explicit user action and persist for the whole core
|
||||
/// session. The remaining `PeerState` fields are *volatile* — mute state, current
|
||||
/// `addr`, and the active screen-share ticket are read fresh at each announce — so
|
||||
/// they are passed into [`SelfPresence::to_state`] rather than stored here.
|
||||
///
|
||||
/// This is the single source of truth for building our own `PeerState`: core
|
||||
/// reconstructs self-state in several command branches (join, mute toggle, avatar
|
||||
/// change, screen-share start/stop), and centralizing the `PeerState` literal here
|
||||
/// means a new presence field is added in exactly one place instead of at every
|
||||
/// call site.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct SelfPresence {
|
||||
pub name: String,
|
||||
pub avatar: crate::avatar::Avatar,
|
||||
}
|
||||
|
||||
impl SelfPresence {
|
||||
/// Combine the sticky identity fields with the volatile per-announce fields
|
||||
/// (`is_muted`, current `addr`, active-share `sharing` ticket) into a full
|
||||
/// `PeerState` ready to announce over the gossip presence plane.
|
||||
pub fn to_state(
|
||||
&self,
|
||||
is_muted: bool,
|
||||
addr: iroh::EndpointAddr,
|
||||
sharing: Option<String>,
|
||||
) -> PeerState {
|
||||
PeerState {
|
||||
name: self.name.clone(),
|
||||
is_muted,
|
||||
addr,
|
||||
sharing,
|
||||
avatar: self.avatar.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RoomEvent {
|
||||
PeerJoined(EndpointId, PeerState),
|
||||
@@ -358,6 +395,27 @@ mod tests {
|
||||
assert_eq!(PeerSpeakTicket::restamp("not-a-ticket", EndpointAddr::from(me)), "not-a-ticket");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_presence_builds_peer_state_with_volatile_fields() {
|
||||
let addr = EndpointAddr::from(SecretKey::generate().public());
|
||||
let presence = SelfPresence {
|
||||
name: "Alice".to_string(),
|
||||
avatar: crate::avatar::Avatar::default(),
|
||||
};
|
||||
// Volatile fields come from the call; sticky fields from the struct.
|
||||
let muted = presence.to_state(true, addr.clone(), Some("ticket".to_string()));
|
||||
assert_eq!(muted.name, "Alice");
|
||||
assert!(muted.is_muted);
|
||||
assert_eq!(muted.addr.id, addr.id);
|
||||
assert_eq!(muted.sharing.as_deref(), Some("ticket"));
|
||||
assert_eq!(muted.avatar, crate::avatar::Avatar::default());
|
||||
// The same sticky presence yields different volatile fields per announce.
|
||||
let unmuted = presence.to_state(false, addr.clone(), None);
|
||||
assert!(!unmuted.is_muted);
|
||||
assert_eq!(unmuted.sharing, None);
|
||||
assert_eq!(unmuted.name, muted.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peer_state_serde_round_trip() {
|
||||
let original = sample_peer_state();
|
||||
|
||||
Reference in New Issue
Block a user