image_cache.rs

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