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, 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, cache: HashMap, } impl CoopImageCache { pub fn new(max_items: usize, cx: &mut App) -> Entity { 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, 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::::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 } }