urushi_tui_app/effect.rs
1//! What an `update` asks the runtime to do.
2
3use std::fmt;
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7use std::time::{Duration, Instant};
8
9use urushi::Key;
10
11/// Blocking work an effect carries.
12pub(crate) type Work<Message> = Box<dyn FnOnce() -> Message + Send + 'static>;
13
14/// What a delayed effect turns its firing time into.
15pub(crate) type Fire<Message> = Box<dyn FnOnce(Instant) -> Message + Send + 'static>;
16
17/// A future an effect carries.
18pub(crate) type BoxFuture<Message> = Pin<Box<dyn Future<Output = Message> + Send + 'static>>;
19
20/// A function the runtime may call for many values.
21pub(crate) type Mapper<From, To> = Arc<dyn Fn(From) -> To + Send + Sync + 'static>;
22
23/// Work the runtime performs on behalf of an `update`, whose result returns as
24/// a message.
25///
26/// An effect is a value. It runs nothing when it is built, it cannot touch the
27/// model, and it names no executor: which thread runs a closure and what polls
28/// a future is the runtime's, behind a boundary an application never sees.
29/// Building one in a test therefore costs nothing and needs no terminal.
30///
31/// ```
32/// use urushi_tui_app::Effect;
33///
34/// enum Message {
35/// Loaded(usize),
36/// }
37///
38/// let effect = Effect::perform(|| Message::Loaded(1));
39/// ```
40///
41/// # Which constructor
42///
43/// [`perform`](Effect::perform) carries blocking work and
44/// [`future`](Effect::future) carries a future, so an application whose I/O is
45/// already asynchronous does not wrap it in a thread and one whose work is
46/// blocking does not reach for an executor by name. The `_latest` forms of each
47/// carry a [`Key`]: starting one replaces an unfinished effect running under
48/// the same key, which is the one staleness the runtime knows about on its own.
49/// Every other reason a completion no longer applies is the application's to
50/// check in `update`.
51///
52/// [`after`](Effect::after) is the one-shot timer: it waits and then sends its
53/// message, reading the runtime's clock rather than an executor the application
54/// brought. [`after_latest`](Effect::after_latest) is the same under a key, so a
55/// second one restarts the wait rather than adding a second timer — which is
56/// what debouncing is. A timer that repeats is
57/// [`Subscription::interval`](super::Subscription::interval) instead, because it
58/// lives for as long as the application declares it.
59///
60/// [`batch`](Effect::batch) starts several effects concurrently and promises
61/// nothing about the order their completions arrive in. There is no sequencing
62/// combinator: work that must follow other work is returned from the `update`
63/// that receives the first completion.
64pub struct Effect<Message> {
65 kind: EffectKind<Message>,
66}
67
68/// The shape of an [`Effect`], as the runtime reads it.
69pub(crate) enum EffectKind<Message> {
70 /// Nothing to do.
71 None,
72 /// Stop the runtime.
73 Shutdown,
74 /// Blocking work, replaceable when it carries a key.
75 Perform {
76 key: Option<Key>,
77 work: Work<Message>,
78 },
79 /// A future, replaceable when it carries a key.
80 Future {
81 key: Option<Key>,
82 future: BoxFuture<Message>,
83 },
84 /// A message to send once `delay` has passed, replaceable when it carries a
85 /// key.
86 After {
87 key: Option<Key>,
88 delay: Duration,
89 fire: Fire<Message>,
90 },
91 /// Several effects, started concurrently.
92 Batch(Vec<Effect<Message>>),
93}
94
95impl<Message> Effect<Message> {
96 /// An effect that does nothing.
97 pub fn none() -> Self {
98 Self {
99 kind: EffectKind::None,
100 }
101 }
102
103 /// The request to stop the runtime.
104 ///
105 /// The runtime reads the request from `update`'s return value rather than
106 /// delivering it, so it never enters admission and is not a message. On it,
107 /// no sibling effect in the same return value starts, the completions of
108 /// effects still in flight are discarded, every subscription stops, the
109 /// terminal session is restored, and the entry point returns the final
110 /// model. Work that must finish before the application exits is an ordinary
111 /// effect whose completion is the `update` that returns this.
112 pub fn shutdown() -> Self {
113 Self {
114 kind: EffectKind::Shutdown,
115 }
116 }
117
118 /// Blocking work, run off the thread that runs `update`.
119 pub fn perform<F>(work: F) -> Self
120 where
121 F: FnOnce() -> Message + Send + 'static,
122 {
123 Self {
124 kind: EffectKind::Perform {
125 key: None,
126 work: Box::new(work),
127 },
128 }
129 }
130
131 /// Blocking work that replaces any unfinished work running under `key`.
132 pub fn perform_latest<F>(key: impl Into<Key>, work: F) -> Self
133 where
134 F: FnOnce() -> Message + Send + 'static,
135 {
136 Self {
137 kind: EffectKind::Perform {
138 key: Some(key.into()),
139 work: Box::new(work),
140 },
141 }
142 }
143
144 /// Asynchronous work.
145 pub fn future<F>(future: F) -> Self
146 where
147 F: Future<Output = Message> + Send + 'static,
148 {
149 Self {
150 kind: EffectKind::Future {
151 key: None,
152 future: Box::pin(future),
153 },
154 }
155 }
156
157 /// Asynchronous work that replaces any unfinished work running under `key`.
158 pub fn future_latest<F>(key: impl Into<Key>, future: F) -> Self
159 where
160 F: Future<Output = Message> + Send + 'static,
161 {
162 Self {
163 kind: EffectKind::Future {
164 key: Some(key.into()),
165 future: Box::pin(future),
166 },
167 }
168 }
169
170 /// A message sent once `delay` has passed, timed by the runtime's clock.
171 ///
172 /// The clock is the runtime's, which is what keeps a waiting application
173 /// free of an executor of its own and what lets a test drive the wait by
174 /// hand instead of sleeping.
175 pub fn after<F>(delay: Duration, f: F) -> Self
176 where
177 F: FnOnce(Instant) -> Message + Send + 'static,
178 {
179 Self {
180 kind: EffectKind::After {
181 key: None,
182 delay,
183 fire: Box::new(f),
184 },
185 }
186 }
187
188 /// A delayed message that replaces any unfired timer under `key`.
189 ///
190 /// Replacing a timer restarts its wait, so an `update` that returns this on
191 /// every keystroke fires once the keystrokes stop: the debounce a search
192 /// field or a live preview needs, with no state in the model.
193 pub fn after_latest<F>(key: impl Into<Key>, delay: Duration, f: F) -> Self
194 where
195 F: FnOnce(Instant) -> Message + Send + 'static,
196 {
197 Self {
198 kind: EffectKind::After {
199 key: Some(key.into()),
200 delay,
201 fire: Box::new(f),
202 },
203 }
204 }
205
206 /// Several effects, started concurrently.
207 ///
208 /// Their completions may arrive in any order.
209 pub fn batch(effects: impl IntoIterator<Item = Self>) -> Self {
210 Self {
211 kind: EffectKind::Batch(effects.into_iter().collect()),
212 }
213 }
214
215 /// The same effect with its message passed through `f`.
216 ///
217 /// This is what lets a program hold another program: a parent whose message
218 /// wraps a child's calls the child's `update`, receives the child's effect,
219 /// and returns it mapped, without the child knowing the parent's message
220 /// type.
221 pub fn map<To>(self, f: impl Fn(Message) -> To + Send + Sync + 'static) -> Effect<To>
222 where
223 Message: Send + 'static,
224 To: Send + 'static,
225 {
226 self.map_with(Arc::new(f))
227 }
228
229 fn map_with<To>(self, f: Mapper<Message, To>) -> Effect<To>
230 where
231 Message: Send + 'static,
232 To: Send + 'static,
233 {
234 let kind = match self.kind {
235 EffectKind::None => EffectKind::None,
236 EffectKind::Shutdown => EffectKind::Shutdown,
237 EffectKind::Perform { key, work } => EffectKind::Perform {
238 key,
239 work: Box::new(move || f(work())),
240 },
241 EffectKind::Future { key, future } => EffectKind::Future {
242 key,
243 future: Box::pin(async move { f(future.await) }),
244 },
245 EffectKind::After { key, delay, fire } => EffectKind::After {
246 key,
247 delay,
248 fire: Box::new(move |at| f(fire(at))),
249 },
250 EffectKind::Batch(effects) => EffectKind::Batch(
251 effects
252 .into_iter()
253 .map(|effect| effect.map_with(Arc::clone(&f)))
254 .collect(),
255 ),
256 };
257 Effect { kind }
258 }
259
260 /// The shape of this effect, for the runtime that interprets it.
261 pub(crate) fn into_kind(self) -> EffectKind<Message> {
262 self.kind
263 }
264
265 /// Whether this effect tree asks the runtime to stop before starting work.
266 pub(crate) fn requests_shutdown(&self) -> bool {
267 match &self.kind {
268 EffectKind::Shutdown => true,
269 EffectKind::Batch(effects) => effects.iter().any(Self::requests_shutdown),
270 EffectKind::None
271 | EffectKind::Perform { .. }
272 | EffectKind::Future { .. }
273 | EffectKind::After { .. } => false,
274 }
275 }
276}
277
278impl<Message> Default for Effect<Message> {
279 fn default() -> Self {
280 Self::none()
281 }
282}
283
284impl<Message> fmt::Debug for Effect<Message> {
285 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match &self.kind {
287 EffectKind::None => formatter.write_str("Effect::none"),
288 EffectKind::Shutdown => formatter.write_str("Effect::shutdown"),
289 EffectKind::Perform { key, .. } => formatter
290 .debug_struct("Effect::perform")
291 .field("key", key)
292 .finish(),
293 EffectKind::Future { key, .. } => formatter
294 .debug_struct("Effect::future")
295 .field("key", key)
296 .finish(),
297 EffectKind::After { key, delay, .. } => formatter
298 .debug_struct("Effect::after")
299 .field("key", key)
300 .field("delay", delay)
301 .finish(),
302 EffectKind::Batch(effects) => formatter.debug_list().entries(effects.iter()).finish(),
303 }
304 }
305}
306
307#[cfg(test)]
308#[path = "effect_tests.rs"]
309mod tests;