freya_hooks/
use_asset_cacher.rs1use std::{
2 collections::{
3 HashMap,
4 HashSet,
5 },
6 time::Duration,
7};
8
9use bytes::Bytes;
10use dioxus_core::{
11 prelude::{
12 current_scope_id,
13 spawn_forever,
14 ScopeId,
15 Task,
16 },
17 schedule_update_any,
18};
19use dioxus_hooks::{
20 use_context,
21 use_context_provider,
22};
23use dioxus_signals::{
24 Readable,
25 Signal,
26 Writable,
27};
28use tokio::time::sleep;
29use tracing::info;
30
31#[derive(Hash, PartialEq, Eq, Clone)]
34pub enum AssetAge {
35 Duration(Duration),
37 Unspecified,
39}
40
41impl Default for AssetAge {
42 fn default() -> Self {
43 Self::Duration(Duration::from_secs(3600)) }
45}
46
47impl From<Duration> for AssetAge {
48 fn from(value: Duration) -> Self {
49 Self::Duration(value)
50 }
51}
52
53#[derive(Hash, PartialEq, Eq, Clone)]
55pub struct AssetConfiguration {
56 pub age: AssetAge,
58 pub id: String,
60}
61
62enum AssetUsers {
63 Scopes(HashSet<ScopeId>),
64 ClearTask(Task),
65}
66
67struct AssetState {
68 users: AssetUsers,
69 asset_bytes: Bytes,
70}
71
72#[derive(Clone, Copy, Default)]
73pub struct AssetCacher {
74 registry: Signal<HashMap<AssetConfiguration, AssetState>>,
75}
76
77impl AssetCacher {
78 pub fn cache_asset(
80 &mut self,
81 asset_config: AssetConfiguration,
82 asset_bytes: Bytes,
83 subscribe: bool,
84 ) {
85 if let Some(asset_state) = self.registry.write().remove(&asset_config) {
87 if let AssetUsers::ClearTask(task) = asset_state.users {
88 task.cancel();
89 info!("Clear task of asset with ID '{}' has been cancelled as the asset has been revalidated", asset_config.id);
90 }
91 }
92
93 let current_scope_id = current_scope_id().unwrap();
95
96 self.registry.write().insert(
97 asset_config.clone(),
98 AssetState {
99 asset_bytes,
100 users: AssetUsers::Scopes(if subscribe {
101 HashSet::from([current_scope_id])
102 } else {
103 HashSet::default()
104 }),
105 },
106 );
107
108 schedule_update_any()(current_scope_id);
109 }
110
111 pub fn unuse_asset(&mut self, asset_config: AssetConfiguration) {
113 let mut registry = self.registry;
114
115 let spawn_clear_task = {
116 let mut registry = registry.write();
117
118 let entry = registry.get_mut(&asset_config);
119 if let Some(asset_state) = entry {
120 match &mut asset_state.users {
121 AssetUsers::Scopes(scopes) => {
122 scopes.remove(¤t_scope_id().unwrap());
124
125 scopes.is_empty()
127 }
128 AssetUsers::ClearTask(task) => {
129 task.cancel();
131 true
132 }
133 }
134 } else {
135 false
136 }
137 };
138
139 if spawn_clear_task {
140 if let AssetAge::Duration(duration) = asset_config.age {
142 let clear_task = spawn_forever({
143 let asset_config = asset_config.clone();
144 async move {
145 info!("Waiting asset with ID '{}' to be cleared", asset_config.id);
146 sleep(duration).await;
147 registry.write().remove(&asset_config);
148 info!("Cleared asset with ID '{}'", asset_config.id);
149 }
150 })
151 .unwrap();
152
153 let mut registry = registry.write();
155 let entry = registry.get_mut(&asset_config).unwrap();
156 entry.users = AssetUsers::ClearTask(clear_task);
157 }
158 }
159 }
160
161 pub fn use_asset(&mut self, asset_config: &AssetConfiguration) -> Option<Bytes> {
163 let mut registry = self.registry.write();
164 if let Some(asset_state) = registry.get_mut(asset_config) {
165 match &mut asset_state.users {
166 AssetUsers::ClearTask(task) => {
167 task.cancel();
169 info!(
170 "Clear task of asset with ID '{}' has been cancelled",
171 asset_config.id
172 );
173
174 asset_state.users =
176 AssetUsers::Scopes(HashSet::from([current_scope_id().unwrap()]));
177 }
178 AssetUsers::Scopes(scopes) => {
179 scopes.insert(current_scope_id().unwrap());
181 }
182 }
183
184 if let AssetUsers::Scopes(scopes) = &asset_state.users {
186 let schedule = schedule_update_any();
187 for scope in scopes {
188 schedule(*scope);
189 }
190 info!(
191 "Reran {} scopes subscribed to asset with id '{}'",
192 scopes.len(),
193 asset_config.id
194 );
195 }
196 }
197
198 registry.get(asset_config).map(|s| s.asset_bytes.clone())
199 }
200
201 pub fn size(&self) -> usize {
203 self.registry.read().len()
204 }
205}
206
207pub fn use_asset_cacher() -> AssetCacher {
209 use_context()
210}
211
212pub fn use_init_asset_cacher() {
216 use_context_provider(AssetCacher::default);
217}