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