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)]
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) {
138 let mut registry = self.registry.write();
139
140 let asset = registry.entry(asset_config).or_insert_with(|| AssetState {
141 asset: Asset::Pending,
142 users: AssetUsers::Listeners(Rc::default()),
143 });
144
145 asset.asset = new_asset;
146
147 if let AssetUsers::Listeners(listeners) = &asset.users {
149 for sub in listeners.borrow().iter() {
150 sub.notify();
151 }
152 }
153 }
154
155 pub fn try_clean(&mut self, asset_config: &AssetConfiguration) {
157 let mut registry = self.registry;
158
159 let spawn_clear_task = {
160 let mut registry = registry.write();
161
162 let entry = registry.get_mut(asset_config);
163 if let Some(asset_state) = entry {
164 match &mut asset_state.users {
165 AssetUsers::Listeners(listeners) => {
166 listeners.borrow().is_empty()
168 }
169 AssetUsers::ClearTask(task) => {
170 task.cancel();
172 true
173 }
174 }
175 } else {
176 false
177 }
178 };
179
180 if spawn_clear_task {
181 if let AssetAge::Duration(duration) = asset_config.age {
183 let clear_task = spawn_forever({
184 let asset_config = asset_config.clone();
185 async move {
186 Timer::after(duration).await;
187 registry.write().remove(&asset_config);
188 }
189 });
190
191 let mut registry = registry.write();
193 if let Some(entry) = registry.get_mut(asset_config) {
194 entry.users = AssetUsers::ClearTask(clear_task);
195 } else {
196 #[cfg(debug_assertions)]
197 tracing::info!(
198 "Failed to spawn clear task to remove cache of {}",
199 asset_config.id
200 )
201 }
202 }
203 }
204 }
205
206 pub(crate) fn listen(&self, mut rc: ReactiveContext, asset_config: AssetConfiguration) {
207 let mut registry = self.registry.write_unchecked();
208
209 let asset = registry.entry(asset_config).or_insert_with(|| AssetState {
210 asset: Asset::Pending,
211 users: AssetUsers::Listeners(Rc::default()),
212 });
213
214 match &mut asset.users {
215 AssetUsers::Listeners(users) => {
216 rc.subscribe(users);
217 }
218 AssetUsers::ClearTask(clear_task) => {
219 clear_task.cancel();
220 let listeners = Rc::default();
221 rc.subscribe(&listeners);
222 asset.users = AssetUsers::Listeners(listeners);
223 }
224 }
225 }
226
227 pub fn size(&self) -> usize {
229 self.registry.read().len()
230 }
231}
232
233pub fn use_asset(asset_config: &AssetConfiguration) -> Asset {
235 let mut asset_cacher = use_hook(AssetCacher::get);
236
237 use_drop({
238 let asset_config = asset_config.clone();
239 move || {
240 spawn_forever(async move {
242 asset_cacher.try_clean(&asset_config);
243 });
244 }
245 });
246
247 let mut prev = use_state::<Option<AssetConfiguration>>(|| None);
248 {
249 let mut prev = prev.write();
250 if prev.as_ref() != Some(asset_config) {
251 if let Some(prev) = &*prev
252 && prev != asset_config
253 {
254 asset_cacher.try_clean(asset_config);
256 }
257 prev.replace(asset_config.clone());
258 }
259 asset_cacher.listen(ReactiveContext::current(), asset_config.clone());
260 }
261
262 asset_cacher
263 .read_asset(asset_config)
264 .expect("Asset should be be cached by now.")
265}