diff --git a/src/feed.rs b/src/feed.rs new file mode 100644 index 00000000..062a0f47 --- /dev/null +++ b/src/feed.rs @@ -0,0 +1,67 @@ +use crate::globals::GLOBALS; +use nostr_types::{Event, EventKind, Id}; +use std::time::{Duration, Instant}; + +pub struct Feed { + feed: Vec, + + // We only recompute the feed at specified intervals + interval_ms: u32, + last_computed: Instant, +} + +impl Feed { + pub fn new() -> Feed { + Feed { + feed: Vec::new(), + interval_ms: 1000, // Every second, until we load from settings + last_computed: Instant::now(), + } + } + + pub fn get(&mut self) -> Vec { + let now = Instant::now(); + if self.last_computed + Duration::from_millis(self.interval_ms as u64) < now { + self.recompute(); + self.last_computed = now; + } + + self.feed.clone() + } + + fn recompute(&mut self) { + let settings = GLOBALS.settings.blocking_read().clone(); + self.interval_ms = settings.feed_recompute_interval_ms; + + let mut events: Vec = GLOBALS + .events + .blocking_read() + .iter() + .map(|(_, e)| e) + .filter(|e| e.kind == EventKind::TextNote) + .filter(|e| !GLOBALS.dismissed.blocking_read().contains(&e.id)) + .filter(|e| { + if settings.view_threaded { + e.replies_to().is_none() + } else { + true + } + }) + .cloned() + .collect(); + + if settings.view_threaded { + events.sort_unstable_by(|a, b| { + let a_last = GLOBALS.last_reply.blocking_read().get(&a.id).cloned(); + let b_last = GLOBALS.last_reply.blocking_read().get(&b.id).cloned(); + let a_time = a_last.unwrap_or(a.created_at); + let b_time = b_last.unwrap_or(b.created_at); + b_time.cmp(&a_time) + }); + } else { + events.sort_unstable_by(|a, b| b.created_at.cmp(&a.created_at)); + } + + self.feed = events.iter().map(|e| e.id).collect(); + } +} diff --git a/src/globals.rs b/src/globals.rs index c87b6841..56a91eb0 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -1,11 +1,12 @@ use crate::comms::BusMessage; use crate::db::{DbEvent, DbPerson, DbPersonRelay, DbRelay}; use crate::error::Error; +use crate::feed::Feed; use crate::relationship::Relationship; use crate::settings::Settings; use crate::signer::Signer; use async_recursion::async_recursion; -use nostr_types::{Event, EventKind, Id, IdHex, PublicKey, PublicKeyHex, Unixtime, Url}; +use nostr_types::{Event, Id, IdHex, PublicKey, PublicKeyHex, Unixtime, Url}; use rusqlite::Connection; use std::collections::HashMap; use std::sync::atomic::AtomicBool; @@ -62,6 +63,9 @@ pub struct Globals { /// Dismissed Events pub dismissed: RwLock>, + + /// Feed + pub feed: Mutex, } lazy_static! { @@ -88,48 +92,12 @@ lazy_static! { settings: RwLock::new(Settings::default()), signer: RwLock::new(Signer::default()), dismissed: RwLock::new(Vec::new()), + feed: Mutex::new(Feed::new()), } }; } impl Globals { - pub fn blocking_get_feed(threaded: bool) -> Vec { - let feed: Vec = GLOBALS - .events - .blocking_read() - .iter() - .map(|(_, e)| e) - .filter(|e| e.kind == EventKind::TextNote) - .filter(|e| !GLOBALS.dismissed.blocking_read().contains(&e.id)) - .filter(|e| { - if threaded { - e.replies_to().is_none() - } else { - true - } - }) - .cloned() - .collect(); - - Self::sort_feed(feed, threaded) - } - - fn sort_feed(mut feed: Vec, threaded: bool) -> Vec { - if threaded { - feed.sort_unstable_by(|a, b| { - let a_last = GLOBALS.last_reply.blocking_read().get(&a.id).cloned(); - let b_last = GLOBALS.last_reply.blocking_read().get(&b.id).cloned(); - let a_time = a_last.unwrap_or(a.created_at); - let b_time = b_last.unwrap_or(b.created_at); - b_time.cmp(&a_time) - }); - } else { - feed.sort_unstable_by(|a, b| b.created_at.cmp(&a.created_at)); - } - - feed.iter().map(|e| e.id).collect() - } - pub async fn store_desired_event(id: Id, url: Option) { let mut desired_events = GLOBALS.desired_events.write().await; desired_events diff --git a/src/main.rs b/src/main.rs index 574eeff3..4796e046 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod comms; mod date_ago; mod db; mod error; +mod feed; mod globals; mod overlord; mod process; diff --git a/src/settings.rs b/src/settings.rs index 7d71fad1..e4c440eb 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -13,6 +13,7 @@ pub const DEFAULT_VIEW_THREADED: bool = true; pub const DEFAULT_NUM_RELAYS_PER_PERSON: u8 = 2; pub const DEFAULT_MAX_RELAYS: u8 = 15; pub const DEFAULT_MAX_FPS: u32 = 60; +pub const DEFAULT_FEED_RECOMPUTE_INTERVAL_MS: u32 = 1000; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Settings { @@ -27,6 +28,7 @@ pub struct Settings { pub public_key: Option, pub encrypted_private_key: Option, pub max_fps: u32, + pub feed_recompute_interval_ms: u32, } impl Default for Settings { @@ -43,6 +45,7 @@ impl Default for Settings { public_key: None, encrypted_private_key: None, max_fps: DEFAULT_MAX_FPS, + feed_recompute_interval_ms: DEFAULT_FEED_RECOMPUTE_INTERVAL_MS, } } } @@ -93,6 +96,12 @@ impl Settings { } } "max_fps" => settings.max_fps = row.1.parse::().unwrap_or(DEFAULT_MAX_FPS), + "feed_recompute_interval_ms" => { + settings.feed_recompute_interval_ms = row + .1 + .parse::() + .unwrap_or(DEFAULT_FEED_RECOMPUTE_INTERVAL_MS) + } _ => {} } } @@ -109,7 +118,7 @@ impl Settings { ('feed_chunk', ?),('overlap', ?),('autofollow', ?),\ ('view_posts_referred_to', ?),('view_posts_referring_to', ?),\ ('view_threaded', ?),('num_relays_per_person', ?), \ - ('max_relays', ?),('max_fps', ?)", + ('max_relays', ?),('max_fps', ?),('feed_recompute_interval_ms', ?)", )?; stmt.execute(( self.feed_chunk, @@ -129,6 +138,7 @@ impl Settings { self.num_relays_per_person, self.max_relays, self.max_fps, + self.feed_recompute_interval_ms, ))?; // Save private key identity diff --git a/src/ui/feed.rs b/src/ui/feed.rs index 91e0c0cc..f758a882 100644 --- a/src/ui/feed.rs +++ b/src/ui/feed.rs @@ -7,7 +7,7 @@ use egui::{Align, Color32, Context, Layout, RichText, ScrollArea, Ui, Vec2}; use nostr_types::{EventKind, Id}; pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Frame, ui: &mut Ui) { - let feed = Globals::blocking_get_feed(true); + let feed = GLOBALS.feed.blocking_lock().get(); //let screen_rect = ctx.input().screen_rect; // Rect diff --git a/src/ui/settings.rs b/src/ui/settings.rs index 503b3aac..60dc6f36 100644 --- a/src/ui/settings.rs +++ b/src/ui/settings.rs @@ -14,9 +14,10 @@ pub(super) fn update( ) { ui.heading("Settings"); + ui.add_space(12.0); ui.separator(); + ui.add_space(12.0); - ui.add_space(24.0); ui.heading("How Many Relays to Query"); ui.horizontal(|ui| { @@ -32,7 +33,10 @@ pub(super) fn update( ui.add(Slider::new(&mut app.settings.max_relays, 1..=30).text("relays")); }); - ui.add_space(24.0); + ui.add_space(12.0); + ui.separator(); + ui.add_space(12.0); + ui.heading("How Many Posts to Load"); ui.horizontal(|ui| { @@ -47,13 +51,28 @@ pub(super) fn update( ui.label(secs_to_string(app.settings.overlap)); }); - ui.add_space(24.0); - ui.heading("Feed Style / Order"); + ui.add_space(12.0); + ui.separator(); + ui.add_space(12.0); + + ui.heading("Feed"); ui.checkbox(&mut app.settings.view_threaded, "Threaded feed") .on_hover_text("If selected, replies are under what they reply to and the newest replied-to thread comes first. Otherwise all posts are independent and in time order."); ui.add_space(24.0); + ui.horizontal(|ui| { + ui.label("Recompute feed every (milliseconds): ") + .on_hover_text( + "The UI redraws frequently. We recompute the feed less frequently to conserve CPU. Takes effect when the feed next recomputes.", + ); + ui.add(Slider::new(&mut app.settings.feed_recompute_interval_ms, 250..=5000).text("milliseconds")); + }); + + ui.add_space(12.0); + ui.separator(); + ui.add_space(12.0); + ui.heading("What Posts to Include"); ui.checkbox( @@ -67,16 +86,10 @@ pub(super) fn update( ui.checkbox(&mut app.settings.view_posts_referring_to, "View posts referring to posts by people you follow (not yet implemented)") .on_hover_text("Not recommended, as anyone can reply to them and you'll certainly encounter spam this way."); - ui.add_space(24.0); - ui.heading("Miscellaneous"); + ui.add_space(12.0); + ui.separator(); + ui.add_space(12.0); - ui.checkbox( - &mut app.settings.autofollow, - "Autofollow everybody (not yet implemented)", - ) - .on_hover_text("Definately not recommended. In fact we may remove this soon."); - - ui.add_space(24.0); ui.heading("User Interface"); ui.horizontal(|ui| { @@ -102,7 +115,7 @@ pub(super) fn update( } }); - ui.add_space(24.0); + ui.add_space(12.0); ui.horizontal(|ui| { ui.label("Maximum FPS: ") .on_hover_text( @@ -111,7 +124,10 @@ pub(super) fn update( ui.add(Slider::new(&mut app.settings.max_fps, 10..=60).text("Frames per second")); }); - ui.add_space(32.0); + ui.add_space(12.0); + ui.separator(); + ui.add_space(24.0); + ui.with_layout(Layout::top_down(Align::Center), |ui| { if ui.button("SAVE CHANGES").clicked() { let tx = GLOBALS.to_overlord.clone();