1use core::fmt;
2use dioxus::prelude::*;
3use dioxus::{
4 hooks::{use_memo, use_reactive},
5 signals::CopyValue,
6};
7use dioxus_core::{provide_root_context, spawn_forever, use_drop, ReactiveContext, Task};
8use std::{
9 cell::{Ref, RefCell},
10 collections::{HashMap, HashSet},
11 future::Future,
12 hash::Hash,
13 mem,
14 rc::Rc,
15 sync::{Arc, Mutex},
16 time::Duration,
17};
18#[cfg(not(target_family = "wasm"))]
19use tokio::time;
20#[cfg(not(target_family = "wasm"))]
21use tokio::time::Instant;
22#[cfg(target_family = "wasm")]
23use wasmtimer::tokio as time;
24#[cfg(target_family = "wasm")]
25use web_time::Instant;
26
27pub trait MutationCapability
28where
29 Self: 'static + Clone + PartialEq + Hash + Eq,
30{
31 type Ok;
32 type Err;
33 type Keys: Hash + PartialEq + Clone;
34
35 fn run(&self, keys: &Self::Keys) -> impl Future<Output = Result<Self::Ok, Self::Err>>;
37
38 fn matches(&self, _keys: &Self::Keys) -> bool {
40 true
41 }
42
43 fn on_settled(
46 &self,
47 _keys: &Self::Keys,
48 _result: &Result<Self::Ok, Self::Err>,
49 ) -> impl Future<Output = ()> {
50 async {}
51 }
52}
53
54pub enum MutationStateData<Q: MutationCapability> {
55 Pending,
57 Loading { res: Option<Result<Q::Ok, Q::Err>> },
59 Settled {
61 res: Result<Q::Ok, Q::Err>,
62 settlement_instant: Instant,
63 },
64}
65
66impl<Q> fmt::Debug for MutationStateData<Q>
67where
68 Q: MutationCapability,
69 Q::Ok: fmt::Debug,
70 Q::Err: fmt::Debug,
71{
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 match self {
74 Self::Pending => f.write_str("Pending"),
75 Self::Loading { res } => write!(f, "Loading {{ {res:?} }}"),
76 Self::Settled { res, .. } => write!(f, "Settled {{ {res:?} }}"),
77 }
78 }
79}
80
81impl<Q: MutationCapability> MutationStateData<Q> {
82 pub fn is_ok(&self) -> bool {
84 matches!(self, MutationStateData::Settled { res: Ok(_), .. })
85 }
86
87 pub fn is_err(&self) -> bool {
89 matches!(self, MutationStateData::Settled { res: Err(_), .. })
90 }
91
92 pub fn is_loading(&self) -> bool {
94 matches!(self, MutationStateData::Loading { .. })
95 }
96
97 pub fn is_pending(&self) -> bool {
99 matches!(self, MutationStateData::Pending)
100 }
101
102 pub fn ok(&self) -> Option<&Q::Ok> {
104 match self {
105 Self::Settled { res: Ok(res), .. } => Some(res),
106 Self::Loading { res: Some(Ok(res)) } => Some(res),
107 _ => None,
108 }
109 }
110
111 pub fn unwrap(&self) -> &Result<Q::Ok, Q::Err> {
113 match self {
114 Self::Loading { res: Some(v) } => v,
115 Self::Settled { res, .. } => res,
116 _ => unreachable!(),
117 }
118 }
119
120 fn into_loading(self) -> MutationStateData<Q> {
121 match self {
122 MutationStateData::Pending => MutationStateData::Loading { res: None },
123 MutationStateData::Loading { res } => MutationStateData::Loading { res },
124 MutationStateData::Settled { res, .. } => MutationStateData::Loading { res: Some(res) },
125 }
126 }
127}
128pub struct MutationsStorage<Q: MutationCapability> {
129 storage: CopyValue<HashMap<Mutation<Q>, MutationData<Q>>>,
130}
131
132impl<Q: MutationCapability> Copy for MutationsStorage<Q> {}
133
134impl<Q: MutationCapability> Clone for MutationsStorage<Q> {
135 fn clone(&self) -> Self {
136 *self
137 }
138}
139
140pub struct MutationData<Q: MutationCapability> {
141 state: Rc<RefCell<MutationStateData<Q>>>,
142 reactive_contexts: Arc<Mutex<HashSet<ReactiveContext>>>,
143
144 clean_task: Rc<RefCell<Option<Task>>>,
145}
146
147impl<Q: MutationCapability> Clone for MutationData<Q> {
148 fn clone(&self) -> Self {
149 Self {
150 state: self.state.clone(),
151 reactive_contexts: self.reactive_contexts.clone(),
152 clean_task: self.clean_task.clone(),
153 }
154 }
155}
156
157impl<Q: MutationCapability> MutationsStorage<Q> {
158 fn new_in_root() -> Self {
159 Self {
160 storage: CopyValue::new_in_scope(HashMap::default(), ScopeId::ROOT),
161 }
162 }
163
164 fn insert_or_get_mutation(&mut self, mutation: Mutation<Q>) -> MutationData<Q> {
165 let mut storage = self.storage.write();
166
167 let mutation_data = storage.entry(mutation).or_insert_with(|| MutationData {
168 state: Rc::new(RefCell::new(MutationStateData::Pending)),
169 reactive_contexts: Arc::default(),
170 clean_task: Rc::default(),
171 });
172
173 if let Some(clean_task) = mutation_data.clean_task.take() {
175 clean_task.cancel();
176 }
177
178 mutation_data.clone()
179 }
180
181 fn update_tasks(&mut self, mutation: Mutation<Q>) {
182 let mut storage_clone = self.storage;
183 let mut storage = self.storage.write();
184
185 let mutation_data = storage.get_mut(&mutation).unwrap();
186
187 if mutation_data.reactive_contexts.lock().unwrap().is_empty() {
189 *mutation_data.clean_task.borrow_mut() = Some(spawn_forever(async move {
190 time::sleep(mutation.clean_time).await;
192
193 let mut storage = storage_clone.write();
195 storage.remove(&mutation);
196 }));
197 }
198 }
199
200 async fn run(mutation: &Mutation<Q>, data: &MutationData<Q>, keys: Q::Keys) {
201 let res =
203 mem::replace(&mut *data.state.borrow_mut(), MutationStateData::Pending).into_loading();
204 *data.state.borrow_mut() = res;
205 for reactive_context in data.reactive_contexts.lock().unwrap().iter() {
206 reactive_context.mark_dirty();
207 }
208
209 let res = mutation.mutation.run(&keys).await;
211
212 mutation.mutation.on_settled(&keys, &res).await;
214 *data.state.borrow_mut() = MutationStateData::Settled {
215 res,
216 settlement_instant: Instant::now(),
217 };
218 for reactive_context in data.reactive_contexts.lock().unwrap().iter() {
219 reactive_context.mark_dirty();
220 }
221 }
222}
223
224#[derive(PartialEq, Clone)]
225pub struct Mutation<Q: MutationCapability> {
226 mutation: Q,
227
228 clean_time: Duration,
229}
230
231impl<Q: MutationCapability> Eq for Mutation<Q> {}
232impl<Q: MutationCapability> Hash for Mutation<Q> {
233 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
234 self.mutation.hash(state);
235 }
236}
237
238impl<Q: MutationCapability> Mutation<Q> {
239 pub fn new(mutation: Q) -> Self {
240 Self {
241 mutation,
242 clean_time: Duration::ZERO,
243 }
244 }
245
246 pub fn clean_time(self, clean_time: Duration) -> Self {
250 Self { clean_time, ..self }
251 }
252}
253
254pub struct MutationReader<Q: MutationCapability> {
255 state: Rc<RefCell<MutationStateData<Q>>>,
256}
257
258impl<Q: MutationCapability> MutationReader<Q> {
259 pub fn state(&self) -> Ref<MutationStateData<Q>> {
260 self.state.borrow()
261 }
262}
263
264pub struct UseMutation<Q: MutationCapability> {
265 mutation: Memo<Mutation<Q>>,
266}
267
268impl<Q: MutationCapability> Clone for UseMutation<Q> {
269 fn clone(&self) -> Self {
270 *self
271 }
272}
273
274impl<Q: MutationCapability> Copy for UseMutation<Q> {}
275
276impl<Q: MutationCapability> UseMutation<Q> {
277 pub fn read(&self) -> MutationReader<Q> {
282 let storage = consume_context::<MutationsStorage<Q>>();
283 let mutation_data = storage
284 .storage
285 .peek_unchecked()
286 .get(&self.mutation.peek())
287 .cloned()
288 .unwrap();
289
290 if let Some(reactive_context) = ReactiveContext::current() {
292 reactive_context.subscribe(mutation_data.reactive_contexts);
293 }
294
295 MutationReader {
296 state: mutation_data.state,
297 }
298 }
299
300 pub fn peek(&self) -> MutationReader<Q> {
305 let storage = consume_context::<MutationsStorage<Q>>();
306 let mutation_data = storage
307 .storage
308 .peek_unchecked()
309 .get(&self.mutation.peek())
310 .cloned()
311 .unwrap();
312
313 MutationReader {
314 state: mutation_data.state,
315 }
316 }
317
318 pub async fn mutate_async(&self, keys: Q::Keys) -> MutationReader<Q> {
322 let storage = consume_context::<MutationsStorage<Q>>();
323
324 let mutation = self.mutation.peek().clone();
325 let mutation_data = storage
326 .storage
327 .peek_unchecked()
328 .get(&mutation)
329 .cloned()
330 .unwrap();
331
332 MutationsStorage::run(&mutation, &mutation_data, keys).await;
334
335 MutationReader {
336 state: mutation_data.state,
337 }
338 }
339
340 pub fn mutate(&self, keys: Q::Keys) {
344 let storage = consume_context::<MutationsStorage<Q>>();
345
346 let mutation = self.mutation.peek().clone();
347 let mutation_data = storage
348 .storage
349 .peek_unchecked()
350 .get(&mutation)
351 .cloned()
352 .unwrap();
353
354 spawn(async move {
356 MutationsStorage::run(&mutation, &mutation_data, keys).await;
357 });
358 }
359}
360
361pub fn use_mutation<Q: MutationCapability>(mutation: Mutation<Q>) -> UseMutation<Q> {
368 let mut storage = match try_consume_context::<MutationsStorage<Q>>() {
369 Some(storage) => storage,
370 None => provide_root_context(MutationsStorage::<Q>::new_in_root()),
371 };
372
373 let current_mutation = use_hook(|| Rc::new(RefCell::new(None)));
374
375 let mutation = use_memo(use_reactive!(|mutation| {
377 let _data = storage.insert_or_get_mutation(mutation.clone());
378
379 if let Some(prev_mutation) = current_mutation.borrow_mut().take() {
381 storage.update_tasks(prev_mutation);
382 }
383
384 current_mutation.borrow_mut().replace(mutation.clone());
386
387 mutation
388 }));
389
390 use_drop({
392 move || {
393 storage.update_tasks(mutation.peek().clone());
394 }
395 });
396
397 UseMutation { mutation }
398}