Skip to content

Commit 2bef19e

Browse files
committed
feat(dispatch-queue): Add dispatch queue and substate support
1 parent 9826c52 commit 2bef19e

File tree

6 files changed

+198
-6
lines changed

6 files changed

+198
-6
lines changed

src/action/enabling_condition.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,3 @@ pub trait EnablingCondition<State> {
77
true
88
}
99
}
10-

src/dispatcher.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use crate::EnablingCondition;
2+
use std::collections::VecDeque;
3+
4+
pub struct Dispatcher<Action, State> {
5+
queue: VecDeque<AnyAction<Action, State>>,
6+
}
7+
8+
impl<Action, State> Dispatcher<Action, State> {
9+
pub fn new() -> Self {
10+
Self {
11+
queue: VecDeque::new(),
12+
}
13+
}
14+
15+
pub fn push<T>(&mut self, action: T)
16+
where
17+
T: 'static + EnablingCondition<State> + Into<Action> + Clone + Send,
18+
{
19+
let wrapped_action = AnyAction::<Action, State>::new(action);
20+
self.queue.push_back(wrapped_action);
21+
}
22+
23+
pub(crate) fn pop(&mut self) -> Option<AnyAction<Action, State>> {
24+
self.queue.pop_front()
25+
}
26+
27+
pub(crate) fn push_front(&mut self, other: Dispatcher<Action, State>) {
28+
other
29+
.queue
30+
.into_iter()
31+
.rev()
32+
.for_each(|action| self.queue.push_front(action));
33+
}
34+
}
35+
36+
trait ActionConvertible<A, S>: EnablingCondition<S> {
37+
fn convert(&self) -> A;
38+
}
39+
40+
pub(crate) struct AnyAction<A, S> {
41+
inner: Box<dyn ActionConvertible<A, S> + Send>,
42+
}
43+
44+
impl<A, S> AnyAction<A, S> {
45+
fn new<T>(action: T) -> Self
46+
where
47+
T: 'static + ActionConvertible<A, S> + Send,
48+
{
49+
Self {
50+
inner: Box::new(action),
51+
}
52+
}
53+
54+
pub fn is_enabled(&self, state: &S, time: crate::Timestamp) -> bool {
55+
self.inner.is_enabled(state, time)
56+
}
57+
58+
pub fn convert(&self) -> A {
59+
self.inner.convert()
60+
}
61+
}
62+
63+
impl<T, A, S> ActionConvertible<A, S> for T
64+
where
65+
T: Clone + Into<A> + EnablingCondition<S>,
66+
{
67+
fn convert(&self) -> A {
68+
self.clone().into()
69+
}
70+
}

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ pub use store::{monotonic_to_time, Store};
2020

2121
mod sub_store;
2222
pub use sub_store::SubStore;
23+
24+
mod dispatcher;
25+
pub use dispatcher::Dispatcher;
26+
27+
mod substate;
28+
pub use substate::{Substate, SubstateAccess};

src/reducer.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use crate::ActionWithMeta;
1+
use crate::{ActionWithMeta, Dispatcher};
22

33
/// Function signature for a reducer.
4-
pub type Reducer<State, Action> = fn(&mut State, &ActionWithMeta<Action>);
4+
pub type Reducer<State, Action> =
5+
fn(&mut State, &ActionWithMeta<Action>, &mut Dispatcher<Action, State>);

src/store.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use std::sync::OnceLock;
22

33
use crate::{
4-
ActionId, ActionMeta, ActionWithMeta, Effects, EnablingCondition, Instant, Reducer, SubStore,
5-
SystemTime, TimeService,
4+
ActionId, ActionMeta, ActionWithMeta, Dispatcher, Effects, EnablingCondition, Instant, Reducer,
5+
SubStore, SystemTime, TimeService,
66
};
77

88
/// Wraps around State and allows only immutable borrow,
@@ -75,6 +75,9 @@ pub struct Store<State, Service, Action> {
7575
recursion_depth: u32,
7676

7777
last_action_id: ActionId,
78+
79+
/// Queue for actions to be dispatched after the state update
80+
dispatch_queue: Dispatcher<Action, State>,
7881
}
7982

8083
impl<State, Service, Action> Store<State, Service, Action>
@@ -109,6 +112,8 @@ where
109112

110113
recursion_depth: 0,
111114
last_action_id: ActionId::new_unchecked(initial_time_nanos as u64),
115+
116+
dispatch_queue: Dispatcher::new(),
112117
}
113118
}
114119

@@ -186,6 +191,7 @@ where
186191
self.last_action_id = curr;
187192
self.recursion_depth += 1;
188193

194+
// TODO: instead return queued actinos, pass them to dispatch_effects?
189195
self.dispatch_reducer(&action_with_meta);
190196
self.dispatch_effects(action_with_meta);
191197

@@ -195,13 +201,26 @@ where
195201
/// Runs the reducer.
196202
#[inline(always)]
197203
fn dispatch_reducer(&mut self, action_with_id: &ActionWithMeta<Action>) {
198-
(self.reducer)(self.state.get_mut(), action_with_id);
204+
// All new queued elements will be stored here
205+
let mut queue = Dispatcher::new();
206+
(self.reducer)(self.state.get_mut(), action_with_id, &mut queue);
207+
208+
// All the enqueued actions gets pushed to the front of the global queue
209+
self.dispatch_queue.push_front(queue);
199210
}
200211

201212
/// Runs the effects.
202213
#[inline(always)]
203214
fn dispatch_effects(&mut self, action_with_id: ActionWithMeta<Action>) {
215+
// First the effects for this specific action must be handled
204216
(self.effects)(self, action_with_id);
217+
218+
// Then dispatch all actions enqueued by the reducer
219+
while let Some(action) = self.dispatch_queue.pop() {
220+
if action.is_enabled(self.state(), self.last_action_id.into()) {
221+
self.dispatch_enabled(action.convert());
222+
}
223+
}
205224
}
206225
}
207226

@@ -222,6 +241,8 @@ where
222241

223242
recursion_depth: self.recursion_depth,
224243
last_action_id: self.last_action_id,
244+
245+
dispatch_queue: Dispatcher::new(), // TODO: clone
225246
}
226247
}
227248
}

src/substate.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
use crate::Dispatcher;
2+
3+
pub trait SubstateAccess<T> {
4+
fn substate(&self) -> &T;
5+
fn substate_mut(&mut self) -> &mut T;
6+
}
7+
8+
pub struct Substate<'a, A, T, S> {
9+
state: &'a mut T,
10+
dispatcher: &'a mut Dispatcher<A, T>,
11+
_marker: std::marker::PhantomData<S>,
12+
}
13+
14+
impl<'a, A, T, S> Substate<'a, A, T, S>
15+
where
16+
T: SubstateAccess<S>,
17+
{
18+
pub fn new(state: &'a mut T, dispatcher: &'a mut Dispatcher<A, T>) -> Self {
19+
Self {
20+
state,
21+
dispatcher,
22+
_marker: Default::default(),
23+
}
24+
}
25+
26+
pub fn from_compatible_substate<OS>(other: Substate<'a, A, T, OS>) -> Substate<'a, A, T, S> {
27+
let Substate {
28+
state, dispatcher, ..
29+
} = other;
30+
31+
Self::new(state, dispatcher)
32+
}
33+
34+
pub fn get_substate(&self) -> &S {
35+
self.state.substate()
36+
}
37+
38+
pub fn get_substate_mut(&mut self) -> &mut S {
39+
self.state.substate_mut()
40+
}
41+
42+
pub fn into_dispatcher_and_state(self) -> (&'a mut Dispatcher<A, T>, &'a T) {
43+
(self.dispatcher, self.state)
44+
}
45+
46+
pub fn into_dispatcher(self) -> &'a mut Dispatcher<A, T> {
47+
self.dispatcher
48+
}
49+
}
50+
51+
impl<'a, A, T, S> From<(&'a mut T, &'a mut Dispatcher<A, T>)> for Substate<'a, A, T, S> {
52+
fn from(state_and_dispatcher: (&'a mut T, &'a mut Dispatcher<A, T>)) -> Self {
53+
let (state, dispatcher) = state_and_dispatcher;
54+
Self {
55+
state,
56+
dispatcher,
57+
_marker: Default::default(),
58+
}
59+
}
60+
}
61+
62+
impl<'a, A, T, S> std::ops::Deref for Substate<'a, A, T, S>
63+
where
64+
T: SubstateAccess<S>,
65+
{
66+
type Target = S;
67+
68+
fn deref(&self) -> &Self::Target {
69+
self.get_substate()
70+
}
71+
}
72+
73+
impl<'a, A, T, S> std::ops::DerefMut for Substate<'a, A, T, S>
74+
where
75+
T: SubstateAccess<S>,
76+
{
77+
fn deref_mut(&mut self) -> &mut Self::Target {
78+
self.get_substate_mut()
79+
}
80+
}
81+
82+
#[macro_export]
83+
macro_rules! impl_substate_access {
84+
($state:ty, $substate_type:ty, $($substate_path:tt)*) => {
85+
impl redux::SubstateAccess<$substate_type> for $state {
86+
fn substate(&self) -> &$substate_type {
87+
&self.$($substate_path)*
88+
}
89+
90+
fn substate_mut(&mut self) -> &mut $substate_type {
91+
&mut self.$($substate_path)*
92+
}
93+
}
94+
};
95+
}

0 commit comments

Comments
 (0)