From 0344ea866f0ffccbfff1b870667a8a524a019540 Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 19 Oct 2025 19:45:49 -0400 Subject: [PATCH 1/2] fix(notif-indicator): more performant impl the previous unseen notification indicator only ran once a few seconds, but when it did it often took > 5ms because of ndb::query, which is unacceptable. This commit removes the ndb::query entirely and relies on the ndb::poll_for_notes which is already being used every time there is a new event from a relay Signed-off-by: kernelkind --- crates/notedeck_columns/src/app.rs | 6 +-- crates/notedeck_columns/src/timeline/cache.rs | 2 +- crates/notedeck_columns/src/timeline/mod.rs | 4 +- crates/notedeck_columns/src/toolbar.rs | 47 ++++++------------- 4 files changed, 19 insertions(+), 40 deletions(-) diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs index 73240f1..a128053 100644 --- a/crates/notedeck_columns/src/app.rs +++ b/crates/notedeck_columns/src/app.rs @@ -698,11 +698,7 @@ fn render_damus_mobile( break 'brk; } - let unseen_notif = unseen_notification( - app, - app_ctx.ndb, - app_ctx.accounts.get_selected_account().key.pubkey, - ); + let unseen_notif = unseen_notification(app, app_ctx.accounts, active_col); if skb_rect.is_none() { let resp = toolbar(ui, unseen_notif); diff --git a/crates/notedeck_columns/src/timeline/cache.rs b/crates/notedeck_columns/src/timeline/cache.rs index 423c389..36c5816 100644 --- a/crates/notedeck_columns/src/timeline/cache.rs +++ b/crates/notedeck_columns/src/timeline/cache.rs @@ -258,7 +258,7 @@ impl TimelineCache { return; }; - tl.current_view_mut().freshness.set_fresh(); + tl.seen_latest_notes = true; } } diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs index ad24a44..2fb476b 100644 --- a/crates/notedeck_columns/src/timeline/mod.rs +++ b/crates/notedeck_columns/src/timeline/mod.rs @@ -243,6 +243,7 @@ pub struct Timeline { pub filter: FilterStates, pub views: Vec, pub selected_view: usize, + pub seen_latest_notes: bool, pub subscription: TimelineSub, pub enable_front_insert: bool, @@ -317,6 +318,7 @@ impl Timeline { subscription, selected_view, enable_front_insert, + seen_latest_notes: false, } } @@ -489,7 +491,7 @@ impl Timeline { if new_note_ids.is_empty() { return Ok(()); } else { - debug!("{} new notes! {:?}", new_note_ids.len(), new_note_ids); + self.seen_latest_notes = false; } self.insert(&new_note_ids, ndb, txn, unknown_ids, note_cache, reversed) diff --git a/crates/notedeck_columns/src/toolbar.rs b/crates/notedeck_columns/src/toolbar.rs index 86d5c93..049c5d7 100644 --- a/crates/notedeck_columns/src/toolbar.rs +++ b/crates/notedeck_columns/src/toolbar.rs @@ -1,4 +1,3 @@ -use nostrdb::Transaction; use notedeck::AppContext; use crate::{ @@ -10,42 +9,24 @@ use crate::{ #[profiling::function] pub fn unseen_notification( columns: &mut Damus, - ndb: &nostrdb::Ndb, - current_pk: notedeck::enostr::Pubkey, + accounts: ¬edeck::Accounts, + active_col: usize, ) -> bool { - let Some(tl) = columns - .timeline_cache - .get_mut(&TimelineKind::Notifications(current_pk)) - else { + let top = columns.columns(accounts).column(active_col).router().top(); + let current_pk = accounts.get_selected_account().keypair().pubkey; + + if let Route::Timeline(TimelineKind::Notifications(notif_pk)) = top { + if notif_pk == current_pk { + return false; + } + } + + let notif_kind = TimelineKind::Notifications(*current_pk); + let Some(tl) = columns.timeline_cache.get_mut(¬if_kind) else { return false; }; - let freshness = &mut tl.current_view_mut().freshness; - freshness.update(|timestamp_last_viewed| { - profiling::scope!("NotesFreshness::update closure"); - let filter = { - profiling::scope!("NotesFreshness::update filter instantiation"); - enostr::Filter::new_with_capacity(1) - .pubkeys([current_pk.bytes()]) - .kinds(crate::timeline::kind::notification_kinds()) - .limit(1) - .since(timestamp_last_viewed) - .build() - }; - let txn = Transaction::new(ndb).expect("txn"); - - let Some(res) = { - profiling::scope!("NoteFreshness::update Ndb::query"); - ndb.query(&txn, &[filter], 1) - } - .ok() else { - return false; - }; - - !res.is_empty() - }); - - freshness.has_unseen() + !tl.seen_latest_notes } /// When you click the toolbar button, these actions From fc248ac389afd0699f9dc7793930a00d92408abe Mon Sep 17 00:00:00 2001 From: kernelkind Date: Sun, 19 Oct 2025 19:49:25 -0400 Subject: [PATCH 2/2] refactor(NoteFreshness): remove now unnecessary `NoteFreshness` Signed-off-by: kernelkind --- crates/notedeck_columns/src/timeline/mod.rs | 109 +------------------- 1 file changed, 2 insertions(+), 107 deletions(-) diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs index 2fb476b..ca7e72c 100644 --- a/crates/notedeck_columns/src/timeline/mod.rs +++ b/crates/notedeck_columns/src/timeline/mod.rs @@ -8,7 +8,6 @@ use crate::{ use notedeck::{ contacts::hybrid_contacts_filter, - debouncer::Debouncer, filter::{self, HybridFilter}, tr, Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization, NoteCache, NoteRef, UnknownIds, @@ -17,12 +16,8 @@ use notedeck::{ use egui_virtual_list::VirtualList; use enostr::{PoolRelay, Pubkey, RelayPool}; use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction}; -use std::{ - cell::RefCell, - collections::HashSet, - time::{Duration, UNIX_EPOCH}, -}; -use std::{rc::Rc, time::SystemTime}; +use std::rc::Rc; +use std::{cell::RefCell, collections::HashSet}; use tracing::{debug, error, info, warn}; @@ -111,7 +106,6 @@ pub struct TimelineTab { pub selection: i32, pub filter: ViewFilter, pub list: Rc>, - pub freshness: NotesFreshness, } impl TimelineTab { @@ -153,7 +147,6 @@ impl TimelineTab { selection, filter, list, - freshness: NotesFreshness::default(), } } @@ -882,101 +875,3 @@ pub fn is_timeline_ready( } } } - -#[derive(Debug)] -pub struct NotesFreshness { - debouncer: Debouncer, - state: NotesFreshnessState, -} - -#[derive(Debug)] -enum NotesFreshnessState { - Fresh { - timestamp_viewed: u64, - }, - Stale { - have_unseen: bool, - timestamp_last_viewed: u64, - }, -} - -impl Default for NotesFreshness { - fn default() -> Self { - Self { - debouncer: Debouncer::new(Duration::from_secs(2)), - state: NotesFreshnessState::Stale { - have_unseen: true, - timestamp_last_viewed: 0, - }, - } - } -} - -impl NotesFreshness { - pub fn set_fresh(&mut self) { - if !self.debouncer.should_act() { - return; - } - self.state = NotesFreshnessState::Fresh { - timestamp_viewed: timestamp_now(), - }; - self.debouncer.bounce(); - } - - pub fn update(&mut self, check_have_unseen: impl FnOnce(u64) -> bool) { - if !self.debouncer.should_act() { - return; - } - - match &self.state { - NotesFreshnessState::Fresh { timestamp_viewed } => { - let Ok(dur) = SystemTime::now() - .duration_since(UNIX_EPOCH + Duration::from_secs(*timestamp_viewed)) - else { - return; - }; - - if dur > Duration::from_secs(2) { - self.state = NotesFreshnessState::Stale { - have_unseen: check_have_unseen(*timestamp_viewed), - timestamp_last_viewed: *timestamp_viewed, - }; - } - } - NotesFreshnessState::Stale { - have_unseen, - timestamp_last_viewed, - } => { - if *have_unseen { - return; - } - - self.state = NotesFreshnessState::Stale { - have_unseen: check_have_unseen(*timestamp_last_viewed), - timestamp_last_viewed: *timestamp_last_viewed, - }; - } - } - - self.debouncer.bounce(); - } - - pub fn has_unseen(&self) -> bool { - match &self.state { - NotesFreshnessState::Fresh { - timestamp_viewed: _, - } => false, - NotesFreshnessState::Stale { - have_unseen, - timestamp_last_viewed: _, - } => *have_unseen, - } - } -} - -fn timestamp_now() -> u64 { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or(Duration::ZERO) - .as_secs() -}