Feed object, only recomputes periodically; comes with a new setting

This commit is contained in:
Mike Dilger 2022-12-29 16:21:29 +13:00
parent 669bcde3f4
commit 958989b01e
6 changed files with 117 additions and 55 deletions

67
src/feed.rs Normal file
View File

@ -0,0 +1,67 @@
use crate::globals::GLOBALS;
use nostr_types::{Event, EventKind, Id};
use std::time::{Duration, Instant};
pub struct Feed {
feed: Vec<Id>,
// 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<Id> {
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<Event> = 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();
}
}

View File

@ -1,11 +1,12 @@
use crate::comms::BusMessage; use crate::comms::BusMessage;
use crate::db::{DbEvent, DbPerson, DbPersonRelay, DbRelay}; use crate::db::{DbEvent, DbPerson, DbPersonRelay, DbRelay};
use crate::error::Error; use crate::error::Error;
use crate::feed::Feed;
use crate::relationship::Relationship; use crate::relationship::Relationship;
use crate::settings::Settings; use crate::settings::Settings;
use crate::signer::Signer; use crate::signer::Signer;
use async_recursion::async_recursion; 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 rusqlite::Connection;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@ -62,6 +63,9 @@ pub struct Globals {
/// Dismissed Events /// Dismissed Events
pub dismissed: RwLock<Vec<Id>>, pub dismissed: RwLock<Vec<Id>>,
/// Feed
pub feed: Mutex<Feed>,
} }
lazy_static! { lazy_static! {
@ -88,48 +92,12 @@ lazy_static! {
settings: RwLock::new(Settings::default()), settings: RwLock::new(Settings::default()),
signer: RwLock::new(Signer::default()), signer: RwLock::new(Signer::default()),
dismissed: RwLock::new(Vec::new()), dismissed: RwLock::new(Vec::new()),
feed: Mutex::new(Feed::new()),
} }
}; };
} }
impl Globals { impl Globals {
pub fn blocking_get_feed(threaded: bool) -> Vec<Id> {
let feed: Vec<Event> = 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<Event>, threaded: bool) -> Vec<Id> {
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<Url>) { pub async fn store_desired_event(id: Id, url: Option<Url>) {
let mut desired_events = GLOBALS.desired_events.write().await; let mut desired_events = GLOBALS.desired_events.write().await;
desired_events desired_events

View File

@ -8,6 +8,7 @@ mod comms;
mod date_ago; mod date_ago;
mod db; mod db;
mod error; mod error;
mod feed;
mod globals; mod globals;
mod overlord; mod overlord;
mod process; mod process;

View File

@ -13,6 +13,7 @@ pub const DEFAULT_VIEW_THREADED: bool = true;
pub const DEFAULT_NUM_RELAYS_PER_PERSON: u8 = 2; pub const DEFAULT_NUM_RELAYS_PER_PERSON: u8 = 2;
pub const DEFAULT_MAX_RELAYS: u8 = 15; pub const DEFAULT_MAX_RELAYS: u8 = 15;
pub const DEFAULT_MAX_FPS: u32 = 60; pub const DEFAULT_MAX_FPS: u32 = 60;
pub const DEFAULT_FEED_RECOMPUTE_INTERVAL_MS: u32 = 1000;
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Settings { pub struct Settings {
@ -27,6 +28,7 @@ pub struct Settings {
pub public_key: Option<PublicKey>, pub public_key: Option<PublicKey>,
pub encrypted_private_key: Option<EncryptedPrivateKey>, pub encrypted_private_key: Option<EncryptedPrivateKey>,
pub max_fps: u32, pub max_fps: u32,
pub feed_recompute_interval_ms: u32,
} }
impl Default for Settings { impl Default for Settings {
@ -43,6 +45,7 @@ impl Default for Settings {
public_key: None, public_key: None,
encrypted_private_key: None, encrypted_private_key: None,
max_fps: DEFAULT_MAX_FPS, 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::<u32>().unwrap_or(DEFAULT_MAX_FPS), "max_fps" => settings.max_fps = row.1.parse::<u32>().unwrap_or(DEFAULT_MAX_FPS),
"feed_recompute_interval_ms" => {
settings.feed_recompute_interval_ms = row
.1
.parse::<u32>()
.unwrap_or(DEFAULT_FEED_RECOMPUTE_INTERVAL_MS)
}
_ => {} _ => {}
} }
} }
@ -109,7 +118,7 @@ impl Settings {
('feed_chunk', ?),('overlap', ?),('autofollow', ?),\ ('feed_chunk', ?),('overlap', ?),('autofollow', ?),\
('view_posts_referred_to', ?),('view_posts_referring_to', ?),\ ('view_posts_referred_to', ?),('view_posts_referring_to', ?),\
('view_threaded', ?),('num_relays_per_person', ?), \ ('view_threaded', ?),('num_relays_per_person', ?), \
('max_relays', ?),('max_fps', ?)", ('max_relays', ?),('max_fps', ?),('feed_recompute_interval_ms', ?)",
)?; )?;
stmt.execute(( stmt.execute((
self.feed_chunk, self.feed_chunk,
@ -129,6 +138,7 @@ impl Settings {
self.num_relays_per_person, self.num_relays_per_person,
self.max_relays, self.max_relays,
self.max_fps, self.max_fps,
self.feed_recompute_interval_ms,
))?; ))?;
// Save private key identity // Save private key identity

View File

@ -7,7 +7,7 @@ use egui::{Align, Color32, Context, Layout, RichText, ScrollArea, Ui, Vec2};
use nostr_types::{EventKind, Id}; use nostr_types::{EventKind, Id};
pub(super) fn update(app: &mut GossipUi, ctx: &Context, frame: &mut eframe::Frame, ui: &mut Ui) { 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 //let screen_rect = ctx.input().screen_rect; // Rect

View File

@ -14,9 +14,10 @@ pub(super) fn update(
) { ) {
ui.heading("Settings"); ui.heading("Settings");
ui.add_space(12.0);
ui.separator(); ui.separator();
ui.add_space(12.0);
ui.add_space(24.0);
ui.heading("How Many Relays to Query"); ui.heading("How Many Relays to Query");
ui.horizontal(|ui| { 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(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.heading("How Many Posts to Load");
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -47,13 +51,28 @@ pub(super) fn update(
ui.label(secs_to_string(app.settings.overlap)); ui.label(secs_to_string(app.settings.overlap));
}); });
ui.add_space(24.0); ui.add_space(12.0);
ui.heading("Feed Style / Order"); ui.separator();
ui.add_space(12.0);
ui.heading("Feed");
ui.checkbox(&mut app.settings.view_threaded, "Threaded 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."); .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.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.heading("What Posts to Include");
ui.checkbox( 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)") 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."); .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.add_space(12.0);
ui.heading("Miscellaneous"); 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.heading("User Interface");
ui.horizontal(|ui| { 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.horizontal(|ui| {
ui.label("Maximum FPS: ") ui.label("Maximum FPS: ")
.on_hover_text( .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(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| { ui.with_layout(Layout::top_down(Align::Center), |ui| {
if ui.button("SAVE CHANGES").clicked() { if ui.button("SAVE CHANGES").clicked() {
let tx = GLOBALS.to_overlord.clone(); let tx = GLOBALS.to_overlord.clone();