refactor image cache
This commit is contained in:
@@ -4,7 +4,7 @@ use std::sync::Arc;
|
|||||||
pub use actions::*;
|
pub use actions::*;
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
|
use chat::{ChatRegistry, Message, RenderedMessage, Room, RoomEvent, SendReport, SendStatus};
|
||||||
use common::TimestampExt;
|
use common::{TimestampExt, coop_cache};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
AnyElement, App, AppContext, ClipboardItem, Context, Entity, EventEmitter, FocusHandle,
|
||||||
@@ -1485,6 +1485,7 @@ impl Focusable for ChatPanel {
|
|||||||
impl Render for ChatPanel {
|
impl Render for ChatPanel {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
|
.image_cache(coop_cache(self.id.clone(), 100))
|
||||||
.on_action(cx.listener(Self::on_command))
|
.on_action(cx.listener(Self::on_command))
|
||||||
.size_full()
|
.size_full()
|
||||||
.when(*self.subject_bar.read(cx), |this| {
|
.when(*self.subject_bar.read(cx), |this| {
|
||||||
|
|||||||
135
crates/common/src/caching.rs
Normal file
135
crates/common/src/caching.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub use caching::*;
|
||||||
pub use debounced_delay::*;
|
pub use debounced_delay::*;
|
||||||
pub use display::*;
|
pub use display::*;
|
||||||
pub use event::*;
|
pub use event::*;
|
||||||
@@ -6,6 +7,7 @@ pub use parser::*;
|
|||||||
pub use paths::*;
|
pub use paths::*;
|
||||||
pub use range::*;
|
pub use range::*;
|
||||||
|
|
||||||
|
mod caching;
|
||||||
mod debounced_delay;
|
mod debounced_delay;
|
||||||
mod display;
|
mod display;
|
||||||
mod event;
|
mod event;
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ pub const KEYRING: &str = "Coop Safe Storage";
|
|||||||
/// Default timeout for subscription
|
/// Default timeout for subscription
|
||||||
pub const TIMEOUT: u64 = 2;
|
pub const TIMEOUT: u64 = 2;
|
||||||
|
|
||||||
|
/// Default image cache size
|
||||||
|
pub const IMAGE_CACHE_SIZE: usize = 20;
|
||||||
|
|
||||||
/// Default delay for searching
|
/// Default delay for searching
|
||||||
pub const FIND_DELAY: u64 = 600;
|
pub const FIND_DELAY: u64 = 600;
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use anyhow::{Context as AnyhowContext, Error};
|
use anyhow::{Context as AnyhowContext, Error};
|
||||||
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
use chat::{ChatEvent, ChatRegistry, Room, RoomKind};
|
||||||
use common::{DebouncedDelay, TimestampExt};
|
use common::{DebouncedDelay, TimestampExt, coop_cache};
|
||||||
use entry::RoomEntry;
|
use entry::RoomEntry;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
App, AppContext, Context, Entity, EventEmitter, FocusHandle, Focusable, IntoElement,
|
||||||
ParentElement, Render, RetainAllImageCache, SharedString, Styled, Subscription, Task,
|
ParentElement, Render, SharedString, Styled, Subscription, Task, UniformListScrollHandle,
|
||||||
UniformListScrollHandle, Window, div, uniform_list,
|
Window, div, uniform_list,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::PersonRegistry;
|
use person::PersonRegistry;
|
||||||
use smallvec::{SmallVec, smallvec};
|
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 theme::{ActiveTheme, SIDEBAR_WIDTH, TABBAR_HEIGHT};
|
||||||
use ui::button::{Button, ButtonVariants};
|
use ui::button::{Button, ButtonVariants};
|
||||||
use ui::dock::{Panel, PanelEvent};
|
use ui::dock::{Panel, PanelEvent};
|
||||||
@@ -39,9 +39,6 @@ pub struct Sidebar {
|
|||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
scroll_handle: UniformListScrollHandle,
|
scroll_handle: UniformListScrollHandle,
|
||||||
|
|
||||||
/// Image cache
|
|
||||||
image_cache: Entity<RetainAllImageCache>,
|
|
||||||
|
|
||||||
/// Find input state
|
/// Find input state
|
||||||
find_input: Entity<InputState>,
|
find_input: Entity<InputState>,
|
||||||
|
|
||||||
@@ -141,7 +138,6 @@ impl Sidebar {
|
|||||||
name: "Sidebar".into(),
|
name: "Sidebar".into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
scroll_handle: UniformListScrollHandle::new(),
|
scroll_handle: UniformListScrollHandle::new(),
|
||||||
image_cache: RetainAllImageCache::new(cx),
|
|
||||||
find_input,
|
find_input,
|
||||||
find_debouncer: DebouncedDelay::new(),
|
find_debouncer: DebouncedDelay::new(),
|
||||||
find_results,
|
find_results,
|
||||||
@@ -507,7 +503,7 @@ impl Render for Sidebar {
|
|||||||
};
|
};
|
||||||
|
|
||||||
v_flex()
|
v_flex()
|
||||||
.image_cache(self.image_cache.clone())
|
.image_cache(coop_cache("sidebar", IMAGE_CACHE_SIZE))
|
||||||
.size_full()
|
.size_full()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(
|
.child(
|
||||||
|
|||||||
@@ -2,19 +2,19 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use ::settings::AppSettings;
|
use ::settings::AppSettings;
|
||||||
use chat::{ChatEvent, ChatRegistry};
|
use chat::{ChatEvent, ChatRegistry};
|
||||||
use common::download_dir;
|
use common::{CoopImageCache, download_dir};
|
||||||
use device::{DeviceEvent, DeviceRegistry};
|
use device::{DeviceEvent, DeviceRegistry};
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
|
||||||
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, div, px,
|
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, div,
|
||||||
relative,
|
image_cache, px, relative,
|
||||||
};
|
};
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use person::{PersonRegistry, shorten_pubkey};
|
use person::{PersonRegistry, shorten_pubkey};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use state::{NostrRegistry, StateEvent};
|
use state::{IMAGE_CACHE_SIZE, NostrRegistry, StateEvent};
|
||||||
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
|
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
|
||||||
use title_bar::TitleBar;
|
use title_bar::TitleBar;
|
||||||
use ui::avatar::Avatar;
|
use ui::avatar::Avatar;
|
||||||
@@ -70,6 +70,9 @@ pub struct Workspace {
|
|||||||
/// App's Dock Area
|
/// App's Dock Area
|
||||||
dock: Entity<DockArea>,
|
dock: Entity<DockArea>,
|
||||||
|
|
||||||
|
/// App's Image Cache
|
||||||
|
image_cache: Entity<CoopImageCache>,
|
||||||
|
|
||||||
/// Event subscriptions
|
/// Event subscriptions
|
||||||
_subscriptions: SmallVec<[Subscription; 5]>,
|
_subscriptions: SmallVec<[Subscription; 5]>,
|
||||||
}
|
}
|
||||||
@@ -82,6 +85,7 @@ impl Workspace {
|
|||||||
|
|
||||||
let titlebar = cx.new(|_| TitleBar::new());
|
let titlebar = cx.new(|_| TitleBar::new());
|
||||||
let dock = cx.new(|cx| DockArea::new(window, cx));
|
let dock = cx.new(|cx| DockArea::new(window, cx));
|
||||||
|
let image_cache = CoopImageCache::new(IMAGE_CACHE_SIZE, cx);
|
||||||
|
|
||||||
let mut subscriptions = smallvec![];
|
let mut subscriptions = smallvec![];
|
||||||
|
|
||||||
@@ -231,6 +235,7 @@ impl Workspace {
|
|||||||
Self {
|
Self {
|
||||||
titlebar,
|
titlebar,
|
||||||
dock,
|
dock,
|
||||||
|
image_cache,
|
||||||
_subscriptions: subscriptions,
|
_subscriptions: subscriptions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -848,13 +853,17 @@ impl Render for Workspace {
|
|||||||
.relative()
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
image_cache(self.image_cache.clone())
|
||||||
.relative()
|
.relative()
|
||||||
.size_full()
|
.size_full()
|
||||||
// Title Bar
|
.child(
|
||||||
.child(self.titlebar.clone())
|
v_flex()
|
||||||
// Dock
|
.size_full()
|
||||||
.child(self.dock.clone()),
|
// Title Bar
|
||||||
|
.child(self.titlebar.clone())
|
||||||
|
// Dock
|
||||||
|
.child(self.dock.clone()),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
// Notifications
|
// Notifications
|
||||||
.children(notification_layer)
|
.children(notification_layer)
|
||||||
|
|||||||
Reference in New Issue
Block a user