From 73b2eac0809e0a314d305eec88d7ede5f01109ce Mon Sep 17 00:00:00 2001 From: reya Date: Wed, 23 Apr 2025 08:16:26 +0700 Subject: [PATCH] feat: add image cache --- Cargo.lock | 76 +++++++++++----------- crates/coop/src/chatspace.rs | 114 ++++++++++++++++++--------------- crates/coop/src/lru_cache.rs | 117 ++++++++++++++++++++++++++++++++++ crates/coop/src/main.rs | 1 + crates/coop/src/views/chat.rs | 53 +++++++-------- 5 files changed, 243 insertions(+), 118 deletions(-) create mode 100644 crates/coop/src/lru_cache.rs diff --git a/Cargo.lock b/Cargo.lock index 45dd1f3..35d9eae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -485,9 +485,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.28.1" +version = "0.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ddeb19ee86cb16ecfc871e5b0660aff6285760957aaedda6284cf0e790d3769" +checksum = "bfa9b6986f250236c27e5a204062434a773a13243d2ffc2955f37bdba4c5c6a1" dependencies = [ "bindgen 0.69.5", "cc", @@ -1135,7 +1135,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1203,7 +1203,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -1472,9 +1472,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e9666f4a9a948d4f1dff0c08a4512b0f7c86414b23960104c243c10d79f4c3" +checksum = "a4735f265ba6a1188052ca32d461028a7d1125868be18e287e756019da7607b5" dependencies = [ "ctor-proc-macro", "dtor", @@ -1524,7 +1524,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "proc-macro2", "quote", @@ -1627,9 +1627,9 @@ dependencies = [ [[package]] name = "dtor" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "222ef136a1c687d4aa0395c175f2c4586e379924c352fd02f7870cf7de783c23" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" dependencies = [ "dtor-proc-macro", ] @@ -2179,9 +2179,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -2306,7 +2306,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2398,7 +2398,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "proc-macro2", "quote", @@ -2622,7 +2622,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "bytes", @@ -2639,7 +2639,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3198,14 +3198,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" [[package]] name = "libredox" @@ -3380,7 +3380,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "bindgen 0.71.1", @@ -3508,7 +3508,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -3578,7 +3578,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" [[package]] name = "nostr" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "aes", "base64", @@ -3588,7 +3588,7 @@ dependencies = [ "cbc", "chacha20", "chacha20poly1305", - "getrandom 0.2.15", + "getrandom 0.2.16", "instant", "regex", "reqwest 0.12.15", @@ -3603,7 +3603,7 @@ dependencies = [ [[package]] name = "nostr-connect" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "async-utility", "nostr", @@ -3615,7 +3615,7 @@ dependencies = [ [[package]] name = "nostr-database" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "flatbuffers", "lru", @@ -3626,7 +3626,7 @@ dependencies = [ [[package]] name = "nostr-lmdb" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "async-utility", "heed", @@ -3639,7 +3639,7 @@ dependencies = [ [[package]] name = "nostr-relay-pool" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "async-utility", "async-wsocket", @@ -3656,7 +3656,7 @@ dependencies = [ [[package]] name = "nostr-sdk" version = "0.41.0" -source = "git+https://github.com/rust-nostr/nostr#54bdb993633146ca4bcfbcedd15735fff7dcbada" +source = "git+https://github.com/rust-nostr/nostr#078cfdd7130c94e461ba8f85956ed0a8ae364dac" dependencies = [ "async-utility", "nostr", @@ -4563,9 +4563,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.10" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc" +checksum = "bcbafbbdbb0f638fe3f35f3c56739f77a8a1d070cb25603226c83339b391472b" dependencies = [ "bytes", "getrandom 0.3.2", @@ -4657,7 +4657,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4805,7 +4805,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 1.0.69", ] @@ -4813,7 +4813,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "derive_refineable", "workspace-hack", @@ -4951,7 +4951,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "bytes", @@ -4998,7 +4998,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -5421,7 +5421,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semantic_version" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "serde", @@ -5744,7 +5744,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "arrayvec", "log", @@ -6646,7 +6646,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#107d8ca483276263362c23482612d1cadea81c71" +source = "git+https://github.com/zed-industries/zed#6a009b447af415f80cdb55d8c6064e984b899fd5" dependencies = [ "anyhow", "async-fs", @@ -7076,7 +7076,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index e88a70b..9f61a95 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -2,8 +2,9 @@ use account::Account; use anyhow::Error; use global::get_client; use gpui::{ - div, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis, Context, Entity, - InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription, Task, Window, + div, image_cache, impl_internal_actions, prelude::FluentBuilder, px, App, AppContext, Axis, + Context, Entity, InteractiveElement, IntoElement, ParentElement, Render, Styled, Subscription, + Task, Window, }; use nostr_sdk::prelude::*; use serde::Deserialize; @@ -16,9 +17,14 @@ use ui::{ ContextModal, IconName, Root, Sizable, TitleBar, }; -use crate::views::{chat, compose, contacts, login, new_account, profile, relays, welcome}; -use crate::views::{onboarding, sidebar}; +use crate::{ + lru_cache::cache_provider, + views::{ + chat, compose, contacts, login, new_account, onboarding, profile, relays, sidebar, welcome, + }, +}; +const CACHE_SIZE: usize = 200; const MODAL_WIDTH: f32 = 420.; const SIDEBAR_WIDTH: f32 = 280.; @@ -288,56 +294,62 @@ impl Render for ChatSpace { .relative() .size_full() .child( - div() - .flex() - .flex_col() + image_cache(cache_provider("image-cache", CACHE_SIZE)) .size_full() - // Title Bar - .when(self.titlebar, |this| { - this.child( - TitleBar::new() - // Left side - .child(div()) - // Right side - .child( - div() - .flex() - .items_center() - .justify_end() - .gap_2() - .px_2() + .child( + div() + .flex() + .flex_col() + .size_full() + // Title Bar + .when(self.titlebar, |this| { + this.child( + TitleBar::new() + // Left side + .child(div()) + // Right side .child( - Button::new("appearance") - .xsmall() - .ghost() - .map(|this| { - if cx.theme().appearance.is_dark() { - this.icon(IconName::Sun) - } else { - this.icon(IconName::Moon) - } - }) - .on_click(cx.listener(|_, _, window, cx| { - if cx.theme().appearance.is_dark() { - Theme::change( - Appearance::Light, - Some(window), - cx, - ); - } else { - Theme::change( - Appearance::Dark, - Some(window), - cx, - ); - } - })), + div() + .flex() + .items_center() + .justify_end() + .gap_2() + .px_2() + .child( + Button::new("appearance") + .xsmall() + .ghost() + .map(|this| { + if cx.theme().appearance.is_dark() { + this.icon(IconName::Sun) + } else { + this.icon(IconName::Moon) + } + }) + .on_click(cx.listener( + |_, _, window, cx| { + if cx.theme().appearance.is_dark() { + Theme::change( + Appearance::Light, + Some(window), + cx, + ); + } else { + Theme::change( + Appearance::Dark, + Some(window), + cx, + ); + } + }, + )), + ), ), - ), - ) - }) - // Dock - .child(self.dock.clone()), + ) + }) + // Dock + .child(self.dock.clone()), + ), ) // Notifications .child(div().absolute().top_8().children(notification_layer)) diff --git a/crates/coop/src/lru_cache.rs b/crates/coop/src/lru_cache.rs new file mode 100644 index 0000000..8de0238 --- /dev/null +++ b/crates/coop/src/lru_cache.rs @@ -0,0 +1,117 @@ +use std::{collections::HashMap, sync::Arc}; + +use futures::FutureExt; +use gpui::{ + hash, AnyImageCache, App, AppContext, Asset, AssetLogger, Context, ElementId, Entity, + ImageAssetLoader, ImageCache, ImageCacheProvider, Window, +}; + +pub fn cache_provider(id: impl Into, max_items: usize) -> LruCacheProvider { + LruCacheProvider { + id: id.into(), + max_items, + } +} + +pub struct LruCacheProvider { + id: ElementId, + max_items: usize, +} + +impl ImageCacheProvider for LruCacheProvider { + fn provide(&mut self, window: &mut Window, cx: &mut App) -> AnyImageCache { + window + .with_global_id(self.id.clone(), |global_id, window| { + window.with_element_state::, _>(global_id, |lru_cache, _window| { + let mut lru_cache = + lru_cache.unwrap_or_else(|| cx.new(|cx| LruCache::new(self.max_items, cx))); + if lru_cache.read(cx).max_items != self.max_items { + lru_cache = cx.new(|cx| LruCache::new(self.max_items, cx)); + } + (lru_cache.clone(), lru_cache) + }) + }) + .into() + } +} + +struct LruCache { + max_items: usize, + usages: Vec, + cache: HashMap, +} + +impl LruCache { + fn new(max_items: usize, cx: &mut Context) -> Self { + cx.on_release(|simple_cache, cx| { + for (_, mut item) in std::mem::take(&mut simple_cache.cache) { + if let Some(Ok(image)) = item.get() { + cx.drop_image(image, None); + } + } + }) + .detach(); + + Self { + max_items, + usages: Vec::with_capacity(max_items), + cache: HashMap::with_capacity(max_items), + } + } +} + +impl ImageCache for LruCache { + fn load( + &mut self, + resource: &gpui::Resource, + window: &mut Window, + cx: &mut App, + ) -> Option, gpui::ImageCacheError>> { + assert_eq!(self.usages.len(), self.cache.len()); + assert!(self.cache.len() <= self.max_items); + + let hash = hash(resource); + + if let Some(item) = self.cache.get_mut(&hash) { + let current_ix = self + .usages + .iter() + .position(|item| *item == hash) + .expect("cache and usages must stay in sync"); + self.usages.remove(current_ix); + self.usages.insert(0, hash); + + return item.get(); + } + + let fut = AssetLogger::::load(resource.clone(), cx); + let task = cx.background_executor().spawn(fut).shared(); + if self.usages.len() == self.max_items { + let oldest = self.usages.pop().unwrap(); + let mut image = self + .cache + .remove(&oldest) + .expect("cache and usages must be in sync"); + if let Some(Ok(image)) = image.get() { + cx.drop_image(image, Some(window)); + } + } + self.cache + .insert(hash, gpui::ImageCacheItem::Loading(task.clone())); + self.usages.insert(0, hash); + + let entity = window.current_view(); + window + .spawn(cx, { + async move |cx| { + _ = task.await; + cx.on_next_frame(move |_, cx| { + cx.notify(entity); + }); + } + }) + .detach(); + + None + } +} diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 1582bd7..d58db3a 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -28,6 +28,7 @@ use ui::{theme::Theme, Root}; pub(crate) mod asset; pub(crate) mod chatspace; +pub(crate) mod lru_cache; pub(crate) mod views; actions!(coop, [Quit]); diff --git a/crates/coop/src/views/chat.rs b/crates/coop/src/views/chat.rs index 256e543..8049770 100644 --- a/crates/coop/src/views/chat.rs +++ b/crates/coop/src/views/chat.rs @@ -226,7 +226,6 @@ impl Chat { return; } - // temporarily disable message input self.input.update(cx, |this, cx| { this.set_loading(true, window, cx); this.set_disabled(true, window, cx); @@ -235,39 +234,35 @@ impl Chat { let room = self.room.read(cx); let task = room.send_message(content, cx); - cx.spawn_in(window, async move |this, cx| { - match task.await { - Ok(reports) => { - cx.update(|window, cx| { - this.update(cx, |this, cx| { - // Reset message input - this.input.update(cx, |this, cx| { - this.set_loading(false, window, cx); - this.set_disabled(false, window, cx); - this.set_text("", window, cx); - cx.notify(); - }); - }) - .ok(); - - for item in reports.into_iter() { - window.push_notification( - Notification::error(item).title("Message Failed to Send"), - cx, - ); - } + cx.spawn_in(window, async move |this, cx| match task.await { + Ok(reports) => { + cx.update(|window, cx| { + this.update(cx, |this, cx| { + this.input.update(cx, |this, cx| { + this.set_loading(false, window, cx); + this.set_disabled(false, window, cx); + this.set_text("", window, cx); + }); }) .ok(); - } - Err(e) => { - cx.update(|window, cx| { + + for item in reports.into_iter() { window.push_notification( - Notification::error(e.to_string()).title("Message Failed to Send"), + Notification::error(item).title("Message Failed to Send"), cx, ); - }) - .ok(); - } + } + }) + .ok(); + } + Err(e) => { + cx.update(|window, cx| { + window.push_notification( + Notification::error(e.to_string()).title("Message Failed to Send"), + cx, + ); + }) + .ok(); } }) .detach();