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 From<Duration> for AssetAge {
37 fn from(value: Duration) -> Self {
38 Self::Duration(value)
39 }
40}
41
42#[derive(Hash, PartialEq, Eq, Clone)]
44pub struct AssetConfiguration {
45 pub age: AssetAge,
47 pub id: u64,
49}
50
51impl AssetConfiguration {
52 pub fn new(id: impl Hash, age: AssetAge) -> Self {
53 let mut state = DefaultHasher::default();
54 id.hash(&mut state);
55 let id = state.finish();
56 Self { id, age }
57 }
58}
59
60enum AssetUsers {
61 Listeners(Rc<RefCell<FxHashSet<ReactiveContext>>>),
62 ClearTask(TaskHandle),
63}
64
65#[derive(Clone)]
66pub enum Asset {
67 Cached(Rc<dyn Any>),
69 Loading,
71 Pending,
73 Error(String),
75}
76
77impl Asset {
78 pub fn try_get(&self) -> Option<&Rc<dyn Any>> {
80 match self {
81 Self::Cached(asset) => Some(asset),
82 _ => None,
83 }
84 }
85}
86
87struct AssetState {
88 users: AssetUsers,
89 asset: Asset,
90}
91
92#[derive(Clone, Copy, PartialEq)]
93pub struct AssetCacher {
94 registry: State<HashMap<AssetConfiguration, AssetState>>,
95}
96
97impl AssetCacher {
98 pub fn create() -> Self {
99 Self {
100 registry: State::create(HashMap::new()),
101 }
102 }
103
104 pub fn try_get() -> Option<Self> {
105 try_consume_root_context()
106 }
107
108 pub fn get() -> Self {
109 consume_root_context()
110 }
111
112 pub fn read_asset(&self, asset_config: &AssetConfiguration) -> Option<Asset> {
114 self.registry
115 .peek()
116 .get(asset_config)
117 .map(|a| a.asset.clone())
118 }
119
120 pub fn subscribe_asset(&self, asset_config: &AssetConfiguration) -> Option<Asset> {
122 self.listen(ReactiveContext::current(), asset_config.clone());
123 self.registry
124 .peek()
125 .get(asset_config)
126 .map(|a| a.asset.clone())
127 }
128
129 pub fn update_asset(&mut self, asset_config: AssetConfiguration, new_asset: Asset) {
131 let mut registry = self.registry.write();
132
133 let asset = registry.entry(asset_config).or_insert_with(|| AssetState {
134 asset: Asset::Pending,
135 users: AssetUsers::Listeners(Rc::default()),
136 });
137
138 asset.asset = new_asset;
139
140 if let AssetUsers::Listeners(listeners) = &asset.users {
142 for sub in listeners.borrow().iter() {
143 sub.notify();
144 }
145 }
146 }
147
148 pub fn try_clean(&mut self, asset_config: &AssetConfiguration) {
150 let mut registry = self.registry;
151
152 let spawn_clear_task = {
153 let mut registry = registry.write();
154
155 let entry = registry.get_mut(asset_config);
156 if let Some(asset_state) = entry {
157 match &mut asset_state.users {
158 AssetUsers::Listeners(listeners) => {
159 listeners.borrow().is_empty()
161 }
162 AssetUsers::ClearTask(task) => {
163 task.cancel();
165 true
166 }
167 }
168 } else {
169 false
170 }
171 };
172
173 if spawn_clear_task {
174 if let AssetAge::Duration(duration) = asset_config.age {
176 let clear_task = spawn_forever({
177 let asset_config = asset_config.clone();
178 async move {
179 Timer::after(duration).await;
180 registry.write().remove(&asset_config);
181 }
182 });
183
184 let mut registry = registry.write();
186 if let Some(entry) = registry.get_mut(asset_config) {
187 entry.users = AssetUsers::ClearTask(clear_task);
188 } else {
189 #[cfg(debug_assertions)]
190 tracing::info!(
191 "Failed to spawn clear task to remove cache of {}",
192 asset_config.id
193 )
194 }
195 }
196 }
197 }
198
199 pub(crate) fn listen(&self, mut rc: ReactiveContext, asset_config: AssetConfiguration) {
200 let mut registry = self.registry.write_unchecked();
201
202 registry
203 .entry(asset_config.clone())
204 .or_insert_with(|| AssetState {
205 asset: Asset::Pending,
206 users: AssetUsers::Listeners(Rc::default()),
207 });
208
209 if let Some(asset) = registry.get(&asset_config) {
210 match &asset.users {
211 AssetUsers::Listeners(users) => {
212 rc.subscribe(users);
213 }
214 AssetUsers::ClearTask(clear_task) => {
215 clear_task.cancel();
216 }
217 }
218 }
219 }
220
221 pub fn size(&self) -> usize {
223 self.registry.read().len()
224 }
225}
226
227pub fn use_asset(asset_config: &AssetConfiguration) -> Asset {
229 let mut asset_cacher = use_hook(AssetCacher::get);
230
231 use_drop({
232 let asset_config = asset_config.clone();
233 move || {
234 spawn_forever(async move {
236 asset_cacher.try_clean(&asset_config);
237 });
238 }
239 });
240
241 let mut prev = use_state::<Option<AssetConfiguration>>(|| None);
242 {
243 let mut prev = prev.write();
244 if prev.as_ref() != Some(asset_config) {
245 if let Some(prev) = &*prev
246 && prev != asset_config
247 {
248 asset_cacher.try_clean(asset_config);
250 }
251 prev.replace(asset_config.clone());
252 }
253 asset_cacher.listen(ReactiveContext::current(), asset_config.clone());
254 }
255
256 asset_cacher
257 .read_asset(asset_config)
258 .expect("Asset should be be cached by now.")
259}