image_cache.rs

  1use crate::{
  2    AnyElement, AnyEntity, App, AppContext, Asset, AssetLogger, Bounds, Element, ElementId, Entity,
  3    GlobalElementId, ImageAssetLoader, ImageCacheError, IntoElement, LayoutId, ParentElement,
  4    Pixels, RenderImage, Resource, Style, StyleRefinement, Styled, Task, Window, hash,
  5};
  6
  7use futures::{FutureExt, future::Shared};
  8use refineable::Refineable;
  9use smallvec::SmallVec;
 10use std::{collections::HashMap, fmt, sync::Arc};
 11
 12/// An image cache element, all its child img elements will use the cache specified by this element.
 13pub fn image_cache<I: ImageCache>(image_cache: &Entity<I>) -> ImageCacheElement {
 14    ImageCacheElement {
 15        image_cache: image_cache.clone().into(),
 16        style: StyleRefinement::default(),
 17        children: SmallVec::default(),
 18    }
 19}
 20
 21/// A dynamically typed image cache, which can be used to store any image cache
 22#[derive(Clone)]
 23pub struct AnyImageCache {
 24    image_cache: AnyEntity,
 25    load_fn: fn(
 26        image_cache: &AnyEntity,
 27        resource: &Resource,
 28        window: &mut Window,
 29        cx: &mut App,
 30    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>,
 31}
 32
 33impl<I: ImageCache> From<Entity<I>> for AnyImageCache {
 34    fn from(image_cache: Entity<I>) -> Self {
 35        Self {
 36            image_cache: image_cache.into_any(),
 37            load_fn: any_image_cache::load::<I>,
 38        }
 39    }
 40}
 41
 42impl AnyImageCache {
 43    /// Load an image given a resource
 44    /// returns the result of loading the image if it has finished loading, or None if it is still loading
 45    pub fn load(
 46        &self,
 47        resource: &Resource,
 48        window: &mut Window,
 49        cx: &mut App,
 50    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
 51        (self.load_fn)(&self.image_cache, resource, window, cx)
 52    }
 53}
 54
 55mod any_image_cache {
 56    use super::*;
 57
 58    pub(crate) fn load<I: 'static + ImageCache>(
 59        image_cache: &AnyEntity,
 60        resource: &Resource,
 61        window: &mut Window,
 62        cx: &mut App,
 63    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
 64        let image_cache = image_cache.clone().downcast::<I>().unwrap();
 65        return image_cache.update(cx, |image_cache, cx| image_cache.load(resource, window, cx));
 66    }
 67}
 68
 69/// An image cache element.
 70pub struct ImageCacheElement {
 71    image_cache: AnyImageCache,
 72    style: StyleRefinement,
 73    children: SmallVec<[AnyElement; 2]>,
 74}
 75
 76impl ParentElement for ImageCacheElement {
 77    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
 78        self.children.extend(elements)
 79    }
 80}
 81
 82impl Styled for ImageCacheElement {
 83    fn style(&mut self) -> &mut StyleRefinement {
 84        &mut self.style
 85    }
 86}
 87
 88impl IntoElement for ImageCacheElement {
 89    type Element = Self;
 90
 91    fn into_element(self) -> Self::Element {
 92        self
 93    }
 94}
 95
 96impl Element for ImageCacheElement {
 97    type RequestLayoutState = SmallVec<[LayoutId; 4]>;
 98    type PrepaintState = ();
 99
100    fn id(&self) -> Option<ElementId> {
101        None
102    }
103
104    fn request_layout(
105        &mut self,
106        _id: Option<&GlobalElementId>,
107        window: &mut Window,
108        cx: &mut App,
109    ) -> (LayoutId, Self::RequestLayoutState) {
110        window.with_image_cache(self.image_cache.clone(), |window| {
111            let child_layout_ids = self
112                .children
113                .iter_mut()
114                .map(|child| child.request_layout(window, cx))
115                .collect::<SmallVec<_>>();
116            let mut style = Style::default();
117            style.refine(&self.style);
118            let layout_id = window.request_layout(style, child_layout_ids.iter().copied(), cx);
119            (layout_id, child_layout_ids)
120        })
121    }
122
123    fn prepaint(
124        &mut self,
125        _id: Option<&GlobalElementId>,
126        _bounds: Bounds<Pixels>,
127        _request_layout: &mut Self::RequestLayoutState,
128        window: &mut Window,
129        cx: &mut App,
130    ) -> Self::PrepaintState {
131        for child in &mut self.children {
132            child.prepaint(window, cx);
133        }
134    }
135
136    fn paint(
137        &mut self,
138        _id: Option<&GlobalElementId>,
139        _bounds: Bounds<Pixels>,
140        _request_layout: &mut Self::RequestLayoutState,
141        _prepaint: &mut Self::PrepaintState,
142        window: &mut Window,
143        cx: &mut App,
144    ) {
145        window.with_image_cache(self.image_cache.clone(), |window| {
146            for child in &mut self.children {
147                child.paint(window, cx);
148            }
149        })
150    }
151}
152
153type ImageLoadingTask = Shared<Task<Result<Arc<RenderImage>, ImageCacheError>>>;
154
155enum CacheItem {
156    Loading(ImageLoadingTask),
157    Loaded(Result<Arc<RenderImage>, ImageCacheError>),
158}
159
160impl CacheItem {
161    fn get(&mut self) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
162        match self {
163            CacheItem::Loading(task) => {
164                let res = task.now_or_never()?;
165                *self = CacheItem::Loaded(res.clone());
166                Some(res)
167            }
168            CacheItem::Loaded(res) => Some(res.clone()),
169        }
170    }
171}
172
173/// An object that can handle the caching and unloading of images.
174/// Implementations of this trait should ensure that images are removed from all windows when they are no longer needed.
175pub trait ImageCache: 'static {
176    /// Load an image given a resource
177    /// returns the result of loading the image if it has finished loading, or None if it is still loading
178    fn load(
179        &mut self,
180        resource: &Resource,
181        window: &mut Window,
182        cx: &mut App,
183    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>>;
184}
185
186/// An implementation of ImageCache, that uses an LRU caching strategy to unload images when the cache is full
187pub struct HashMapImageCache(HashMap<u64, CacheItem>);
188
189impl fmt::Debug for HashMapImageCache {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        f.debug_struct("HashMapImageCache")
192            .field("num_images", &self.0.len())
193            .finish()
194    }
195}
196
197impl HashMapImageCache {
198    /// Create a new image cache.
199    #[inline]
200    pub fn new(cx: &mut App) -> Entity<Self> {
201        let e = cx.new(|_cx| HashMapImageCache(HashMap::new()));
202        cx.observe_release(&e, |image_cache, cx| {
203            for (_, mut item) in std::mem::replace(&mut image_cache.0, HashMap::new()) {
204                if let Some(Ok(image)) = item.get() {
205                    cx.drop_image(image, None);
206                }
207            }
208        })
209        .detach();
210        e
211    }
212
213    /// Load an image from the given source.
214    ///
215    /// Returns `None` if the image is loading.
216    pub fn load(
217        &mut self,
218        source: &Resource,
219        window: &mut Window,
220        cx: &mut App,
221    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
222        let hash = hash(source);
223
224        if let Some(item) = self.0.get_mut(&hash) {
225            return item.get();
226        }
227
228        let fut = AssetLogger::<ImageAssetLoader>::load(source.clone(), cx);
229        let task = cx.background_executor().spawn(fut).shared();
230        self.0.insert(hash, CacheItem::Loading(task.clone()));
231
232        let entity = window.current_view();
233        window
234            .spawn(cx, {
235                async move |cx| {
236                    _ = task.await;
237                    cx.on_next_frame(move |_, cx| {
238                        cx.notify(entity);
239                    });
240                }
241            })
242            .detach();
243
244        None
245    }
246
247    /// Clear the image cache.
248    pub fn clear(&mut self, window: &mut Window, cx: &mut App) {
249        for (_, mut item) in std::mem::replace(&mut self.0, HashMap::new()) {
250            if let Some(Ok(image)) = item.get() {
251                cx.drop_image(image, Some(window));
252            }
253        }
254    }
255
256    /// Remove the image from the cache by the given source.
257    pub fn remove(&mut self, source: &Resource, window: &mut Window, cx: &mut App) {
258        let hash = hash(source);
259        if let Some(mut item) = self.0.remove(&hash) {
260            if let Some(Ok(image)) = item.get() {
261                cx.drop_image(image, Some(window));
262            }
263        }
264    }
265
266    /// Returns the number of images in the cache.
267    pub fn len(&self) -> usize {
268        self.0.len()
269    }
270
271    /// Returns true if the cache is empty.
272    pub fn is_empty(&self) -> bool {
273        self.0.is_empty()
274    }
275}
276
277impl ImageCache for HashMapImageCache {
278    fn load(
279        &mut self,
280        resource: &Resource,
281        window: &mut Window,
282        cx: &mut App,
283    ) -> Option<Result<Arc<RenderImage>, ImageCacheError>> {
284        HashMapImageCache::load(self, resource, window, cx)
285    }
286}