freya_components/
cache.rs1use std::{
2 any::Any,
3 cell::RefCell,
4 collections::HashMap,
5 hash::{
6 DefaultHasher,
7 Hash,
8 Hasher,
9 },
10 rc::Rc,
11 time::Duration,
12};
13
14use async_io::Timer;
15use freya_core::{
16 integration::FxHashSet,
17 prelude::*,
18};
19
20#[derive(Hash, PartialEq, Eq, Clone, Copy)]
23pub enum AssetAge {
24 Duration(Duration),
26 Unspecified,
28}
29
30impl Default for AssetAge {
31 fn default() -> Self {
32 Self::Duration(Duration::from_secs(3600)) }
34}
35
36impl AssetAge {
37 pub fn zero() -> Self {
39 Self::Duration(Duration::ZERO)
40 }
41}
42
43impl From<Duration> for AssetAge {
44 fn from(value: Duration) -> Self {
45 Self::Duration(value)
46 }
47}
48
49#[derive(Hash, PartialEq, Eq, Clone)]
51pub struct AssetConfiguration {
52 pub age: AssetAge,
54 pub id: u64,
56}
57
58impl AssetConfiguration {
59 pub fn new(id: impl Hash, age: AssetAge) -> Self {
60 let mut state = DefaultHasher::default();
61 id.hash(&mut state);
62 let id = state.finish();
63 Self { id, age }
64 }
65}
66
67enum AssetUsers {
68 Listeners(Rc<RefCell<FxHashSet<ReactiveContext>>>),
69 ClearTask(TaskHandle),
70}
71
72#[derive(Clone)]
73pub enum Asset {
74 Cached(Rc<dyn Any>),
76 Loading,
78 Pending,
80 Error(String),
82}
83
84impl Asset {
85 pub fn try_get(&self) -> Option<&Rc<dyn Any>> {
87 match self {
88 Self::Cached(asset) => Some(asset),
89 _ => None,
90 }
91 }
92}
93
94struct AssetState {
95 users: AssetUsers,
96 asset: Asset,
97}
98
99#[derive(Clone, Copy, PartialEq)]
100pub struct AssetCacher {
101 registry: State<HashMap<AssetConfiguration, AssetState>>,
102}
103
104impl AssetCacher {
105 pub fn create() -> Self {
106 Self {
107 registry: State::create(HashMap::new()),
108 }
109 }
110
111 pub fn try_get() -> Option<Self> {
112 try_consume_root_context()
113 }
114
115 pub fn get() -> Self {
116 consume_root_context()
117 }
118
119 pub fn read_asset(&self, asset_config: &AssetConfiguration) -> Option<Asset> {
121 self.registry
122 .peek()
123 .get(asset_config)
124 .map(|a| a.asset.clone())
125 }
126
127 pub fn subscribe_asset(&self, asset_config: &AssetConfiguration) -> Option<Asset> {
129 self.listen(ReactiveContext::current(), asset_config.clone());
130 self.registry
131 .peek()
132 .get(asset_config)
133 .map(|a| a.asset.clone())
134 }
135
136 pub fn update_asset(&mut self, asset_config: AssetConfiguration, new_asset: Asset) {
140 let mut registry = self.registry.write();
141
142 let Some(asset) = registry.get_mut(&asset_config) else {
143 return;
144 };
145
146 asset.asset = new_asset;
147
148 if let AssetUsers::Listeners(listeners) = &asset.users {
150 for sub in listeners.borrow().iter() {
151 sub.notify();
152 }
153 }
154 }
155
156 pub fn try_clean(&mut self, asset_config: &AssetConfiguration) {
158 let mut registry = self.registry;
159
160 let spawn_clear_task = {
161 let mut registry = registry.write();
162
163 let entry = registry.get_mut(asset_config);
164 if let Some(asset_state) = entry {
165 match &mut asset_state.users {
166 AssetUsers::Listeners(listeners) => {
167 listeners.borrow().is_empty()
169 }
170 AssetUsers::ClearTask(task) => {
171 task.cancel();
173 true
174 }
175 }
176 } else {
177 false
178 }
179 };
180
181 if spawn_clear_task {
182 if let AssetAge::Duration(duration) = asset_config.age {
184 let clear_task = spawn_forever({
185 let asset_config = asset_config.clone();
186 async move {
187 Timer::after(duration).await;
188 registry.write().remove(&asset_config);
189 }
190 });
191
192 let mut registry = registry.write();
194 if let Some(entry) = registry.get_mut(asset_config) {
195 entry.users = AssetUsers::ClearTask(clear_task);
196 } else {
197 #[cfg(debug_assertions)]
198 tracing::info!(
199 "Failed to spawn clear task to remove cache of {}",
200 asset_config.id
201 )
202 }
203 }
204 }
205 }
206
207 pub(crate) fn listen(&self, mut rc: ReactiveContext, asset_config: AssetConfiguration) {
208 let mut registry = self.registry.write_unchecked();
209
210 let asset = registry.entry(asset_config).or_insert_with(|| AssetState {
211 asset: Asset::Pending,
212 users: AssetUsers::Listeners(Rc::default()),
213 });
214
215 match &mut asset.users {
216 AssetUsers::Listeners(users) => {
217 rc.subscribe(users);
218 }
219 AssetUsers::ClearTask(clear_task) => {
220 clear_task.cancel();
221 let listeners = Rc::default();
222 rc.subscribe(&listeners);
223 asset.users = AssetUsers::Listeners(listeners);
224 }
225 }
226 }
227
228 pub fn size(&self) -> usize {
230 self.registry.read().len()
231 }
232}
233
234pub fn use_asset(asset_config: &AssetConfiguration) -> Asset {
236 let mut asset_cacher = use_hook(AssetCacher::get);
237
238 use_drop({
239 let asset_config = asset_config.clone();
240 move || {
241 spawn_forever(async move {
243 asset_cacher.try_clean(&asset_config);
244 });
245 }
246 });
247
248 let mut prev = use_state::<Option<AssetConfiguration>>(|| None);
249 {
250 let mut prev = prev.write();
251 if prev.as_ref() != Some(asset_config) {
252 if let Some(prev) = &*prev
253 && prev != asset_config
254 {
255 asset_cacher.try_clean(prev);
257 }
258 prev.replace(asset_config.clone());
259 }
260 asset_cacher.listen(ReactiveContext::current(), asset_config.clone());
261 }
262
263 asset_cacher
264 .read_asset(asset_config)
265 .expect("Asset should be be cached by now.")
266}