Merge seen note perf improvements by kernel #1174

kernelkind (2):
      fix(notif-indicator): more performant impl
      refactor(NoteFreshness): remove now unnecessary `NoteFreshness`
This commit is contained in:
William Casarin
2025-10-20 11:20:54 -07:00
4 changed files with 21 additions and 147 deletions

View File

@@ -698,11 +698,7 @@ fn render_damus_mobile(
break 'brk; break 'brk;
} }
let unseen_notif = unseen_notification( let unseen_notif = unseen_notification(app, app_ctx.accounts, active_col);
app,
app_ctx.ndb,
app_ctx.accounts.get_selected_account().key.pubkey,
);
if skb_rect.is_none() { if skb_rect.is_none() {
let resp = toolbar(ui, unseen_notif); let resp = toolbar(ui, unseen_notif);

View File

@@ -258,7 +258,7 @@ impl TimelineCache {
return; return;
}; };
tl.current_view_mut().freshness.set_fresh(); tl.seen_latest_notes = true;
} }
} }

View File

@@ -8,7 +8,6 @@ use crate::{
use notedeck::{ use notedeck::{
contacts::hybrid_contacts_filter, contacts::hybrid_contacts_filter,
debouncer::Debouncer,
filter::{self, HybridFilter}, filter::{self, HybridFilter},
tr, Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization, tr, Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization,
NoteCache, NoteRef, UnknownIds, NoteCache, NoteRef, UnknownIds,
@@ -17,12 +16,8 @@ use notedeck::{
use egui_virtual_list::VirtualList; use egui_virtual_list::VirtualList;
use enostr::{PoolRelay, Pubkey, RelayPool}; use enostr::{PoolRelay, Pubkey, RelayPool};
use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction}; use nostrdb::{Filter, Ndb, Note, NoteKey, Transaction};
use std::{ use std::rc::Rc;
cell::RefCell, use std::{cell::RefCell, collections::HashSet};
collections::HashSet,
time::{Duration, UNIX_EPOCH},
};
use std::{rc::Rc, time::SystemTime};
use tracing::{debug, error, info, warn}; use tracing::{debug, error, info, warn};
@@ -111,7 +106,6 @@ pub struct TimelineTab {
pub selection: i32, pub selection: i32,
pub filter: ViewFilter, pub filter: ViewFilter,
pub list: Rc<RefCell<VirtualList>>, pub list: Rc<RefCell<VirtualList>>,
pub freshness: NotesFreshness,
} }
impl TimelineTab { impl TimelineTab {
@@ -153,7 +147,6 @@ impl TimelineTab {
selection, selection,
filter, filter,
list, list,
freshness: NotesFreshness::default(),
} }
} }
@@ -243,6 +236,7 @@ pub struct Timeline {
pub filter: FilterStates, pub filter: FilterStates,
pub views: Vec<TimelineTab>, pub views: Vec<TimelineTab>,
pub selected_view: usize, pub selected_view: usize,
pub seen_latest_notes: bool,
pub subscription: TimelineSub, pub subscription: TimelineSub,
pub enable_front_insert: bool, pub enable_front_insert: bool,
@@ -317,6 +311,7 @@ impl Timeline {
subscription, subscription,
selected_view, selected_view,
enable_front_insert, enable_front_insert,
seen_latest_notes: false,
} }
} }
@@ -489,7 +484,7 @@ impl Timeline {
if new_note_ids.is_empty() { if new_note_ids.is_empty() {
return Ok(()); return Ok(());
} else { } 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) self.insert(&new_note_ids, ndb, txn, unknown_ids, note_cache, reversed)
@@ -880,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()
}

View File

@@ -1,4 +1,3 @@
use nostrdb::Transaction;
use notedeck::AppContext; use notedeck::AppContext;
use crate::{ use crate::{
@@ -10,42 +9,24 @@ use crate::{
#[profiling::function] #[profiling::function]
pub fn unseen_notification( pub fn unseen_notification(
columns: &mut Damus, columns: &mut Damus,
ndb: &nostrdb::Ndb, accounts: &notedeck::Accounts,
current_pk: notedeck::enostr::Pubkey, active_col: usize,
) -> bool { ) -> bool {
let Some(tl) = columns let top = columns.columns(accounts).column(active_col).router().top();
.timeline_cache let current_pk = accounts.get_selected_account().keypair().pubkey;
.get_mut(&TimelineKind::Notifications(current_pk))
else { 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(&notif_kind) else {
return false; return false;
}; };
let freshness = &mut tl.current_view_mut().freshness; !tl.seen_latest_notes
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()
} }
/// When you click the toolbar button, these actions /// When you click the toolbar button, these actions