registry.rs (4003B)
1 use enostr::Pubkey; 2 use hashbrown::{hash_map::RawEntryMut, HashMap}; 3 use std::{ 4 fmt::Debug, 5 hash::{BuildHasher, Hash}, 6 }; 7 8 pub type ConversationId = u32; 9 10 #[derive(Default)] 11 pub struct ConversationRegistry { 12 next_id: ConversationId, 13 conversation_ids: HashMap<ConversationIdentifier, ConversationId>, 14 } 15 16 impl ConversationRegistry { 17 pub fn get(&self, id: ConversationIdentifierUnowned) -> Option<ConversationId> { 18 let hash = id.hash(self.conversation_ids.hasher()); 19 self.conversation_ids 20 .raw_entry() 21 .from_hash(hash, |existing| id.matches(existing)) 22 .map(|(_, v)| *v) 23 } 24 25 pub fn get_or_insert(&mut self, id: ConversationIdentifierUnowned) -> ConversationId { 26 let hash = id.hash(self.conversation_ids.hasher()); 27 let id_c = id.clone(); 28 29 let uid = match self 30 .conversation_ids 31 .raw_entry_mut() 32 .from_hash(hash, |existing| id.matches(existing)) 33 { 34 RawEntryMut::Occupied(entry) => *entry.get(), 35 RawEntryMut::Vacant(entry) => { 36 let owned = id.into_owned(); 37 let uid = self.next_id; 38 entry.insert(owned, uid); 39 self.next_id = self.next_id.wrapping_add(1); 40 uid 41 } 42 }; 43 tracing::info!("normalized conversation id: {id_c:?} | uid: {uid}"); 44 uid 45 } 46 47 pub fn insert(&mut self, id: ConversationIdentifier) -> ConversationId { 48 let uid = self.next_id; 49 self.conversation_ids.insert(id, uid); 50 self.next_id = self.next_id.wrapping_add(1); 51 52 uid 53 } 54 } 55 56 #[derive(Hash, Eq, PartialEq, Debug, Clone)] 57 pub enum ConversationIdentifier { 58 Nip17(ParticipantSet), 59 } 60 61 #[derive(Debug, Clone)] 62 pub enum ConversationIdentifierUnowned<'a> { 63 Nip17(ParticipantSetUnowned<'a>), 64 } 65 66 // Set of Pubkeys, sorted and deduplicated 67 #[derive(Hash, Eq, PartialEq, Debug, Clone)] 68 pub struct ParticipantSet(Vec<[u8; 32]>); 69 70 impl ParticipantSet { 71 pub fn new(mut items: Vec<[u8; 32]>) -> Self { 72 items.sort(); 73 items.dedup(); 74 Self(items) 75 } 76 } 77 78 #[derive(Clone)] 79 pub struct ParticipantSetUnowned<'a>(Vec<&'a [u8; 32]>); 80 81 impl<'a> ParticipantSetUnowned<'a> { 82 pub fn new(mut items: Vec<&'a [u8; 32]>) -> Self { 83 items.sort(); 84 items.dedup(); 85 Self(items) 86 } 87 88 pub fn normalize(&mut self) { 89 self.0.sort_unstable(); 90 self.0.dedup(); 91 } 92 93 fn hash_with<S: BuildHasher>(&self, build_hasher: &S) -> u64 { 94 build_hasher.hash_one(&self.0) 95 } 96 97 fn matches(&self, owned: &ParticipantSet) -> bool { 98 if self.0.len() != owned.0.len() { 99 return false; 100 } 101 102 self.0 103 .iter() 104 .zip(&owned.0) 105 .all(|(left, right)| *left == right) 106 } 107 108 fn into_owned(self) -> ParticipantSet { 109 let owned = self.0.into_iter().copied().collect(); 110 ParticipantSet::new(owned) 111 } 112 } 113 114 impl<'a> Debug for ParticipantSetUnowned<'a> { 115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 116 let hexes: Vec<String> = self 117 .0 118 .iter() 119 .map(|bytes| Pubkey::new(**bytes).hex()) 120 .collect(); 121 122 f.debug_tuple("ConversationParticipantsUnowned") 123 .field(&hexes) 124 .finish() 125 } 126 } 127 128 impl<'a> ConversationIdentifierUnowned<'a> { 129 fn hash<S: BuildHasher>(&self, build_hasher: &S) -> u64 { 130 match self { 131 Self::Nip17(participants) => participants.hash_with(build_hasher), 132 } 133 } 134 135 fn matches(&self, owned: &ConversationIdentifier) -> bool { 136 match (self, owned) { 137 (Self::Nip17(left), ConversationIdentifier::Nip17(right)) => left.matches(right), 138 } 139 } 140 141 fn into_owned(self) -> ConversationIdentifier { 142 match self { 143 Self::Nip17(participants) => ConversationIdentifier::Nip17(participants.into_owned()), 144 } 145 } 146 }