redux/
store.rs

1use std::sync::OnceLock;
2
3use crate::{
4    ActionId, ActionMeta, ActionWithMeta, AnyAction, Callback, Dispatcher, Effects,
5    EnablingCondition, Instant, Reducer, SubStore, SystemTime, TimeService, Timestamp,
6};
7
8/// Wraps around State and allows only immutable borrow,
9/// Through `StateWrapper::get` method.
10///
11/// Mutable borrow of state can only happen in reducer.
12pub struct StateWrapper<State> {
13    inner: State,
14}
15
16impl<State> StateWrapper<State> {
17    /// Get immutable reference to State.
18    #[inline(always)]
19    pub fn get(&self) -> &State {
20        &self.inner
21    }
22
23    /// Get mutable reference to State.
24    ///
25    /// Only should be used in the reducer and it's not `pub`,
26    /// so it can't be accessed from lib users.
27    #[inline(always)]
28    fn get_mut(&mut self) -> &mut State {
29        &mut self.inner
30    }
31}
32
33impl<T: Clone> Clone for StateWrapper<T> {
34    fn clone(&self) -> Self {
35        Self {
36            inner: self.inner.clone(),
37        }
38    }
39}
40
41/// Monotonic and system time reference points.
42static INITIAL_TIME: OnceLock<(Instant, SystemTime)> = OnceLock::new();
43
44/// Converts monotonic time to nanoseconds since Unix epoch.
45///
46/// If `None` passed, returns result for current time.
47pub fn monotonic_to_time(time: Option<Instant>) -> u64 {
48    let (monotonic, system) = INITIAL_TIME.get_or_init(|| (Instant::now(), SystemTime::now()));
49    let time_passed = time.unwrap_or_else(Instant::now).duration_since(*monotonic);
50    system
51        .duration_since(SystemTime::UNIX_EPOCH)
52        .map(|x| x + time_passed)
53        .map(|x| x.as_nanos())
54        .unwrap_or(0) as u64
55}
56
57/// Main struct for the state machine.
58///
59/// Exposes a [`Store::dispatch`] method, using
60/// which actions can be dispatched, which triggers a
61/// 1. [`Reducer`] - to update the state.
62/// 2. [`Effects`] - to trigger side-effects of the action.
63pub struct Store<State, Service, Action> {
64    reducer: Reducer<State, Action>,
65    effects: Effects<State, Service, Action>,
66
67    /// Current State.
68    ///
69    /// Immutable access can be gained using `store.state.get()`.
70    /// Mutation can only happen inside reducer.
71    pub state: StateWrapper<State>,
72    pub service: Service,
73
74    initial_monotonic_time: Instant,
75    initial_time: Timestamp,
76
77    /// Current recursion depth of dispatch.
78    recursion_depth: u32,
79
80    last_action_id: ActionId,
81}
82
83impl<State, Service, Action> Store<State, Service, Action>
84where
85    Service: TimeService,
86    Action: EnablingCondition<State>,
87{
88    /// Creates a new store.
89    pub fn new(
90        reducer: Reducer<State, Action>,
91        effects: Effects<State, Service, Action>,
92        mut service: Service,
93        initial_time: SystemTime,
94        initial_state: State,
95    ) -> Self {
96        let initial_monotonic_time = service.monotonic_time();
97        let initial_time_nanos = initial_time
98            .duration_since(SystemTime::UNIX_EPOCH)
99            .map(|x| x.as_nanos())
100            .unwrap_or(0);
101
102        INITIAL_TIME.get_or_init(move || (initial_monotonic_time, initial_time));
103
104        Self {
105            reducer,
106            effects,
107            service,
108            state: StateWrapper {
109                inner: initial_state,
110            },
111
112            initial_monotonic_time,
113            initial_time: Timestamp::new(initial_time_nanos as u64),
114
115            recursion_depth: 0,
116            last_action_id: ActionId::new_unchecked(initial_time_nanos as u64),
117        }
118    }
119
120    /// Returns the current state.
121    #[inline(always)]
122    pub fn state(&self) -> &State {
123        self.state.get()
124    }
125
126    #[inline(always)]
127    pub fn service(&mut self) -> &mut Service {
128        &mut self.service
129    }
130
131    /// Convert monotonic time to system clock in nanoseconds from epoch.
132    pub fn monotonic_to_time(&self, monotonic_time: Instant) -> u64 {
133        monotonic_to_time(Some(monotonic_time))
134    }
135
136    /// Dispatch an Action.
137    ///
138    /// Returns `true` if the action was enabled, hence if it was dispatched
139    /// to reducer and then effects.
140    ///
141    /// If action is not enabled, we return false and do nothing.
142    pub fn dispatch<T>(&mut self, action: T) -> bool
143    where
144        T: Into<Action> + EnablingCondition<State>,
145    {
146        if !action.is_enabled(self.state(), self.last_action_id.into()) {
147            return false;
148        }
149        self.dispatch_enabled(action.into());
150
151        true
152    }
153
154    pub fn dispatch_callback<T>(&mut self, callback: Callback<T>, args: T) -> bool
155    where
156        T: 'static,
157        Action: From<AnyAction> + EnablingCondition<State>,
158    {
159        let action: Action = callback.call(args);
160        self.dispatch(action)
161    }
162
163    /// Dispatch an Action (For `SubStore`).
164    ///
165    /// Returns `true` if the action was enabled, hence if it was dispatched
166    /// to reducer and then effects.
167    ///
168    /// If action is not enabled, we return false and do nothing.
169    pub fn sub_dispatch<A, S>(&mut self, action: A) -> bool
170    where
171        A: Into<<Self as SubStore<State, S>>::SubAction> + EnablingCondition<S>,
172        <Self as SubStore<State, S>>::SubAction: Into<Action>,
173        Self: SubStore<State, S>,
174    {
175        if !action.is_enabled(
176            <Self as SubStore<State, S>>::state(self),
177            self.last_action_id.into(),
178        ) {
179            return false;
180        }
181        self.dispatch_enabled(action.into().into());
182
183        true
184    }
185
186    fn update_action_id(&mut self) -> ActionId {
187        let prev_action_id = self.last_action_id;
188        let now = self.initial_time
189            + self
190                .service
191                .monotonic_time()
192                .duration_since(self.initial_monotonic_time);
193
194        let t = (Timestamp::from(prev_action_id) + 1).max(now);
195        self.last_action_id = ActionId::new_unchecked(t.into());
196        prev_action_id
197    }
198
199    /// Dispatches action without checking the enabling condition.
200    fn dispatch_enabled(&mut self, action: Action) {
201        let prev = self.update_action_id();
202        self.recursion_depth += 1;
203
204        let action_with_meta =
205            ActionMeta::new(self.last_action_id, prev, self.recursion_depth).with_action(action);
206
207        let mut dispatcher = Dispatcher::new();
208        self.dispatch_reducer(&action_with_meta, &mut dispatcher);
209        self.dispatch_effects(action_with_meta, dispatcher);
210
211        self.recursion_depth -= 1;
212    }
213
214    /// Runs the reducer.
215    #[inline(always)]
216    fn dispatch_reducer(
217        &mut self,
218        action_with_id: &ActionWithMeta<Action>,
219        dispatcher: &mut Dispatcher<Action, State>,
220    ) {
221        (self.reducer)(self.state.get_mut(), action_with_id, dispatcher);
222    }
223
224    /// Runs the effects.
225    #[inline(always)]
226    fn dispatch_effects(
227        &mut self,
228        action_with_id: ActionWithMeta<Action>,
229        mut queued: Dispatcher<Action, State>,
230    ) {
231        // First the effects for this specific action must be handled
232        (self.effects)(self, action_with_id);
233
234        // Then dispatch all actions enqueued by the reducer
235        while let Some(action) = queued.pop() {
236            if action.is_enabled(self.state(), self.last_action_id.into()) {
237                self.dispatch_enabled(action);
238            }
239        }
240    }
241}
242
243impl<State, Service, Action> Clone for Store<State, Service, Action>
244where
245    State: Clone,
246    Service: Clone,
247    Action: Clone + EnablingCondition<State>,
248{
249    fn clone(&self) -> Self {
250        Self {
251            reducer: self.reducer,
252            effects: self.effects,
253            service: self.service.clone(),
254            state: self.state.clone(),
255
256            initial_monotonic_time: self.initial_monotonic_time,
257            initial_time: self.initial_time,
258
259            recursion_depth: self.recursion_depth,
260            last_action_id: self.last_action_id,
261        }
262    }
263}