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