sauron_core/dom/
cmd.rs

1use crate::dom::Effects;
2use futures::channel::mpsc;
3use futures::channel::mpsc::UnboundedReceiver;
4use futures::StreamExt;
5use std::future::Future;
6use std::pin::Pin;
7#[cfg(feature = "with-dom")]
8use wasm_bindgen::closure::Closure;
9
10/// Cmd is a way to tell the Runtime that something needs to be executed
11pub struct Cmd<MSG> {
12    /// commands
13    pub(crate) commands: Vec<Command<MSG>>,
14}
15
16/// encapsulate anything a component can do
17pub enum Command<MSG> {
18    /// A task with one single resulting MSG
19    Action(Action<MSG>),
20    #[cfg(feature = "with-dom")]
21    /// A task with recurring resulting MSG
22    Sub(Sub<MSG>),
23}
24
25impl<MSG> Cmd<MSG>
26where
27    MSG: 'static,
28{
29    /// maps to a once future
30    pub fn new<F>(f: F) -> Self
31    where
32        F: Future<Output = MSG> + 'static,
33    {
34        Self::once(f)
35    }
36
37    /// Creates a Cmd which expects to be polled only once
38    pub fn once<F>(f: F) -> Self
39    where
40        F: Future<Output = MSG> + 'static,
41    {
42        Self {
43            commands: vec![Command::single(f)],
44        }
45    }
46    /// Creates a Cmd which will be polled multiple times
47    pub fn recurring(
48        rx: UnboundedReceiver<MSG>,
49        event_closure: Closure<dyn FnMut(web_sys::Event)>,
50    ) -> Self {
51        Self {
52            commands: vec![Command::sub(rx, event_closure)],
53        }
54    }
55
56    /// map the msg of this Cmd such that Cmd<MSG> becomes Cmd<MSG2>.
57    pub fn map_msg<F, MSG2>(self, f: F) -> Cmd<MSG2>
58    where
59        F: Fn(MSG) -> MSG2 + 'static + Clone,
60        MSG2: 'static,
61    {
62        Cmd {
63            commands: self
64                .commands
65                .into_iter()
66                .map(|t| t.map_msg(f.clone()))
67                .collect(),
68        }
69    }
70
71    /// batch together multiple Cmd into one task
72    pub fn batch(tasks: impl IntoIterator<Item = Self>) -> Self {
73        let mut commands = vec![];
74        for task in tasks.into_iter() {
75            commands.extend(task.commands);
76        }
77        Self { commands }
78    }
79
80    ///
81    pub fn none() -> Self {
82        Self { commands: vec![] }
83    }
84}
85
86impl<MSG> From<Effects<MSG, ()>> for Cmd<MSG>
87where
88    MSG: 'static,
89{
90    /// Convert Effects that has only follow ups
91    fn from(effects: Effects<MSG, ()>) -> Self {
92        // we can safely ignore the effects here
93        // as there is no content on it.
94        let Effects { local, external: _ } = effects;
95
96        Cmd::batch(local.into_iter().map(Cmd::from))
97    }
98}
99
100impl<MSG> Command<MSG>
101where
102    MSG: 'static,
103{
104    ///
105    pub fn single<F>(f: F) -> Self
106    where
107        F: Future<Output = MSG> + 'static,
108    {
109        Self::Action(Action::new(f))
110    }
111
112    ///
113    #[cfg(feature = "with-dom")]
114    pub fn sub(
115        rx: UnboundedReceiver<MSG>,
116        event_closure: Closure<dyn FnMut(web_sys::Event)>,
117    ) -> Self {
118        Self::Sub(Sub {
119            receiver: rx,
120            event_closure,
121        })
122    }
123
124    /// apply a function to the msg to create a different task which has a different msg
125    pub fn map_msg<F, MSG2>(self, f: F) -> Command<MSG2>
126    where
127        F: Fn(MSG) -> MSG2 + 'static,
128        MSG2: 'static,
129    {
130        match self {
131            Self::Action(task) => Command::Action(task.map_msg(f)),
132            #[cfg(feature = "with-dom")]
133            Self::Sub(task) => Command::Sub(task.map_msg(f)),
134        }
135    }
136
137    /// return the next value
138    pub async fn next(&mut self) -> Option<MSG> {
139        match self {
140            Self::Action(task) => task.next().await,
141            #[cfg(feature = "with-dom")]
142            Self::Sub(task) => task.next().await,
143        }
144    }
145}
146
147/// Action is used to do asynchronous operations
148pub struct Action<MSG> {
149    task: Pin<Box<dyn Future<Output = MSG>>>,
150    /// a marker to indicate if the value of the future is awaited.
151    /// any attempt to await it again will error,
152    /// saying that the async function is resumed after completion.
153    done: bool,
154}
155
156impl<MSG> Action<MSG>
157where
158    MSG: 'static,
159{
160    /// create a new task from a function which returns a future
161    fn new<F>(f: F) -> Self
162    where
163        F: Future<Output = MSG> + 'static,
164    {
165        Self {
166            task: Box::pin(f),
167            done: false,
168        }
169    }
170
171    /// apply a function to the msg to create a different task which has a different msg
172    fn map_msg<F, MSG2>(self, f: F) -> Action<MSG2>
173    where
174        F: Fn(MSG) -> MSG2 + 'static,
175        MSG2: 'static,
176    {
177        let task = self.task;
178        Action::new(async move {
179            let msg = task.await;
180            f(msg)
181        })
182    }
183
184    /// get the next value
185    async fn next(&mut self) -> Option<MSG> {
186        // return None is already done since awaiting it again is an error
187        if self.done {
188            None
189        } else {
190            let msg = self.task.as_mut().await;
191            // mark as done
192            self.done = true;
193            Some(msg)
194        }
195    }
196}
197
198impl<F, MSG> From<F> for Action<MSG>
199where
200    F: Future<Output = MSG> + 'static,
201    MSG: 'static,
202{
203    fn from(f: F) -> Self {
204        Action::new(f)
205    }
206}
207
208#[cfg(feature = "with-dom")]
209/// Sub is a recurring operation
210pub struct Sub<MSG> {
211    pub(crate) receiver: UnboundedReceiver<MSG>,
212    /// store the associated closures so it is not dropped before being event executed
213    pub(crate) event_closure: Closure<dyn FnMut(web_sys::Event)>,
214}
215
216#[cfg(feature = "with-dom")]
217impl<MSG> Sub<MSG>
218where
219    MSG: 'static,
220{
221    async fn next(&mut self) -> Option<MSG> {
222        self.receiver.next().await
223    }
224
225    /// apply a function to the msg to create a different task which has a different msg
226    fn map_msg<F, MSG2>(self, f: F) -> Sub<MSG2>
227    where
228        F: Fn(MSG) -> MSG2 + 'static,
229        MSG2: 'static,
230    {
231        let (mut tx, rx) = mpsc::unbounded();
232        let Sub {
233            mut receiver,
234            event_closure,
235        } = self;
236
237        crate::dom::spawn_local(async move {
238            while let Some(msg) = receiver.next().await {
239                tx.start_send(f(msg)).expect("must send");
240            }
241        });
242
243        Sub {
244            receiver: rx,
245            event_closure,
246        }
247    }
248}