notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit badf3070c8b5008ba3ad9f20ae9bafee916393ca
parent 5cdf3698d2f64c75332894307ce8f93d57bcbfaa
Author: kernelkind <kernelkind@gmail.com>
Date:   Sat,  8 Mar 2025 20:45:00 -0500

introduce JobsCache

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_columns/src/app.rs | 7++++++-
Acrates/notedeck_ui/src/jobs.rs | 153+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/notedeck_ui/src/lib.rs | 1+
3 files changed, 160 insertions(+), 1 deletion(-)

diff --git a/crates/notedeck_columns/src/app.rs b/crates/notedeck_columns/src/app.rs @@ -13,7 +13,7 @@ use crate::{ }; use notedeck::{Accounts, AppAction, AppContext, DataPath, DataPathType, FilterState, UnknownIds}; -use notedeck_ui::NoteOptions; +use notedeck_ui::{jobs::JobsCache, NoteOptions}; use enostr::{ClientMessage, Keypair, PoolRelay, Pubkey, RelayEvent, RelayMessage, RelayPool}; use uuid::Uuid; @@ -42,6 +42,7 @@ pub struct Damus { pub timeline_cache: TimelineCache, pub subscriptions: Subscriptions, pub support: Support, + pub jobs: JobsCache, //frame_history: crate::frame_history::FrameHistory, @@ -430,6 +431,8 @@ impl Damus { note_options.set_scramble_text(parsed_args.scramble); note_options.set_hide_media(parsed_args.no_media); + let jobs = JobsCache::default(); + Self { subscriptions: Subscriptions::default(), since_optimize: parsed_args.since_optimize, @@ -444,6 +447,7 @@ impl Damus { decks_cache, debug, unrecognized_args, + jobs, } } @@ -487,6 +491,7 @@ impl Damus { support, decks_cache, unrecognized_args: BTreeSet::default(), + jobs: JobsCache::default(), } } diff --git a/crates/notedeck_ui/src/jobs.rs b/crates/notedeck_ui/src/jobs.rs @@ -0,0 +1,153 @@ +use egui::TextureHandle; +use hashbrown::{hash_map::RawEntryMut, HashMap}; +use notedeck::JobPool; +use poll_promise::Promise; + +#[derive(Default)] +pub struct JobsCache { + jobs: HashMap<JobIdOwned, JobState>, +} + +pub enum JobState { + Pending(Promise<Option<Result<Job, JobError>>>), + Error(JobError), + Completed(Job), +} + +pub enum JobError { + InvalidParameters, +} + +#[derive(Debug)] +pub enum JobParams<'a> { + Blurhash(BlurhashParams<'a>), +} + +#[derive(Debug)] +pub enum JobParamsOwned { + Blurhash(BlurhashParamsOwned), +} + +impl<'a> From<BlurhashParams<'a>> for BlurhashParamsOwned { + fn from(params: BlurhashParams<'a>) -> Self { + BlurhashParamsOwned { + blurhash: params.blurhash.to_owned(), + url: params.url.to_owned(), + ctx: params.ctx.clone(), + } + } +} + +impl<'a> From<JobParams<'a>> for JobParamsOwned { + fn from(params: JobParams<'a>) -> Self { + match params { + JobParams::Blurhash(bp) => JobParamsOwned::Blurhash(bp.into()), + } + } +} + +#[derive(Debug)] +pub struct BlurhashParams<'a> { + pub blurhash: &'a str, + pub url: &'a str, + pub ctx: &'a egui::Context, +} + +#[derive(Debug)] +pub struct BlurhashParamsOwned { + pub blurhash: String, + pub url: String, + pub ctx: egui::Context, +} + +impl JobsCache { + pub fn get_or_insert_with< + 'a, + F: FnOnce(Option<JobParamsOwned>) -> Result<Job, JobError> + Send + 'static, + >( + &'a mut self, + job_pool: &mut JobPool, + jobid: &JobId, + params: Option<JobParams>, + run_job: F, + ) -> &'a mut JobState { + match self.jobs.raw_entry_mut().from_key(jobid) { + RawEntryMut::Occupied(entry) => 's: { + let mut state = entry.into_mut(); + + let JobState::Pending(promise) = &mut state else { + break 's state; + }; + + let Some(res) = promise.ready_mut() else { + break 's state; + }; + + let Some(res) = res.take() else { + tracing::error!("Failed to take the promise for job: {:?}", jobid); + break 's state; + }; + + *state = match res { + Ok(j) => JobState::Completed(j), + Err(e) => JobState::Error(e), + }; + + state + } + RawEntryMut::Vacant(entry) => { + let owned_params = params.map(JobParams::into); + let wrapped: Box<dyn FnOnce() -> Option<Result<Job, JobError>> + Send + 'static> = + Box::new(move || Some(run_job(owned_params))); + + let promise = Promise::spawn_async(job_pool.schedule(wrapped)); + + let (_, state) = entry.insert(jobid.into(), JobState::Pending(promise)); + + state + } + } + } + + pub fn get(&self, jobid: &JobId) -> Option<&JobState> { + self.jobs.get(jobid) + } +} + +impl<'a> From<&JobId<'a>> for JobIdOwned { + fn from(jobid: &JobId<'a>) -> Self { + match jobid { + JobId::Blurhash(s) => JobIdOwned::Blurhash(s.to_string()), + } + } +} + +impl hashbrown::Equivalent<JobIdOwned> for JobId<'_> { + fn equivalent(&self, key: &JobIdOwned) -> bool { + match (self, key) { + (JobId::Blurhash(a), JobIdOwned::Blurhash(b)) => *a == b.as_str(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] +enum JobIdOwned { + Blurhash(String), // image URL +} + +#[derive(Debug, Hash)] +pub enum JobId<'a> { + Blurhash(&'a str), // image URL +} + +pub enum Job { + Blurhash(Option<TextureHandle>), +} + +impl std::fmt::Debug for Job { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Job::Blurhash(_) => write!(f, "Blurhash"), + } + } +} diff --git a/crates/notedeck_ui/src/lib.rs b/crates/notedeck_ui/src/lib.rs @@ -5,6 +5,7 @@ pub mod contacts; pub mod gif; pub mod icons; pub mod images; +pub mod jobs; pub mod mention; pub mod note; pub mod profile;