notedeck

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

commit 686dea983125e899ab2e86c5970b9921a30bf9b1
parent 01171ff9d798a271a116ebec06deeeaaf373434c
Author: kernelkind <kernelkind@gmail.com>
Date:   Tue, 19 Aug 2025 16:50:50 -0400

move `HybridSet` to own file

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_columns/src/actionbar.rs | 6++----
Acrates/notedeck_columns/src/timeline/hybrid_set.rs | 99+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_columns/src/timeline/mod.rs | 2++
Mcrates/notedeck_columns/src/timeline/thread.rs | 100+------------------------------------------------------------------------------
4 files changed, 104 insertions(+), 103 deletions(-)

diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs @@ -3,10 +3,8 @@ use crate::{ nav::{RouterAction, RouterType}, route::Route, timeline::{ - thread::{ - selected_has_at_least_n_replies, InsertionResponse, NoteSeenFlags, ThreadNode, Threads, - }, - ThreadSelection, TimelineCache, TimelineKind, + thread::{selected_has_at_least_n_replies, NoteSeenFlags, ThreadNode, Threads}, + InsertionResponse, ThreadSelection, TimelineCache, TimelineKind, }, view_state::ViewState, }; diff --git a/crates/notedeck_columns/src/timeline/hybrid_set.rs b/crates/notedeck_columns/src/timeline/hybrid_set.rs @@ -0,0 +1,99 @@ +use std::{ + collections::{BTreeSet, HashSet}, + hash::Hash, +}; + +use crate::timeline::MergeKind; + +/// Affords: +/// - O(1) contains +/// - O(log n) sorted insertion +pub struct HybridSet<T> { + reversed: bool, + lookup: HashSet<T>, // fast deduplication + ordered: BTreeSet<T>, // sorted iteration +} + +impl<T> Default for HybridSet<T> { + fn default() -> Self { + Self { + reversed: Default::default(), + lookup: Default::default(), + ordered: Default::default(), + } + } +} + +pub enum InsertionResponse { + AlreadyExists, + Merged(MergeKind), +} + +impl<T: Copy + Ord + Eq + Hash> HybridSet<T> { + pub fn insert(&mut self, val: T) -> InsertionResponse { + if !self.lookup.insert(val) { + return InsertionResponse::AlreadyExists; + } + + let front_insertion = match self.ordered.iter().next() { + Some(first) => (val >= *first) == self.reversed, + None => true, + }; + + self.ordered.insert(val); // O(log n) + + InsertionResponse::Merged(if front_insertion { + MergeKind::FrontInsert + } else { + MergeKind::Spliced + }) + } +} + +impl<T: Eq + Hash> HybridSet<T> { + pub fn contains(&self, val: &T) -> bool { + self.lookup.contains(val) // O(1) + } +} + +impl<T> HybridSet<T> { + pub fn iter(&self) -> HybridIter<'_, T> { + HybridIter { + inner: self.ordered.iter(), + reversed: self.reversed, + } + } + + pub fn new(reversed: bool) -> Self { + Self { + reversed, + ..Default::default() + } + } +} + +impl<'a, T> IntoIterator for &'a HybridSet<T> { + type Item = &'a T; + type IntoIter = HybridIter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +pub struct HybridIter<'a, T> { + inner: std::collections::btree_set::Iter<'a, T>, + reversed: bool, +} + +impl<'a, T> Iterator for HybridIter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<Self::Item> { + if self.reversed { + self.inner.next_back() + } else { + self.inner.next() + } + } +} diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -26,11 +26,13 @@ use std::{rc::Rc, time::SystemTime}; use tracing::{debug, error, info, warn}; pub mod cache; +mod hybrid_set; pub mod kind; pub mod route; pub mod thread; pub use cache::TimelineCache; +pub use hybrid_set::{HybridSet, InsertionResponse}; pub use kind::{ColumnTitle, PubkeySource, ThreadSelection, TimelineKind}; #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] diff --git a/crates/notedeck_columns/src/timeline/thread.rs b/crates/notedeck_columns/src/timeline/thread.rs @@ -1,8 +1,3 @@ -use std::{ - collections::{BTreeSet, HashSet}, - hash::Hash, -}; - use egui_nav::ReturnType; use egui_virtual_list::VirtualList; use enostr::{NoteId, RelayPool}; @@ -13,7 +8,7 @@ use notedeck::{NoteCache, NoteRef, UnknownIds}; use crate::{ actionbar::{process_thread_notes, NewThreadNotes}, multi_subscriber::ThreadSubs, - timeline::MergeKind, + timeline::hybrid_set::HybridSet, }; use super::ThreadSelection; @@ -33,99 +28,6 @@ pub enum ParentState { Parent(NoteId), } -/// Affords: -/// - O(1) contains -/// - O(log n) sorted insertion -pub struct HybridSet<T> { - reversed: bool, - lookup: HashSet<T>, // fast deduplication - ordered: BTreeSet<T>, // sorted iteration -} - -impl<T> Default for HybridSet<T> { - fn default() -> Self { - Self { - reversed: Default::default(), - lookup: Default::default(), - ordered: Default::default(), - } - } -} - -pub enum InsertionResponse { - AlreadyExists, - Merged(MergeKind), -} - -impl<T: Copy + Ord + Eq + Hash> HybridSet<T> { - pub fn insert(&mut self, val: T) -> InsertionResponse { - if !self.lookup.insert(val) { - return InsertionResponse::AlreadyExists; - } - - let front_insertion = match self.ordered.iter().next() { - Some(first) => (val >= *first) == self.reversed, - None => true, - }; - - self.ordered.insert(val); // O(log n) - - InsertionResponse::Merged(if front_insertion { - MergeKind::FrontInsert - } else { - MergeKind::Spliced - }) - } -} - -impl<T: Eq + Hash> HybridSet<T> { - pub fn contains(&self, val: &T) -> bool { - self.lookup.contains(val) // O(1) - } -} - -impl<T> HybridSet<T> { - pub fn iter(&self) -> HybridIter<'_, T> { - HybridIter { - inner: self.ordered.iter(), - reversed: self.reversed, - } - } - - pub fn new(reversed: bool) -> Self { - Self { - reversed, - ..Default::default() - } - } -} - -impl<'a, T> IntoIterator for &'a HybridSet<T> { - type Item = &'a T; - type IntoIter = HybridIter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -pub struct HybridIter<'a, T> { - inner: std::collections::btree_set::Iter<'a, T>, - reversed: bool, -} - -impl<'a, T> Iterator for HybridIter<'a, T> { - type Item = &'a T; - - fn next(&mut self) -> Option<Self::Item> { - if self.reversed { - self.inner.next_back() - } else { - self.inner.next() - } - } -} - impl ThreadNode { pub fn new(parent: ParentState) -> Self { Self {