bounce/query/
mutation_states.rs

1use std::cell::RefCell;
2use std::collections::hash_map::Entry;
3use std::collections::HashMap;
4use std::rc::Rc;
5
6use yew::platform::pinned::oneshot;
7use yew::prelude::*;
8
9use super::traits::{Mutation, MutationResult};
10use crate::future_notion;
11use crate::root_state::BounceStates;
12use crate::states::future_notion::Deferred;
13use crate::states::input_selector::InputSelector;
14use crate::states::notion::WithNotion;
15use crate::states::slice::Slice;
16use crate::utils::Id;
17
18// We create 2 ID types to better distinguish them in code.
19#[derive(Default, PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord, Copy)]
20pub(super) struct HandleId(Id);
21
22#[derive(Default, PartialEq, Debug, Clone, Eq, Hash, PartialOrd, Ord, Copy)]
23pub(super) struct MutationId(Id);
24
25#[derive(PartialEq, Debug)]
26pub(super) enum MutationSliceValue<T>
27where
28    T: Mutation + 'static,
29{
30    Idle,
31    Loading {
32        id: MutationId,
33    },
34    Completed {
35        id: MutationId,
36        result: MutationResult<T>,
37    },
38    Outdated {
39        id: MutationId,
40        result: MutationResult<T>,
41    },
42}
43
44impl<T> Clone for MutationSliceValue<T>
45where
46    T: Mutation + 'static,
47{
48    fn clone(&self) -> Self {
49        match self {
50            Self::Idle => Self::Idle,
51            Self::Loading { id } => Self::Loading { id: *id },
52            Self::Completed { id, result } => Self::Completed {
53                id: *id,
54                result: result.clone(),
55            },
56            Self::Outdated { id, result } => Self::Outdated {
57                id: *id,
58                result: result.clone(),
59            },
60        }
61    }
62}
63
64pub(super) struct RunMutationInput<T>
65where
66    T: Mutation,
67{
68    pub handle_id: HandleId,
69    pub mutation_id: MutationId,
70    pub input: Rc<T::Input>,
71    pub sender: RefCell<Option<oneshot::Sender<MutationResult<T>>>>,
72}
73
74#[future_notion(RunMutation)]
75pub(super) async fn run_mutation<T>(
76    states: &BounceStates,
77    input: &RunMutationInput<T>,
78) -> MutationResult<T>
79where
80    T: Mutation + 'static,
81{
82    let result = T::run(states, input.input.clone()).await;
83
84    if let Some(m) = input.sender.borrow_mut().take() {
85        let _result = m.send(result.clone());
86    }
87
88    result
89}
90
91pub(super) enum MutationSliceAction {
92    /// Start tracking a handle.
93    Create(HandleId),
94    /// Stop tracking a handle.
95    Destroy(HandleId),
96}
97
98#[derive(Slice, Debug)]
99#[bounce(with_notion(Deferred<RunMutation<T>>))]
100pub(super) struct MutationSlice<T>
101where
102    T: Mutation + 'static,
103{
104    ctr: u64,
105    mutations: HashMap<HandleId, MutationSliceValue<T>>,
106}
107
108impl<T> PartialEq for MutationSlice<T>
109where
110    T: Mutation + 'static,
111{
112    fn eq(&self, rhs: &Self) -> bool {
113        self.ctr == rhs.ctr
114    }
115}
116
117impl<T> Default for MutationSlice<T>
118where
119    T: Mutation + 'static,
120{
121    fn default() -> Self {
122        Self {
123            ctr: 0,
124            mutations: HashMap::new(),
125        }
126    }
127}
128
129impl<T> Clone for MutationSlice<T>
130where
131    T: Mutation + 'static,
132{
133    fn clone(&self) -> Self {
134        Self {
135            ctr: self.ctr,
136            mutations: self.mutations.clone(),
137        }
138    }
139}
140
141impl<T> Reducible for MutationSlice<T>
142where
143    T: Mutation + 'static,
144{
145    type Action = MutationSliceAction;
146
147    fn reduce(mut self: Rc<Self>, action: Self::Action) -> Rc<Self> {
148        {
149            let this = Rc::make_mut(&mut self);
150            // we don't increase the counter here as there's nothing to update.
151
152            match action {
153                Self::Action::Create(id) => {
154                    this.mutations.insert(id, MutationSliceValue::Idle);
155                }
156
157                Self::Action::Destroy(id) => {
158                    this.mutations.remove(&id);
159                }
160            }
161        }
162
163        self
164    }
165}
166
167impl<T> WithNotion<Deferred<RunMutation<T>>> for MutationSlice<T>
168where
169    T: Mutation + 'static,
170{
171    fn apply(mut self: Rc<Self>, notion: Rc<Deferred<RunMutation<T>>>) -> Rc<Self> {
172        match notion.as_ref() {
173            Deferred::Completed {
174                ref input,
175                ref output,
176            } => {
177                let this = Rc::make_mut(&mut self);
178                this.ctr += 1;
179
180                match this.mutations.entry(input.handle_id) {
181                    Entry::Vacant(_m) => {
182                        return self; // The handle has been destroyed so there's no need to track it any more.
183                    }
184                    Entry::Occupied(mut m) => {
185                        let m = m.get_mut();
186                        match m {
187                            MutationSliceValue::Loading { id }
188                            | MutationSliceValue::Completed { id, .. }
189                            | MutationSliceValue::Outdated { id, .. } => {
190                                // only replace if new id is higher.
191                                if *id <= input.mutation_id {
192                                    *m = MutationSliceValue::Completed {
193                                        id: input.mutation_id,
194                                        result: output.as_ref().clone(),
195                                    };
196                                }
197                            }
198                            MutationSliceValue::Idle => {
199                                *m = MutationSliceValue::Completed {
200                                    id: input.mutation_id,
201                                    result: output.as_ref().clone(),
202                                };
203                            }
204                        }
205                    }
206                }
207            }
208            Deferred::Pending { input } => {
209                let this = Rc::make_mut(&mut self);
210                this.ctr += 1;
211
212                match this.mutations.entry(input.handle_id) {
213                    Entry::Vacant(_m) => {
214                        return self; // The handle has been destroyed so there's no need to track it any more.
215                    }
216                    Entry::Occupied(mut m) => {
217                        let m = m.get_mut();
218                        match m {
219                            MutationSliceValue::Loading { .. } => {}
220                            MutationSliceValue::Completed { id, result } => {
221                                *m = MutationSliceValue::Outdated {
222                                    id: *id,
223                                    result: result.clone(),
224                                };
225                            }
226                            MutationSliceValue::Outdated { .. } => {}
227                            MutationSliceValue::Idle => {
228                                *m = MutationSliceValue::Loading {
229                                    id: input.mutation_id,
230                                };
231                            }
232                        }
233                    }
234                }
235            }
236            Deferred::Outdated { .. } => {}
237        }
238
239        self
240    }
241}
242
243#[derive(PartialEq)]
244pub(super) struct MutationSelector<T>
245where
246    T: Mutation + 'static,
247{
248    pub id: Option<MutationId>,
249    pub value: Option<MutationSliceValue<T>>,
250}
251
252impl<T> InputSelector for MutationSelector<T>
253where
254    T: Mutation + 'static,
255{
256    type Input = HandleId;
257    fn select(states: &BounceStates, input: Rc<HandleId>) -> Rc<Self> {
258        let value = states
259            .get_slice_value::<MutationSlice<T>>()
260            .mutations
261            .get(&input)
262            .cloned();
263
264        let id = value.as_ref().and_then(|m| match m {
265            MutationSliceValue::Loading { id }
266            | MutationSliceValue::Completed { id, .. }
267            | MutationSliceValue::Outdated { id, .. } => Some(*id),
268            MutationSliceValue::Idle => None,
269        });
270
271        Self { id, value }.into()
272    }
273}