notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

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 }