1use core::fmt;
2use std::{
3 cell::{
4 Ref,
5 RefCell,
6 },
7 collections::HashMap,
8 future::Future,
9 hash::Hash,
10 mem,
11 rc::Rc,
12 time::{
13 Duration,
14 Instant,
15 },
16};
17
18use async_io::Timer;
19use freya_core::{
20 integration::FxHashSet,
21 lifecycle::context::{
22 consume_context,
23 provide_context_for_scope_id,
24 try_consume_context,
25 },
26 prelude::*,
27 scope_id::ScopeId,
28};
29
30pub trait MutationCapability
31where
32 Self: 'static + Clone + PartialEq + Hash + Eq,
33{
34 type Ok;
35 type Err;
36 type Keys: Hash + PartialEq + Clone;
37
38 fn run(&self, keys: &Self::Keys) -> impl Future<Output = Result<Self::Ok, Self::Err>>;
40
41 fn matches(&self, _keys: &Self::Keys) -> bool {
43 true
44 }
45
46 fn on_settled(
49 &self,
50 _keys: &Self::Keys,
51 _result: &Result<Self::Ok, Self::Err>,
52 ) -> impl Future<Output = ()> {
53 async {}
54 }
55}
56
57pub enum MutationStateData<Q: MutationCapability> {
58 Pending,
60 Loading { res: Option<Result<Q::Ok, Q::Err>> },
62 Settled {
64 res: Result<Q::Ok, Q::Err>,
65 settlement_instant: Instant,
66 },
67}
68
69impl<Q> fmt::Debug for MutationStateData<Q>
70where
71 Q: MutationCapability,
72 Q::Ok: fmt::Debug,
73 Q::Err: fmt::Debug,
74{
75 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76 match self {
77 Self::Pending => f.write_str("Pending"),
78 Self::Loading { res } => write!(f, "Loading {{ {res:?} }}"),
79 Self::Settled { res, .. } => write!(f, "Settled {{ {res:?} }}"),
80 }
81 }
82}
83
84impl<Q: MutationCapability> MutationStateData<Q> {
85 pub fn is_ok(&self) -> bool {
87 matches!(self, MutationStateData::Settled { res: Ok(_), .. })
88 }
89
90 pub fn is_err(&self) -> bool {
92 matches!(self, MutationStateData::Settled { res: Err(_), .. })
93 }
94
95 pub fn is_loading(&self) -> bool {
97 matches!(self, MutationStateData::Loading { .. })
98 }
99
100 pub fn is_pending(&self) -> bool {
102 matches!(self, MutationStateData::Pending)
103 }
104
105 pub fn ok(&self) -> Option<&Q::Ok> {
107 match self {
108 Self::Settled { res: Ok(res), .. } => Some(res),
109 Self::Loading { res: Some(Ok(res)) } => Some(res),
110 _ => None,
111 }
112 }
113
114 pub fn unwrap(&self) -> &Result<Q::Ok, Q::Err> {
116 match self {
117 Self::Loading { res: Some(v) } => v,
118 Self::Settled { res, .. } => res,
119 _ => unreachable!(),
120 }
121 }
122
123 fn into_loading(self) -> MutationStateData<Q> {
124 match self {
125 MutationStateData::Pending => MutationStateData::Loading { res: None },
126 MutationStateData::Loading { res } => MutationStateData::Loading { res },
127 MutationStateData::Settled { res, .. } => MutationStateData::Loading { res: Some(res) },
128 }
129 }
130}
131pub struct MutationsStorage<Q: MutationCapability> {
132 storage: State<HashMap<Mutation<Q>, MutationData<Q>>>,
133}
134
135impl<Q: MutationCapability> Copy for MutationsStorage<Q> {}
136
137impl<Q: MutationCapability> Clone for MutationsStorage<Q> {
138 fn clone(&self) -> Self {
139 *self
140 }
141}
142
143pub struct MutationData<Q: MutationCapability> {
144 state: Rc<RefCell<MutationStateData<Q>>>,
145 reactive_contexts: Rc<RefCell<FxHashSet<ReactiveContext>>>,
146
147 clean_task: Rc<RefCell<Option<TaskHandle>>>,
148}
149
150impl<Q: MutationCapability> Clone for MutationData<Q> {
151 fn clone(&self) -> Self {
152 Self {
153 state: self.state.clone(),
154 reactive_contexts: self.reactive_contexts.clone(),
155 clean_task: self.clean_task.clone(),
156 }
157 }
158}
159
160impl<Q: MutationCapability> MutationsStorage<Q> {
161 fn new_in_root() -> Self {
162 Self {
163 storage: State::create_global(HashMap::default()),
164 }
165 }
166
167 fn insert_or_get_mutation(&mut self, mutation: Mutation<Q>) -> MutationData<Q> {
168 let mut storage = self.storage.write_unchecked();
169
170 let mutation_data = storage.entry(mutation).or_insert_with(|| MutationData {
171 state: Rc::new(RefCell::new(MutationStateData::Pending)),
172 reactive_contexts: Rc::new(RefCell::new(FxHashSet::default())),
173 clean_task: Rc::default(),
174 });
175
176 if let Some(clean_task) = mutation_data.clean_task.take() {
178 clean_task.cancel();
179 }
180
181 mutation_data.clone()
182 }
183
184 fn update_tasks(&mut self, mutation: Mutation<Q>) {
185 let storage_clone = self.storage;
186 let mut storage = self.storage.write_unchecked();
187
188 let mutation_data = storage.get_mut(&mutation).unwrap();
189
190 if mutation_data.reactive_contexts.borrow().is_empty() {
192 *mutation_data.clean_task.borrow_mut() = Some(spawn_forever(async move {
193 Timer::after(mutation.clean_time).await;
195
196 let mut storage = storage_clone.write_unchecked();
198 storage.remove(&mutation);
199 }));
200 }
201 }
202
203 async fn run(mutation: &Mutation<Q>, data: &MutationData<Q>, keys: Q::Keys) {
204 let res =
206 mem::replace(&mut *data.state.borrow_mut(), MutationStateData::Pending).into_loading();
207 *data.state.borrow_mut() = res;
208 for reactive_context in data.reactive_contexts.borrow().iter() {
209 reactive_context.notify();
210 }
211
212 let res = mutation.mutation.run(&keys).await;
214
215 mutation.mutation.on_settled(&keys, &res).await;
217 *data.state.borrow_mut() = MutationStateData::Settled {
218 res,
219 settlement_instant: Instant::now(),
220 };
221 for reactive_context in data.reactive_contexts.borrow().iter() {
222 reactive_context.notify();
223 }
224 }
225}
226
227#[derive(PartialEq, Clone)]
228pub struct Mutation<Q: MutationCapability> {
229 mutation: Q,
230
231 clean_time: Duration,
232}
233
234impl<Q: MutationCapability> Eq for Mutation<Q> {}
235impl<Q: MutationCapability> Hash for Mutation<Q> {
236 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
237 self.mutation.hash(state);
238 }
239}
240
241impl<Q: MutationCapability> Mutation<Q> {
242 pub fn new(mutation: Q) -> Self {
243 Self {
244 mutation,
245 clean_time: Duration::ZERO,
246 }
247 }
248
249 pub fn clean_time(self, clean_time: Duration) -> Self {
253 Self { clean_time, ..self }
254 }
255}
256
257pub struct MutationReader<Q: MutationCapability> {
258 state: Rc<RefCell<MutationStateData<Q>>>,
259}
260
261impl<Q: MutationCapability> MutationReader<Q> {
262 pub fn state(&'_ self) -> Ref<'_, MutationStateData<Q>> {
263 self.state.borrow()
264 }
265}
266
267pub struct UseMutation<Q: MutationCapability> {
268 mutation: State<Mutation<Q>>,
269}
270
271impl<Q: MutationCapability> Clone for UseMutation<Q> {
272 fn clone(&self) -> Self {
273 *self
274 }
275}
276
277impl<Q: MutationCapability> Copy for UseMutation<Q> {}
278
279impl<Q: MutationCapability> UseMutation<Q> {
280 pub fn read(&self) -> MutationReader<Q> {
285 let storage = consume_context::<MutationsStorage<Q>>();
286 let map = storage.storage.peek();
287 let mutation_data = map.get(&self.mutation.peek()).cloned().unwrap();
288
289 if let Some(mut reactive_context) = ReactiveContext::try_current() {
291 reactive_context.subscribe(&mutation_data.reactive_contexts);
292 }
293
294 MutationReader {
295 state: mutation_data.state,
296 }
297 }
298
299 pub fn peek(&self) -> MutationReader<Q> {
304 let storage = consume_context::<MutationsStorage<Q>>();
305 let map = storage.storage.peek();
306 let mutation_data = map.get(&self.mutation.peek()).cloned().unwrap();
307
308 MutationReader {
309 state: mutation_data.state,
310 }
311 }
312
313 pub async fn mutate_async(&self, keys: Q::Keys) -> MutationReader<Q> {
317 let storage = consume_context::<MutationsStorage<Q>>();
318
319 let mutation = self.mutation.peek().clone();
320 let map = storage.storage.peek();
321 let mutation_data = map.get(&mutation).cloned().unwrap();
322
323 MutationsStorage::run(&mutation, &mutation_data, keys).await;
325
326 MutationReader {
327 state: mutation_data.state,
328 }
329 }
330
331 pub fn mutate(&self, keys: Q::Keys) {
335 let storage = consume_context::<MutationsStorage<Q>>();
336
337 let mutation = self.mutation.peek().clone();
338 let map = storage.storage.peek();
339 let mutation_data = map.get(&mutation).cloned().unwrap();
340
341 spawn(async move { MutationsStorage::run(&mutation, &mutation_data, keys).await });
343 }
344}
345
346pub fn use_mutation<Q: MutationCapability>(mutation: Mutation<Q>) -> UseMutation<Q> {
353 let mut storage = match try_consume_context::<MutationsStorage<Q>>() {
354 Some(storage) => storage,
355 None => {
356 provide_context_for_scope_id(MutationsStorage::<Q>::new_in_root(), Some(ScopeId::ROOT));
357 try_consume_context::<MutationsStorage<Q>>().unwrap()
358 }
359 };
360
361 let mut make_mutation = |mutation: &Mutation<Q>, mut prev_mutation: Option<Mutation<Q>>| {
362 let _data = storage.insert_or_get_mutation(mutation.clone());
363
364 if let Some(prev_mutation) = prev_mutation.take() {
366 storage.update_tasks(prev_mutation);
367 }
368 };
369
370 let mut current_mutation = use_hook(|| {
371 make_mutation(&mutation, None);
372 State::create(mutation.clone())
373 });
374
375 if *current_mutation.read() != mutation {
376 let prev = mem::replace(&mut *current_mutation.write(), mutation.clone());
377 make_mutation(&mutation, Some(prev));
378 }
379
380 use_drop({
382 move || {
383 storage.update_tasks(current_mutation.peek().clone());
384 }
385 });
386
387 UseMutation {
388 mutation: current_mutation,
389 }
390}