Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/rendering/texture_cache.rs
StratoSDK / crates / strato-ui-core / src / rendering / texture_cache.rs
1use std::sync::{Arc, Weak};
2 
3use crate::image_cache::StaticImage;
4 
5/// An opaque identifier for a texture from which we can render an image.
6///
7/// This *MUST* only be used within a single frame, and is not safe to use
8/// across frames.
9#[derive(Copy, Clone)]
10pub struct TextureCacheIndex(usize);
11 
12pub struct TextureInfo<T> {
13 /// The actual information about the texture.
14 inner: T,
15 
16 /// The backing asset for the texture.
17 asset: Weak<StaticImage>,
18 
19 /// The index of the last frame on which this texture was accessed. This
20 /// is used to know when a texture has gone "stale" and can be dropped from
21 /// the cache.
22 last_accessed_frame: usize,
23}
24 
25/// A simple cache for textures from which we can render images.
26pub struct TextureCache<T> {
27 textures: Vec<TextureInfo<T>>,
28 
29 /// The index of the last frame that was rendered.
30 frame_index: usize,
31}
32 
33impl<T> TextureCache<T> {
34 /// The maximum number of frames that a texture can go unused before it
35 /// gets dropped from the cache.
36 const MAX_UNUSED_FRAMES: usize = 10;
37 
38 pub fn new() -> Self {
39 Self {
40 textures: Default::default(),
41 frame_index: 0,
42 }
43 }
44 
45 pub fn get(&self, texture_id: TextureCacheIndex) -> Option<&T> {
46 self.textures.get(texture_id.0).map(|info| &info.inner)
47 }
48 
49 pub fn get_or_insert_by_asset(
50 &mut self,
51 asset: &Arc<StaticImage>,
52 texinfo_func: impl FnOnce(&Arc<StaticImage>) -> T,
53 ) -> (TextureCacheIndex, &T) {
54 let mut found = None;
55 let weak_asset = Arc::downgrade(asset);
56 for (index, texture) in self.textures.iter().enumerate() {
57 if texture.asset.ptr_eq(&weak_asset) {
58 found = Some(index);
59 }
60 }
61 let index = match found {
62 Some(index) => index,
63 None => {
64 self.textures.push(TextureInfo {
65 inner: texinfo_func(asset),
66 asset: weak_asset.clone(),
67 last_accessed_frame: self.frame_index,
68 });
69 self.textures.len() - 1
70 }
71 };
72 
73 // This array lookup is safe, as we either found the texture in the
74 // cache or we inserted a new one and returned its index.
75 self.textures[index].last_accessed_frame = self.frame_index;
76 
77 (TextureCacheIndex(index), &self.textures[index].inner)
78 }
79 
80 /// Updates the texture cache at the end of a frame.
81 ///
82 /// This should be called at the end of every frame to ensure that stale
83 /// texture resources get cleaned up.
84 pub fn end_frame(&mut self) {
85 // Drop any textures which are no longer referenced by the asset cache
86 // or have not been rendered in the last MAX_UNUSED_FRAMES frames.
87 self.textures.retain(|texture| {
88 texture.asset.strong_count() > 0
89 && self.frame_index - texture.last_accessed_frame < Self::MAX_UNUSED_FRAMES
90 });
91 
92 self.frame_index += 1;
93 }
94}
95 
96impl<T> Default for TextureCache<T> {
97 fn default() -> Self {
98 Self::new()
99 }
100}
101 
102#[cfg(test)]
103#[path = "texture_cache_tests.rs"]
104mod tests;
105