commit 7f01f3623dadda9f342b56e2afc47be6afed1b33
parent faec75e1b6b042ed30eee8a17318ab115830301e
Author: kernelkind <kernelkind@gmail.com>
Date: Tue, 29 Apr 2025 10:48:07 -0400
add `TexturesCache`
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
4 files changed, 188 insertions(+), 9 deletions(-)
diff --git a/crates/notedeck/src/imgcache.rs b/crates/notedeck/src/imgcache.rs
@@ -13,18 +13,183 @@ use std::time::{Duration, Instant, SystemTime};
use hex::ToHex;
use sha2::Digest;
-use std::path;
use std::path::PathBuf;
+use std::path::{self};
use tracing::warn;
pub type MediaCacheValue = Promise<Result<TexturedImage>>;
pub type MediaCacheMap = HashMap<String, MediaCacheValue>;
+#[derive(Default)]
+pub struct TexturesCache {
+ cache: hashbrown::HashMap<String, TextureStateInternal>,
+}
+
+impl TexturesCache {
+ pub fn handle_and_get_or_insert_loadable(
+ &mut self,
+ url: &str,
+ closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>,
+ ) -> LoadableTextureState {
+ let internal = self.handle_and_get_state_internal(url, true, closure);
+
+ internal.into()
+ }
+
+ pub fn handle_and_get_or_insert(
+ &mut self,
+ url: &str,
+
+ closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>,
+ ) -> TextureState {
+ let internal = self.handle_and_get_state_internal(url, false, closure);
+
+ internal.into()
+ }
+
+ fn handle_and_get_state_internal(
+ &mut self,
+ url: &str,
+ use_loading: bool,
+ closure: impl FnOnce() -> Promise<Option<Result<TexturedImage>>>,
+ ) -> &mut TextureStateInternal {
+ let state = match self.cache.raw_entry_mut().from_key(url) {
+ hashbrown::hash_map::RawEntryMut::Occupied(entry) => {
+ let state = entry.into_mut();
+ handle_occupied(state, use_loading);
+
+ state
+ }
+ hashbrown::hash_map::RawEntryMut::Vacant(entry) => {
+ let res = closure();
+ let (_, state) = entry.insert(url.to_owned(), TextureStateInternal::Pending(res));
+
+ state
+ }
+ };
+
+ state
+ }
+
+ pub fn insert_pending(&mut self, url: &str, promise: Promise<Option<Result<TexturedImage>>>) {
+ self.cache
+ .insert(url.to_owned(), TextureStateInternal::Pending(promise));
+ }
+
+ pub fn move_to_loaded(&mut self, url: &str) {
+ let hashbrown::hash_map::RawEntryMut::Occupied(entry) =
+ self.cache.raw_entry_mut().from_key(url)
+ else {
+ return;
+ };
+
+ entry.replace_entry_with(|_, v| {
+ let TextureStateInternal::Loading(textured) = v else {
+ return None;
+ };
+
+ Some(TextureStateInternal::Loaded(textured))
+ });
+ }
+
+ pub fn get_and_handle(&mut self, url: &str) -> Option<LoadableTextureState> {
+ self.cache.get_mut(url).map(|state| {
+ handle_occupied(state, true);
+ state.into()
+ })
+ }
+}
+
+fn handle_occupied(state: &mut TextureStateInternal, use_loading: bool) {
+ let TextureStateInternal::Pending(promise) = state else {
+ return;
+ };
+
+ let Some(res) = promise.ready_mut() else {
+ return;
+ };
+
+ let Some(res) = res.take() else {
+ tracing::error!("Failed to take the promise");
+ *state =
+ TextureStateInternal::Error(crate::Error::Generic("Promise already taken".to_owned()));
+ return;
+ };
+
+ match res {
+ Ok(textured) => {
+ *state = if use_loading {
+ TextureStateInternal::Loading(textured)
+ } else {
+ TextureStateInternal::Loaded(textured)
+ }
+ }
+ Err(e) => *state = TextureStateInternal::Error(e),
+ }
+}
+
+pub enum LoadableTextureState<'a> {
+ Pending,
+ Error(&'a crate::Error),
+ Loading {
+ actual_image_tex: &'a mut TexturedImage,
+ }, // the texture is in the loading state, for transitioning between the pending and loaded states
+ Loaded(&'a mut TexturedImage),
+}
+
+pub enum TextureState<'a> {
+ Pending,
+ Error(&'a crate::Error),
+ Loaded(&'a mut TexturedImage),
+}
+
+impl<'a> From<&'a mut TextureStateInternal> for TextureState<'a> {
+ fn from(value: &'a mut TextureStateInternal) -> Self {
+ match value {
+ TextureStateInternal::Pending(_) => TextureState::Pending,
+ TextureStateInternal::Error(error) => TextureState::Error(error),
+ TextureStateInternal::Loading(textured_image) => TextureState::Loaded(textured_image),
+ TextureStateInternal::Loaded(textured_image) => TextureState::Loaded(textured_image),
+ }
+ }
+}
+
+pub enum TextureStateInternal {
+ Pending(Promise<Option<Result<TexturedImage>>>),
+ Error(crate::Error),
+ Loading(TexturedImage), // the image is in the loading state, for transitioning between blur and image
+ Loaded(TexturedImage),
+}
+
+impl<'a> From<&'a mut TextureStateInternal> for LoadableTextureState<'a> {
+ fn from(value: &'a mut TextureStateInternal) -> Self {
+ match value {
+ TextureStateInternal::Pending(_) => LoadableTextureState::Pending,
+ TextureStateInternal::Error(error) => LoadableTextureState::Error(error),
+ TextureStateInternal::Loading(textured_image) => LoadableTextureState::Loading {
+ actual_image_tex: textured_image,
+ },
+ TextureStateInternal::Loaded(textured_image) => {
+ LoadableTextureState::Loaded(textured_image)
+ }
+ }
+ }
+}
+
pub enum TexturedImage {
Static(TextureHandle),
Animated(Animation),
}
+impl TexturedImage {
+ pub fn get_first_texture(&self) -> &TextureHandle {
+ match self {
+ TexturedImage::Static(texture_handle) => texture_handle,
+ TexturedImage::Animated(animation) => &animation.first_frame.texture,
+ }
+ }
+}
+
pub struct Animation {
pub first_frame: TextureFrame,
pub other_frames: Vec<TextureFrame>,
@@ -60,7 +225,7 @@ pub struct MediaCache {
url_imgs: MediaCacheMap,
}
-#[derive(Clone)]
+#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum MediaCacheType {
Image,
Gif,
@@ -215,6 +380,20 @@ impl Images {
self.static_imgs.migrate_v0()?;
self.gifs.migrate_v0()
}
+
+ pub fn get_cache(&self, cache_type: MediaCacheType) -> &MediaCache {
+ match cache_type {
+ MediaCacheType::Image => &self.static_imgs,
+ MediaCacheType::Gif => &self.gifs,
+ }
+ }
+
+ pub fn get_cache_mut(&mut self, cache_type: MediaCacheType) -> &mut MediaCache {
+ match cache_type {
+ MediaCacheType::Image => &mut self.static_imgs,
+ MediaCacheType::Gif => &mut self.gifs,
+ }
+ }
}
pub type GifStateMap = HashMap<String, GifState>;
diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs
@@ -41,8 +41,8 @@ pub use error::{Error, FilterError, ZapError};
pub use filter::{FilterState, FilterStates, UnifiedSubscription};
pub use fonts::NamedFontFamily;
pub use imgcache::{
- Animation, GifState, GifStateMap, ImageFrame, Images, MediaCache, MediaCacheType,
- MediaCacheValue, TextureFrame, TexturedImage,
+ Animation, GifState, GifStateMap, ImageFrame, Images, LoadableTextureState, MediaCache,
+ MediaCacheType, TextureFrame, TextureState, TexturedImage, TexturesCache,
};
pub use job_pool::JobPool;
pub use muted::{MuteFun, Muted};
diff --git a/crates/notedeck_ui/src/images.rs b/crates/notedeck_ui/src/images.rs
@@ -468,7 +468,7 @@ fn render_media_cache(
let m_cached_promise = cache.map().get(url);
if m_cached_promise.is_none() {
- let res = crate::images::fetch_img(cache, ui.ctx(), url, img_type, cache_type.clone());
+ let res = crate::images::fetch_img(cache, ui.ctx(), url, img_type, cache_type);
cache.map_mut().insert(url.to_owned(), res);
}
diff --git a/crates/notedeck_ui/src/note/contents.rs b/crates/notedeck_ui/src/note/contents.rs
@@ -318,7 +318,7 @@ fn image_carousel(
ui.ctx().memory(|mem| {
mem.data
.get_temp::<(String, MediaCacheType)>(carousel_id.with("current_image"))
- .unwrap_or_else(|| (images[0].0.clone(), images[0].1.clone()))
+ .unwrap_or_else(|| (images[0].0.clone(), images[0].1))
})
});
@@ -333,7 +333,7 @@ fn image_carousel(
img_cache,
&image,
ImageType::Content,
- cache_type.clone(),
+ cache_type,
|ui| {
ui.allocate_space(egui::vec2(spinsz, spinsz));
},
@@ -360,7 +360,7 @@ fn image_carousel(
mem.data.insert_temp(carousel_id.with("show_popup"), true);
mem.data.insert_temp(
carousel_id.with("current_image"),
- (image.clone(), cache_type.clone()),
+ (image.clone(), cache_type),
);
});
}
@@ -438,7 +438,7 @@ fn image_carousel(
img_cache,
&image,
ImageType::Content,
- cache_type.clone(),
+ cache_type,
|ui| {
ui.allocate_space(egui::vec2(spinsz, spinsz));
},