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;
}
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);

View File

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

View File

@@ -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<RefCell<VirtualList>>,
pub freshness: NotesFreshness,
}
impl TimelineTab {
@@ -153,7 +147,6 @@ impl TimelineTab {
selection,
filter,
list,
freshness: NotesFreshness::default(),
}
}
@@ -243,6 +236,7 @@ pub struct Timeline {
pub filter: FilterStates,
pub views: Vec<TimelineTab>,
pub selected_view: usize,
pub seen_latest_notes: bool,
pub subscription: TimelineSub,
pub enable_front_insert: bool,
@@ -317,6 +311,7 @@ impl Timeline {
subscription,
selected_view,
enable_front_insert,
seen_latest_notes: false,
}
}
@@ -489,7 +484,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)
@@ -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 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: &notedeck::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 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 {
}
let notif_kind = TimelineKind::Notifications(*current_pk);
let Some(tl) = columns.timeline_cache.get_mut(&notif_kind) else {
return false;
};
!res.is_empty()
});
freshness.has_unseen()
!tl.seen_latest_notes
}
/// When you click the toolbar button, these actions