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