refactor image cache

This commit is contained in:
Ren Amamiya
2026-04-10 08:50:01 +07:00
parent 09c8245465
commit cd2484a256
6 changed files with 165 additions and 19 deletions

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
pub use actions::*;
use anyhow::{Context as AnyhowContext, Error};
use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
use common::TimestampExt;
use common::{TimestampExt, coop_cache};
use gpui::prelude::FluentBuilder;
use gpui::{
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
@@ -1485,6 +1485,7 @@ impl Focusable for ChatPanel {
impl Render for ChatPanel {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.image_cache(coop_cache(self.id.clone(), 100))
.on_action(cx.listener(Self::on_command))
.size_full()
.when(*self.subject_bar.read(cx), |this| {

View File

@@ -0,0 +1,135 @@
use std::collections::{HashMap, VecDeque};
use std::mem::take;
use futures::FutureExt;
use gpui::{
App, AppContext, Asset, AssetLogger, ElementId, Entity, ImageAssetLoader, ImageCache,
ImageCacheItem, ImageCacheProvider, ImageSource, Resource, hash,
};
pub fn coop_cache(id: impl Into<ElementId>, max_items: usize) -> CoopImageCacheProvider {
CoopImageCacheProvider {
id: id.into(),
max_items,
}
}
pub struct CoopImageCacheProvider {
id: ElementId,
max_items: usize,
}
impl ImageCacheProvider for CoopImageCacheProvider {
fn provide(&mut self, window: &mut gpui::Window, cx: &mut App) -> gpui::AnyImageCache {
window
.with_global_id(self.id.clone(), |id, window| {
window.with_element_state(id, |cache, _| {
let cache = cache.unwrap_or_else(|| CoopImageCache::new(self.max_items, cx));
(cache.clone(), cache)
})
})
.into()
}
}
pub struct CoopImageCache {
max_items: usize,
usage_list: VecDeque<u64>,
cache: HashMap<u64, (ImageCacheItem, Resource)>,
}
impl CoopImageCache {
pub fn new(max_items: usize, cx: &mut App) -> Entity<Self> {
cx.new(|cx| {
log::info!("Creating CoopImageCache");
cx.on_release(|this: &mut Self, cx| {
for (ix, (mut image, resource)) in take(&mut this.cache) {
if let Some(Ok(image)) = image.get() {
log::info!("Dropping image {ix}");
cx.drop_image(image, None);
}
ImageSource::Resource(resource).remove_asset(cx);
}
})
.detach();
CoopImageCache {
max_items,
usage_list: VecDeque::with_capacity(max_items),
cache: HashMap::with_capacity(max_items),
}
})
}
}
impl ImageCache for CoopImageCache {
fn load(
&mut self,
resource: &Resource,
window: &mut gpui::Window,
cx: &mut gpui::App,
) -> Option<Result<std::sync::Arc<gpui::RenderImage>, gpui::ImageCacheError>> {
let hash = hash(resource);
if let Some(item) = self.cache.get_mut(&hash) {
let current_idx = self
.usage_list
.iter()
.position(|item| *item == hash)
.expect("cache has an item usage_list doesn't");
self.usage_list.remove(current_idx);
self.usage_list.push_front(hash);
return item.0.get();
}
let load_future = AssetLogger::<ImageAssetLoader>::load(resource.clone(), cx);
let task = cx.background_executor().spawn(load_future).shared();
if self.usage_list.len() >= self.max_items {
log::info!("Image cache is full, evicting oldest item");
if let Some(oldest) = self.usage_list.pop_back() {
let mut image = self
.cache
.remove(&oldest)
.expect("usage_list has an item cache doesn't");
if let Some(Ok(image)) = image.0.get() {
log::info!("requesting image to be dropped");
cx.drop_image(image, Some(window));
}
ImageSource::Resource(image.1).remove_asset(cx);
}
}
self.cache.insert(
hash,
(
gpui::ImageCacheItem::Loading(task.clone()),
resource.clone(),
),
);
self.usage_list.push_front(hash);
let entity = window.current_view();
window
.spawn(cx, async move |cx| {
let result = task.await;
if let Err(err) = result {
log::error!("error loading image into cache: {:?}", err);
}
cx.on_next_frame(move |_, cx| {
cx.notify(entity);
});
})
.detach();
None
}
}

View File

@@ -1,3 +1,4 @@
pub use caching::*;
pub use debounced_delay::*;
pub use display::*;
pub use event::*;
@@ -6,6 +7,7 @@ pub use parser::*;
pub use paths::*;
pub use range::*;
mod caching;
mod debounced_delay;
mod display;
mod event;

View File

@@ -15,6 +15,9 @@ pub const KEYRING: &str = "Coop Safe Storage";
/// Default timeout for subscription
pub const TIMEOUT: u64 = 2;
/// Default image cache size
pub const IMAGE_CACHE_SIZE: usize = 20;
/// Default delay for searching
pub const FIND_DELAY: u64 = 600;

View File

@@ -4,18 +4,18 @@ use std::time::Duration;
use anyhow::{Context as AnyhowContext, Error};
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
use common::{DebouncedDelay, TimestampExt};
use common::{DebouncedDelay, TimestampExt, coop_cache};
use entry::RoomEntry;
use gpui::prelude::FluentBuilder;
use gpui::{
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
ParentElement, Render, RetainAllImageCache, SharedString, Styled, Subscription, Task,
UniformListScrollHandle, Window, div, uniform_list,
ParentElement, Render, SharedString, Styled, Subscription, Task, UniformListScrollHandle,
Window, div, uniform_list,
};
use nostr_sdk::prelude::*;
use person::PersonRegistry;
use smallvec::{SmallVec, smallvec};
use state::{FIND_DELAY, NostrRegistry};
use state::{FIND_DELAY, IMAGE_CACHE_SIZE, NostrRegistry};
use theme::{ActiveTheme, SIDEBAR_WIDTH, TABBAR_HEIGHT};
use ui::button::{Button, ButtonVariants};
use ui::dock::{Panel, PanelEvent};
@@ -39,9 +39,6 @@ pub struct Sidebar {
focus_handle: FocusHandle,
scroll_handle: UniformListScrollHandle,
/// Image cache
image_cache: Entity<RetainAllImageCache>,
/// Find input state
find_input: Entity<InputState>,
@@ -141,7 +138,6 @@ impl Sidebar {
name: "Sidebar".into(),
focus_handle: cx.focus_handle(),
scroll_handle: UniformListScrollHandle::new(),
image_cache: RetainAllImageCache::new(cx),
find_input,
find_debouncer: DebouncedDelay::new(),
find_results,
@@ -507,7 +503,7 @@ impl Render for Sidebar {
};
v_flex()
.image_cache(self.image_cache.clone())
.image_cache(coop_cache("sidebar", IMAGE_CACHE_SIZE))
.size_full()
.gap_2()
.child(

View File

@@ -2,19 +2,19 @@ use std::sync::Arc;
use ::settings::AppSettings;
use chat::{ChatEvent, ChatRegistry};
use common::download_dir;
use common::{CoopImageCache, download_dir};
use device::{DeviceEvent, DeviceRegistry};
use gpui::prelude::FluentBuilder;
use gpui::{
Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, div, px,
relative,
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, div,
image_cache, px, relative,
};
use nostr_sdk::prelude::*;
use person::{PersonRegistry, shorten_pubkey};
use serde::Deserialize;
use smallvec::{SmallVec, smallvec};
use state::{NostrRegistry, StateEvent};
use state::{IMAGE_CACHE_SIZE, NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
use title_bar::TitleBar;
use ui::avatar::Avatar;
@@ -70,6 +70,9 @@ pub struct Workspace {
/// App's Dock Area
dock: Entity<DockArea>,
/// App's Image Cache
image_cache: Entity<CoopImageCache>,
/// Event subscriptions
_subscriptions: SmallVec<[Subscription; 5]>,
}
@@ -82,6 +85,7 @@ impl Workspace {
let titlebar = cx.new(|_| TitleBar::new());
let dock = cx.new(|cx| DockArea::new(window, cx));
let image_cache = CoopImageCache::new(IMAGE_CACHE_SIZE, cx);
let mut subscriptions = smallvec![];
@@ -231,6 +235,7 @@ impl Workspace {
Self {
titlebar,
dock,
image_cache,
_subscriptions: subscriptions,
}
}
@@ -848,13 +853,17 @@ impl Render for Workspace {
.relative()
.size_full()
.child(
v_flex()
image_cache(self.image_cache.clone())
.relative()
.size_full()
// Title Bar
.child(self.titlebar.clone())
// Dock
.child(self.dock.clone()),
.child(
v_flex()
.size_full()
// Title Bar
.child(self.titlebar.clone())
// Dock
.child(self.dock.clone()),
),
)
// Notifications
.children(notification_layer)