use std::collections::{HashMap, HashSet}; use nostrdb::NoteKey; use notedeck::NoteRef; use crate::timeline::{ unit::{CompositeUnit, NoteUnit, NoteUnitFragment}, MergeKind, }; type StorageIndex = usize; /// Provides efficient access to `NoteUnit`s /// Useful for threads and timelines /// when reversed=false, sorts from newest to oldest #[derive(Debug, Default)] pub struct NoteUnits { reversed: bool, storage: Vec, lookup: HashMap, // the key to index in `NoteUnits::storage` order: Vec, // the sorted order of the `NoteUnit`s in `NoteUnits::storage` } impl NoteUnits { pub fn values(&self) -> Values<'_> { Values { set: self, front: 0, back: self.order.len(), } } pub fn contains_key(&self, k: &UnitKey) -> bool { self.lookup.contains_key(k) } pub fn new_with_cap(cap: usize, reversed: bool) -> Self { Self { reversed, storage: Vec::with_capacity(cap), lookup: HashMap::with_capacity(cap), order: Vec::with_capacity(cap), } } pub fn len(&self) -> usize { self.storage.len() } pub fn is_empty(&self) -> bool { self.storage.is_empty() } /// Get the kth index from 0..Self::len pub fn kth(&self, k: usize) -> Option<&NoteUnit> { if k >= self.order.len() { return None; } let idx = if self.reversed { self.order[self.order.len() - 1 - k] } else { self.order[k] }; Some(&self.storage[idx]) } /// Core bulk insert for already-built `NoteUnit`s /// Merges new `NoteUnit`s into `Self::storage` /// Updates `Self::order` fn merge_many_internal( &mut self, mut units: Vec, touched_indices: &[usize], ) -> InsertManyResponse { units.retain(|e| !self.lookup.contains_key(&e.key())); if units.is_empty() && touched_indices.is_empty() { return InsertManyResponse::Zero; } let mut touched = Vec::new(); if !touched_indices.is_empty() { touched = touched_indices.to_vec(); touched.sort_unstable(); // sort for later reinsertion touched.dedup(); self.order.retain(|i| touched.binary_search(i).is_err()); // temporarily remove touched from Self::order } units.sort_unstable(); units.dedup_by_key(|u| u.key()); let base = self.storage.len(); let mut new_order = Vec::with_capacity(units.len()); self.storage.reserve(units.len()); for (i, unit) in units.into_iter().enumerate() { let idx = base + i; let key = unit.key(); self.storage.push(unit); self.lookup.insert(key, idx); new_order.push(idx); } let inserted_new = new_order.len(); let front_insertion = if self.order.is_empty() || new_order.is_empty() { !new_order.is_empty() } else if self.reversed { // reversed is true, sorting should occur less recent to most recent (oldest to newest, opposite of `self.order`) let first_new = *new_order.first().unwrap(); // most recent unit of the new order let last_old = *self.order.last().unwrap(); // least recent unit of the current order // if the most recent unit of the new order is less recent than the least recent unit of the current order, // all current order units are less recent than the new order units. // In other words, they are all being inserted in the front self.storage[first_new] >= self.storage[last_old] } else { // reversed is false, sorting should occur most recent to least recent (newest to oldest, as it is in `self.order`) let last_new = *new_order.last().unwrap(); // least recent unit of the new order let first_old = *self.order.first().unwrap(); // most recent unit of the current order // if the least recent unit of the new order is more recent than the most recent unit of the current order, // all new units are more recent than the current units. // In other words, they are all being inserted in the front self.storage[last_new] <= self.storage[first_old] }; let mut merged = Vec::with_capacity(self.order.len() + new_order.len()); let (mut i, mut j) = (0, 0); while i < self.order.len() && j < new_order.len() { let index_left = self.order[i]; let index_right = new_order[j]; let left_unit = &self.storage[index_left]; let right_unit = &self.storage[index_right]; if left_unit <= right_unit { // the left unit is more recent than (or the same recency as) the right unit merged.push(index_left); i += 1; } else { merged.push(index_right); j += 1; } } merged.extend_from_slice(&self.order[i..]); merged.extend_from_slice(&new_order[j..]); // reinsert touched for touched_index in touched { let pos = merged .binary_search_by(|&i2| self.storage[i2].cmp(&self.storage[touched_index])) .unwrap_or_else(|p| p); merged.insert(pos, touched_index); } self.order = merged; if inserted_new == 0 { InsertManyResponse::Zero } else if front_insertion { InsertManyResponse::Some { entries_merged: inserted_new, merge_kind: MergeKind::FrontInsert, } } else { InsertManyResponse::Some { entries_merged: inserted_new, merge_kind: MergeKind::Spliced, } } } /// Merges `NoteUnitFragment`s /// `NoteUnitFragment::Single` is added normally /// if `NoteUnitFragment::Composite` exists already, it will fold the fragment into the `CompositeUnit` /// otherwise, it will generate the `NoteUnit::CompositeUnit` from the `NoteUnitFragment::Composite` pub fn merge_fragments(&mut self, frags: Vec) -> InsertManyResponse { let mut to_build: HashMap = HashMap::new(); // new composites by key let mut singles_to_build: Vec = Vec::new(); let mut singles_seen: HashSet = HashSet::new(); let mut touched = Vec::new(); for frag in frags { match frag { NoteUnitFragment::Single(note_ref) => { let key = note_ref.key; if self.lookup.contains_key(&UnitKey::Single(key)) { continue; } if singles_seen.insert(key) { singles_to_build.push(note_ref); } } NoteUnitFragment::Composite(c_frag) => { let key = c_frag.get_underlying_noteref().key; let composite_type = c_frag.get_type(); if let Some(&storage_idx) = self.lookup.get(&UnitKey::Composite(c_frag.key())) { if let Some(NoteUnit::Composite(c_unit)) = self.storage.get_mut(storage_idx) { if c_frag.get_latest_ref() < c_unit.get_latest_ref() { touched.push(storage_idx); } c_frag.fold_into(c_unit); continue; } } // aggregate for new composite use std::collections::hash_map::Entry; match to_build.entry(CompositeKey { key, composite_type, }) { Entry::Occupied(mut o) => { c_frag.fold_into(o.get_mut()); } Entry::Vacant(v) => { v.insert(c_frag.into()); } } } } } let mut items: Vec = Vec::with_capacity(singles_to_build.len() + to_build.len()); items.extend(singles_to_build.into_iter().map(NoteUnit::Single)); items.extend(to_build.into_values().map(NoteUnit::Composite)); self.merge_many_internal(items, &touched) } /// Convienience method to merge a single note pub fn merge_single_unit(&mut self, note_ref: NoteRef) -> InsertionResponse { match self.merge_many_internal(vec![NoteUnit::Single(note_ref)], &[]) { InsertManyResponse::Zero => InsertionResponse::AlreadyExists, InsertManyResponse::Some { entries_merged: _, merge_kind, } => InsertionResponse::Merged(merge_kind), } } pub fn latest_ref(&self) -> Option<&NoteRef> { if self.reversed { self.order.last().map(|&i| &self.storage[i]) } else { self.order.first().map(|&i| &self.storage[i]) } .map(NoteUnit::get_latest_ref) } } #[derive(Hash, PartialEq, Eq, Debug)] pub struct CompositeKey { pub key: NoteKey, pub composite_type: CompositeType, } #[derive(Hash, PartialEq, Eq, Debug)] pub enum CompositeType { Reaction, Repost, } #[derive(Hash, PartialEq, Eq, Debug)] pub enum UnitKey { Single(NoteKey), Composite(CompositeKey), } pub enum InsertManyResponse { Zero, Some { entries_merged: usize, merge_kind: MergeKind, }, } pub struct Values<'a> { set: &'a NoteUnits, front: usize, back: usize, } impl<'a> Iterator for Values<'a> { type Item = &'a NoteUnit; fn next(&mut self) -> Option { if self.front >= self.back { return None; } let idx = if !self.set.reversed { let i = self.front; self.front += 1; self.set.order[i] } else { self.back -= 1; self.set.order[self.back] }; Some(&self.set.storage[idx]) } } impl<'a> DoubleEndedIterator for Values<'a> { fn next_back(&mut self) -> Option { if self.front >= self.back { return None; } let idx = if !self.set.reversed { self.back -= 1; self.set.order[self.back] } else { let i = self.front; self.front += 1; self.set.order[i] }; Some(&self.set.storage[idx]) } } pub enum InsertionResponse { AlreadyExists, Merged(MergeKind), } #[cfg(test)] mod tests { use std::collections::{BTreeMap, HashSet}; use egui::ahash::HashMap; use enostr::Pubkey; use nostrdb::NoteKey; use notedeck::NoteRef; use pretty_assertions::assert_eq; use uuid::Uuid; use crate::timeline::{ unit::{ CompositeFragment, CompositeUnit, NoteUnit, NoteUnitFragment, Reaction, ReactionFragment, ReactionUnit, RepostFragment, }, NoteUnits, RepostUnit, }; #[derive(Default)] struct UnitBuilder { counter: u64, frags: HashMap, units: NoteUnits, } impl UnitBuilder { fn counter(&mut self) -> u64 { let res = self.counter; self.counter += 1; res } fn random_sender(&mut self) -> Pubkey { let mut out = [0u8; 32]; out[..8].copy_from_slice(&self.counter().to_le_bytes()); Pubkey::new(out) } fn build_reac_frag(&mut self, reacted_to: NoteRef) -> NoteUnitFragment { NoteUnitFragment::Composite(CompositeFragment::Reaction(ReactionFragment { noteref_reacted_to: reacted_to, reaction_note_ref: NoteRef { key: NoteKey::new(self.counter()), created_at: self.counter(), }, reaction: Reaction { reaction: "+".to_owned(), sender: self.random_sender(), }, })) } fn build_repost_frag(&mut self, reposting: NoteRef) -> NoteUnitFragment { NoteUnitFragment::Composite(CompositeFragment::Repost(RepostFragment { reposted_noteref: reposting, repost_noteref: self.new_noteref(), reposter: self.random_sender(), })) } fn insert_repost(&mut self, reposting: NoteRef) -> String { let repost = self.build_repost_frag(reposting); let id = Uuid::new_v4().to_string(); self.frags.insert(id.clone(), repost.clone()); self.units.merge_fragments(vec![repost]); id } fn insert_reac_frag(&mut self, reacted_to: NoteRef) -> String { let frag = self.build_reac_frag(reacted_to); let id = Uuid::new_v4().to_string(); self.frags.insert(id.clone(), frag.clone()); self.units.merge_fragments(vec![frag]); id } fn insert_reac_frag_pair(&mut self, reacted_to: NoteRef) -> (String, String) { let frag1 = self.build_reac_frag(reacted_to); let frag2 = self.build_reac_frag(reacted_to); self.units .merge_fragments(vec![frag1.clone(), frag2.clone()]); let id1 = Uuid::new_v4().to_string(); self.frags.insert(id1.clone(), frag1); let id2 = Uuid::new_v4().to_string(); self.frags.insert(id2.clone(), frag2); (id1, id2) } fn new_noteref(&mut self) -> NoteRef { NoteRef { key: NoteKey::new(self.counter()), created_at: self.counter(), } } fn insert_note(&mut self) -> String { let note_ref = NoteRef { key: NoteKey::new(self.counter()), created_at: self.counter(), }; let id = Uuid::new_v4().to_string(); self.frags .insert(id.clone(), NoteUnitFragment::Single(note_ref.clone())); self.units.merge_single_unit(note_ref); id } fn expected_reactions(&mut self, ids: Vec<&String>) -> NoteUnit { let mut reactions = BTreeMap::new(); let mut reaction_id = None; let mut senders = HashSet::new(); for id in ids { let NoteUnitFragment::Composite(CompositeFragment::Reaction(reac)) = self.frags.get(id).unwrap() else { panic!("got something other than reaction"); }; if let Some(prev_reac_id) = reaction_id { if prev_reac_id != reac.noteref_reacted_to { panic!("internal error"); } } reaction_id = Some(reac.noteref_reacted_to); reactions.insert(reac.reaction_note_ref, reac.reaction.clone()); senders.insert(reac.reaction.sender); } NoteUnit::Composite(CompositeUnit::Reaction(ReactionUnit { note_reacted_to: reaction_id.unwrap(), reactions, senders: senders, })) } fn expected_reposts(&mut self, ids: Vec<&String>) -> NoteUnit { let mut reposts = BTreeMap::new(); let mut reposted_id = None; let mut senders = HashSet::new(); for id in ids { let NoteUnitFragment::Composite(CompositeFragment::Repost(repost)) = self.frags.get(id).unwrap() else { panic!("got something other than repost"); }; if let Some(prev_reposted_id) = reposted_id { if prev_reposted_id != repost.reposted_noteref { panic!("internal error"); } } reposted_id = Some(repost.reposted_noteref); reposts.insert(repost.repost_noteref, repost.reposter); senders.insert(repost.reposter); } NoteUnit::Composite(CompositeUnit::Repost(RepostUnit { note_reposted: reposted_id.unwrap(), reposts, senders, })) } fn expected_single(&mut self, id: &String) -> NoteUnit { let Some(NoteUnitFragment::Single(note_ref)) = self.frags.get(id) else { panic!("fail"); }; NoteUnit::Single(*note_ref) } fn asserted_at(&self, index: usize) -> NoteUnit { self.units.kth(index).unwrap().clone() } fn aeq(&mut self, units_kth: usize, expect: Expect) { assert_eq!( self.asserted_at(units_kth), match expect { Expect::Single(id) => self.expected_single(id), Expect::Reaction(items) => self.expected_reactions(items), Expect::Repost(items) => self.expected_reposts(items), } ); } } enum Expect<'a> { Single(&'a String), Reaction(Vec<&'a String>), Repost(Vec<&'a String>), } #[test] fn test_reactions1() { let mut builder = UnitBuilder::default(); let reaction_note = builder.new_noteref(); let single0 = builder.insert_note(); builder.aeq(0, Expect::Single(&single0)); let reac1 = builder.insert_reac_frag(reaction_note); builder.aeq(0, Expect::Reaction(vec![&reac1])); builder.aeq(1, Expect::Single(&single0)); let single1 = builder.insert_note(); builder.aeq(0, Expect::Single(&single1)); builder.aeq(1, Expect::Reaction(vec![&reac1])); builder.aeq(2, Expect::Single(&single0)); let reac2 = builder.insert_reac_frag(reaction_note); builder.aeq(0, Expect::Reaction(vec![&reac2, &reac1])); builder.aeq(1, Expect::Single(&single1)); builder.aeq(2, Expect::Single(&single0)); let single2 = builder.insert_note(); builder.aeq(0, Expect::Single(&single2)); builder.aeq(1, Expect::Reaction(vec![&reac2, &reac1])); builder.aeq(2, Expect::Single(&single1)); builder.aeq(3, Expect::Single(&single0)); let reac3 = builder.insert_reac_frag(reaction_note); builder.aeq(0, Expect::Reaction(vec![&reac1, &reac2, &reac3])); builder.aeq(1, Expect::Single(&single2)); builder.aeq(2, Expect::Single(&single1)); builder.aeq(3, Expect::Single(&single0)); } #[test] fn test_reactions2() { let mut builder = UnitBuilder::default(); let reaction_note1 = builder.new_noteref(); let reaction_note2 = builder.new_noteref(); let single0 = builder.insert_note(); builder.aeq(0, Expect::Single(&single0)); let reac1_1 = builder.insert_reac_frag(reaction_note1); builder.aeq(0, Expect::Reaction(vec![&reac1_1])); builder.aeq(1, Expect::Single(&single0)); let reac2_1 = builder.insert_reac_frag(reaction_note2); builder.aeq(0, Expect::Reaction(vec![&reac2_1])); builder.aeq(1, Expect::Reaction(vec![&reac1_1])); builder.aeq(2, Expect::Single(&single0)); let single1 = builder.insert_note(); builder.aeq(0, Expect::Single(&single1)); builder.aeq(1, Expect::Reaction(vec![&reac2_1])); builder.aeq(2, Expect::Reaction(vec![&reac1_1])); builder.aeq(3, Expect::Single(&single0)); let reac1_2 = builder.insert_reac_frag(reaction_note1); builder.aeq(0, Expect::Reaction(vec![&reac1_2, &reac1_1])); builder.aeq(1, Expect::Single(&single1)); builder.aeq(2, Expect::Reaction(vec![&reac2_1])); builder.aeq(3, Expect::Single(&single0)); let single2 = builder.insert_note(); builder.aeq(0, Expect::Single(&single2)); builder.aeq(1, Expect::Reaction(vec![&reac1_2, &reac1_1])); builder.aeq(2, Expect::Single(&single1)); builder.aeq(3, Expect::Reaction(vec![&reac2_1])); builder.aeq(4, Expect::Single(&single0)); let reac1_3 = builder.insert_reac_frag(reaction_note1); builder.aeq(0, Expect::Reaction(vec![&reac1_2, &reac1_1, &reac1_3])); builder.aeq(1, Expect::Single(&single2)); builder.aeq(2, Expect::Single(&single1)); builder.aeq(3, Expect::Reaction(vec![&reac2_1])); builder.aeq(4, Expect::Single(&single0)); let reac2_2 = builder.insert_reac_frag(reaction_note2); builder.aeq(0, Expect::Reaction(vec![&reac2_1, &reac2_2])); builder.aeq(1, Expect::Reaction(vec![&reac1_2, &reac1_1, &reac1_3])); builder.aeq(2, Expect::Single(&single2)); builder.aeq(3, Expect::Single(&single1)); builder.aeq(4, Expect::Single(&single0)); } #[test] fn test_reactions3() { let mut builder = UnitBuilder::default(); let reaction_note1 = builder.new_noteref(); let single1 = builder.insert_note(); builder.aeq(0, Expect::Single(&single1)); let reac0 = builder.insert_reac_frag(reaction_note1); builder.aeq(0, Expect::Reaction(vec![&reac0])); builder.aeq(1, Expect::Single(&single1)); let (reac1, reac2) = builder.insert_reac_frag_pair(reaction_note1); builder.aeq(0, Expect::Reaction(vec![&reac0, &reac1, &reac2])); builder.aeq(1, Expect::Single(&single1)); let single2 = builder.insert_note(); builder.aeq(0, Expect::Single(&single2)); builder.aeq(1, Expect::Reaction(vec![&reac0, &reac1, &reac2])); builder.aeq(2, Expect::Single(&single1)); } #[test] fn test_repost() { let mut builder = UnitBuilder::default(); let repost_note = builder.new_noteref(); let single1 = builder.insert_note(); builder.aeq(0, Expect::Single(&single1)); let repost1 = builder.insert_repost(repost_note); builder.aeq(0, Expect::Repost(vec![&repost1])); builder.aeq(1, Expect::Single(&single1)); let single2 = builder.insert_note(); builder.aeq(0, Expect::Single(&single2)); builder.aeq(1, Expect::Repost(vec![&repost1])); builder.aeq(2, Expect::Single(&single1)); let reac1 = builder.insert_reac_frag(repost_note); builder.aeq(0, Expect::Reaction(vec![&reac1])); builder.aeq(1, Expect::Single(&single2)); builder.aeq(2, Expect::Repost(vec![&repost1])); builder.aeq(3, Expect::Single(&single1)); let repost2 = builder.insert_repost(repost_note); builder.aeq(0, Expect::Repost(vec![&repost1, &repost2])); builder.aeq(1, Expect::Reaction(vec![&reac1])); builder.aeq(2, Expect::Single(&single2)); builder.aeq(3, Expect::Single(&single1)); let reac2 = builder.insert_reac_frag(repost_note); builder.aeq(0, Expect::Reaction(vec![&reac1, &reac2])); builder.aeq(1, Expect::Repost(vec![&repost1, &repost2])); builder.aeq(2, Expect::Single(&single2)); builder.aeq(3, Expect::Single(&single1)); } }