1use std::any::Any;
2use std::collections::HashMap;
3use std::future::Future;
4use std::pin::Pin;
5use std::sync::atomic::{AtomicU64, Ordering};
6use std::sync::{Arc, OnceLock};
7
8use parking_lot::Mutex;
9use std::time::Duration;
10
11use crate::bus::BusSender;
12use rust_dependencies::DependencyValues;
13use crate::env::Environment;
14use crate::error::EffectError;
15
16pub type EffectId = u64;
17
18type ErasedTask = Arc<
19 dyn Fn() -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, EffectError>> + Send>>
20 + Send
21 + Sync,
22>;
23
24type ErasedEnvTask = Arc<
25 dyn Fn(
26 &Environment,
27 ) -> Pin<Box<dyn Future<Output = Result<Box<dyn Any + Send>, EffectError>> + Send>>
28 + Send
29 + Sync,
30>;
31
32type ErasedRun = Arc<
33 dyn Fn(
34 Box<dyn Any + Send>,
35 ) -> Pin<Box<dyn Future<Output = Result<(), EffectError>> + Send>>
36 + Send
37 + Sync,
38>;
39
40static TASK_REGISTRY: OnceLock<Mutex<HashMap<EffectId, ErasedTask>>> = OnceLock::new();
41static ENV_TASK_REGISTRY: OnceLock<Mutex<HashMap<EffectId, ErasedEnvTask>>> = OnceLock::new();
42static RUN_REGISTRY: OnceLock<Mutex<HashMap<EffectId, ErasedRun>>> = OnceLock::new();
43static NEXT_REGISTRY_ID: AtomicU64 = AtomicU64::new(1);
44
45fn task_registry() -> &'static Mutex<HashMap<EffectId, ErasedTask>> {
46 TASK_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
47}
48
49fn env_task_registry() -> &'static Mutex<HashMap<EffectId, ErasedEnvTask>> {
50 ENV_TASK_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
51}
52
53fn run_registry() -> &'static Mutex<HashMap<EffectId, ErasedRun>> {
54 RUN_REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
55}
56
57pub struct RunSender<M> {
59 pub(crate) tx: BusSender<M>,
60}
61
62impl<M> RunSender<M> {
63 pub fn send(&self, msg: M) {
64 let _ = self.tx.send_blocking(msg);
65 }
66}
67
68pub(crate) fn register_task<M, F>(run: F) -> EffectId
69where
70 M: Send + 'static,
71 F: Fn() -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>> + Send + Sync + 'static,
72{
73 let id = NEXT_REGISTRY_ID.fetch_add(1, Ordering::Relaxed);
74 task_registry().lock().insert(
75 id,
76 Arc::new(move || {
77 let fut = run();
78 Box::pin(async move {
79 fut.await.map(|value| Box::new(value) as Box<dyn Any + Send>)
80 })
81 }),
82 );
83 id
84}
85
86pub(crate) fn register_env_task<M, F>(run: F) -> EffectId
87where
88 M: Send + 'static,
89 F: Fn(&Environment) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
90 + Send
91 + Sync
92 + 'static,
93{
94 let id = NEXT_REGISTRY_ID.fetch_add(1, Ordering::Relaxed);
95 env_task_registry().lock().insert(
96 id,
97 Arc::new(move |env| {
98 let fut = run(env);
99 Box::pin(async move {
100 fut.await.map(|value| Box::new(value) as Box<dyn Any + Send>)
101 })
102 }),
103 );
104 id
105}
106
107pub(crate) fn run_registered_task<M: Send + 'static>(
108 id: EffectId,
109) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>> {
110 let registry = task_registry().lock();
111 let Some(task) = registry.get(&id).cloned() else {
112 return Box::pin(async {
113 Err(EffectError::TaskFailed("missing registered task"))
114 });
115 };
116 drop(registry);
117 Box::pin(async move {
118 let any = (task)().await?;
119 any.downcast::<M>()
120 .map(|boxed| *boxed)
121 .map_err(|_| EffectError::TaskFailed("task message type mismatch"))
122 })
123}
124
125pub(crate) fn run_registered_env_task<M: Send + 'static>(
126 env: &Environment,
127 id: EffectId,
128) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>> {
129 let registry = env_task_registry().lock();
130 let Some(task) = registry.get(&id).cloned() else {
131 return Box::pin(async {
132 Err(EffectError::TaskFailed("missing registered env task"))
133 });
134 };
135 let env = env.clone();
136 drop(registry);
137 Box::pin(async move {
138 let any = (task)(&env).await?;
139 any.downcast::<M>()
140 .map(|boxed| *boxed)
141 .map_err(|_| EffectError::TaskFailed("env task message type mismatch"))
142 })
143}
144
145pub(crate) fn register_run<M, F>(run: F) -> EffectId
146where
147 M: Send + 'static,
148 F: Fn(RunSender<M>) -> Pin<Box<dyn Future<Output = Result<(), EffectError>> + Send>>
149 + Send
150 + Sync
151 + 'static,
152{
153 let id = NEXT_REGISTRY_ID.fetch_add(1, Ordering::Relaxed);
154 run_registry().lock().insert(
155 id,
156 Arc::new(move |any: Box<dyn Any + Send>| {
157 match any.downcast::<BusSender<M>>() {
158 Ok(tx) => run(RunSender { tx: *tx }),
159 Err(_) => Box::pin(async {
160 Err(EffectError::TaskFailed("run sender type mismatch"))
161 }),
162 }
163 }),
164 );
165 id
166}
167
168pub(crate) fn run_registered_run<M: Send + 'static>(
169 id: EffectId,
170 tx: BusSender<M>,
171) -> Pin<Box<dyn Future<Output = Result<(), EffectError>> + Send>> {
172 let registry = run_registry().lock();
173 let Some(run) = registry.get(&id).cloned() else {
174 return Box::pin(async { Err(EffectError::TaskFailed("missing registered run")) });
175 };
176 drop(registry);
177 Box::pin(async move { (run)(Box::new(tx) as Box<dyn Any + Send>).await })
178}
179
180pub type TaskFn<M> = fn() -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>;
182
183pub type EnvTaskFn<M> =
184 fn(&Environment) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>;
185
186pub enum Effect<M> {
191 None,
192 Task { id: EffectId, run: TaskFn<M> },
193 RegisteredTask { id: EffectId },
194 EnvTask { id: EffectId, run: EnvTaskFn<M> },
195 RegisteredEnvTask { id: EffectId },
196 RegisteredRun { id: EffectId },
197 Batch(Vec<Effect<M>>),
198 Cancellable {
199 id: EffectId,
200 cancel_in_flight: bool,
201 inner: Box<Effect<M>>,
202 },
203 Debounce {
204 id: EffectId,
205 duration: Duration,
206 inner: Box<Effect<M>>,
207 },
208 Throttle {
209 id: EffectId,
210 duration: Duration,
211 latest: bool,
212 inner: Box<Effect<M>>,
213 },
214 Provide {
215 env: Environment,
216 inner: Box<Effect<M>>,
217 },
218 Retry {
219 attempts: u32,
220 inner: Box<Effect<M>>,
221 },
222 Timeout {
223 duration: Duration,
224 inner: Box<Effect<M>>,
225 },
226 Sequence(Vec<Effect<M>>),
227 Race(Vec<Effect<M>>),
228 Catch {
229 inner: Box<Effect<M>>,
230 recover: fn(EffectError) -> Effect<M>,
231 },
232 Cancel { id: EffectId },
233}
234
235impl<M> Clone for Effect<M> {
236 fn clone(&self) -> Self {
237 match self {
238 Self::None => Self::None,
239 Self::Task { id, run } => Self::Task { id: *id, run: *run },
240 Self::RegisteredTask { id } => Self::RegisteredTask { id: *id },
241 Self::EnvTask { id, run } => Self::EnvTask { id: *id, run: *run },
242 Self::RegisteredEnvTask { id } => Self::RegisteredEnvTask { id: *id },
243 Self::RegisteredRun { id } => Self::RegisteredRun { id: *id },
244 Self::Batch(items) => Self::Batch(items.clone()),
245 Self::Cancellable {
246 id,
247 cancel_in_flight,
248 inner,
249 } => Self::Cancellable {
250 id: *id,
251 cancel_in_flight: *cancel_in_flight,
252 inner: inner.clone(),
253 },
254 Self::Debounce {
255 id,
256 duration,
257 inner,
258 } => Self::Debounce {
259 id: *id,
260 duration: *duration,
261 inner: inner.clone(),
262 },
263 Self::Throttle {
264 id,
265 duration,
266 latest,
267 inner,
268 } => Self::Throttle {
269 id: *id,
270 duration: *duration,
271 latest: *latest,
272 inner: inner.clone(),
273 },
274 Self::Provide { env, inner } => Self::Provide {
275 env: env.clone(),
276 inner: inner.clone(),
277 },
278 Self::Retry { attempts, inner } => Self::Retry {
279 attempts: *attempts,
280 inner: inner.clone(),
281 },
282 Self::Timeout { duration, inner } => Self::Timeout {
283 duration: *duration,
284 inner: inner.clone(),
285 },
286 Self::Sequence(items) => Self::Sequence(items.clone()),
287 Self::Race(items) => Self::Race(items.clone()),
288 Self::Catch { inner, recover } => Self::Catch {
289 inner: inner.clone(),
290 recover: *recover,
291 },
292 Self::Cancel { id } => Self::Cancel { id: *id },
293 }
294 }
295}
296
297impl<M> Effect<M> {
298 pub fn none() -> Self {
299 Self::None
300 }
301
302 pub fn task(id: EffectId, run: TaskFn<M>) -> Self {
303 Self::Task { id, run }
304 }
305
306 pub fn from_fn<F>(run: F) -> Self
307 where
308 M: Send + 'static,
309 F: Fn() -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
310 + Send
311 + Sync
312 + 'static,
313 {
314 let id = register_task(run);
315 Self::RegisteredTask { id }
316 }
317
318 pub fn env_task(id: EffectId, run: EnvTaskFn<M>) -> Self {
319 Self::EnvTask { id, run }
320 }
321
322 pub fn from_env_fn<F>(run: F) -> Self
323 where
324 M: Send + 'static,
325 F: Fn(&Environment) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
326 + Send
327 + Sync
328 + 'static,
329 {
330 let id = register_env_task(run);
331 Self::RegisteredEnvTask { id }
332 }
333
334 pub fn from_run<F>(run: F) -> Self
335 where
336 M: Send + 'static,
337 F: Fn(RunSender<M>) -> Pin<Box<dyn Future<Output = Result<(), EffectError>> + Send>>
338 + Send
339 + Sync
340 + 'static,
341 {
342 let id = register_run(run);
343 Self::RegisteredRun { id }
344 }
345
346 pub fn task_try(id: EffectId, run: TaskFn<M>) -> Self {
348 Self::task(id, run)
349 }
350
351 pub fn result_task<T, E>(run: TaskFn<Result<T, E>>, on_ok: fn(T) -> M, on_err: fn(E) -> M) -> Self
353 where
354 T: Send + 'static,
355 E: Send + 'static,
356 M: Send + 'static,
357 {
358 Self::from_fn(move || {
359 let fut = run();
360 Box::pin(async move {
361 match fut.await {
362 Ok(Ok(value)) => Ok(on_ok(value)),
363 Ok(Err(err)) => Ok(on_err(err)),
364 Err(effect_err) => Err(effect_err),
365 }
366 })
367 })
368 }
369
370 pub fn batch(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
371 let mut effects: Vec<_> = effects.into_iter().collect();
372 match effects.len() {
373 0 => Self::None,
374 1 => effects.pop().map_or(Self::None, |single| single),
375 _ => Self::Batch(effects),
376 }
377 }
378
379 pub fn merge(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
380 Self::batch(effects)
381 }
382
383 pub fn concatenate(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
384 let effects: Vec<_> = effects.into_iter().collect();
385 if effects.is_empty() {
386 Self::None
387 } else {
388 Self::Sequence(effects)
389 }
390 }
391
392 pub fn map<N>(self, f: fn(M) -> N) -> Effect<N>
393 where
394 M: Send + 'static,
395 N: Send + 'static,
396 {
397 match self {
398 Self::None => Effect::None,
399 Self::Task { run, .. } => Effect::from_fn(move || {
400 let fut = run();
401 Box::pin(async move { fut.await.map(f) })
402 }),
403 Self::RegisteredTask { id } => Effect::from_fn(move || {
404 let fut = run_registered_task::<M>(id);
405 Box::pin(async move { fut.await.map(f) })
406 }),
407 Self::EnvTask { run, .. } => Effect::from_env_fn(move |env| {
408 let fut = run(env);
409 Box::pin(async move { fut.await.map(f) })
410 }),
411 Self::RegisteredEnvTask { id } => Effect::from_env_fn(move |env| {
412 let fut = run_registered_env_task::<M>(env, id);
413 Box::pin(async move { fut.await.map(f) })
414 }),
415 Self::RegisteredRun { id } => Effect::RegisteredRun { id },
416 Self::Batch(items) => Effect::Batch(items.into_iter().map(|e| e.map(f)).collect()),
417 Self::Cancellable {
418 id,
419 cancel_in_flight,
420 inner,
421 } => Effect::Cancellable {
422 id,
423 cancel_in_flight,
424 inner: Box::new(inner.map(f)),
425 },
426 Self::Debounce {
427 id,
428 duration,
429 inner,
430 } => Effect::Debounce {
431 id,
432 duration,
433 inner: Box::new(inner.map(f)),
434 },
435 Self::Throttle {
436 id,
437 duration,
438 latest,
439 inner,
440 } => Effect::Throttle {
441 id,
442 duration,
443 latest,
444 inner: Box::new(inner.map(f)),
445 },
446 Self::Provide { env, inner } => Effect::Provide {
447 env,
448 inner: Box::new(inner.map(f)),
449 },
450 Self::Retry { attempts, inner } => Effect::Retry {
451 attempts,
452 inner: Box::new(inner.map(f)),
453 },
454 Self::Timeout { duration, inner } => Effect::Timeout {
455 duration,
456 inner: Box::new(inner.map(f)),
457 },
458 Self::Sequence(items) => Effect::Sequence(items.into_iter().map(|e| e.map(f)).collect()),
459 Self::Race(items) => Effect::Race(items.into_iter().map(|e| e.map(f)).collect()),
460 Self::Catch { inner, recover: _ } => inner.map(f),
461 Self::Cancel { id } => Effect::Cancel { id },
462 }
463 }
464
465 pub fn cancellable(id: EffectId, inner: Effect<M>) -> Self {
466 Self::cancellable_with(id, true, inner)
467 }
468
469 pub fn cancellable_with(id: EffectId, cancel_in_flight: bool, inner: Effect<M>) -> Self {
470 Self::Cancellable {
471 id,
472 cancel_in_flight,
473 inner: Box::new(inner),
474 }
475 }
476
477 pub fn debounce(id: EffectId, duration: Duration, inner: Effect<M>) -> Self {
478 Self::Debounce {
479 id,
480 duration,
481 inner: Box::new(inner),
482 }
483 }
484
485 pub fn throttle(id: EffectId, duration: Duration, latest: bool, inner: Effect<M>) -> Self {
486 Self::Throttle {
487 id,
488 duration,
489 latest,
490 inner: Box::new(inner),
491 }
492 }
493
494 pub fn cancel(id: EffectId) -> Self {
495 Self::Cancel { id }
496 }
497
498 pub fn provide(env: Environment, inner: Effect<M>) -> Self {
499 Self::Provide {
500 env,
501 inner: Box::new(inner),
502 }
503 }
504
505 pub fn provide_dependency<D: Send + Sync + 'static>(value: D, inner: Effect<M>) -> Self {
507 Self::provide(Environment::from_values(DependencyValues::new().with(value)), inner)
508 }
509
510 pub fn retry(attempts: u32, inner: Effect<M>) -> Self {
511 Self::Retry {
512 attempts,
513 inner: Box::new(inner),
514 }
515 }
516
517 pub fn timeout(duration: Duration, inner: Effect<M>) -> Self {
518 Self::Timeout {
519 duration,
520 inner: Box::new(inner),
521 }
522 }
523
524 pub fn sequence(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
525 Self::concatenate(effects)
526 }
527
528 pub fn race(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
529 let effects: Vec<_> = effects.into_iter().collect();
530 if effects.is_empty() {
531 Self::None
532 } else {
533 Self::Race(effects)
534 }
535 }
536
537 pub fn catch(inner: Effect<M>, recover: fn(EffectError) -> Effect<M>) -> Self {
538 Self::Catch {
539 inner: Box::new(inner),
540 recover,
541 }
542 }
543}
544
545impl<M> PartialEq for Effect<M> {
546 fn eq(&self, other: &Self) -> bool {
547 std::mem::discriminant(self) == std::mem::discriminant(other)
548 }
549}
550
551impl<M> std::fmt::Debug for Effect<M> {
552 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
553 match self {
554 Self::None => write!(f, "Effect::None"),
555 Self::Task { id, .. } => write!(f, "Effect::Task({id})"),
556 Self::RegisteredTask { id } => write!(f, "Effect::RegisteredTask({id})"),
557 Self::EnvTask { id, .. } => write!(f, "Effect::EnvTask({id})"),
558 Self::RegisteredEnvTask { id } => write!(f, "Effect::RegisteredEnvTask({id})"),
559 Self::RegisteredRun { id } => write!(f, "Effect::RegisteredRun({id})"),
560 Self::Batch(n) => write!(f, "Effect::Batch({})", n.len()),
561 Self::Cancellable {
562 id,
563 cancel_in_flight,
564 ..
565 } => write!(f, "Effect::Cancellable({id}, in_flight={cancel_in_flight})"),
566 Self::Debounce { id, duration, .. } => {
567 write!(f, "Effect::Debounce({id}, {duration:?})")
568 }
569 Self::Throttle {
570 id,
571 duration,
572 latest,
573 ..
574 } => write!(f, "Effect::Throttle({id}, {duration:?}, latest={latest})"),
575 Self::Provide { .. } => write!(f, "Effect::Provide"),
576 Self::Retry { attempts, .. } => write!(f, "Effect::Retry({attempts})"),
577 Self::Timeout { duration, .. } => write!(f, "Effect::Timeout({duration:?})"),
578 Self::Sequence(n) => write!(f, "Effect::Sequence({})", n.len()),
579 Self::Race(n) => write!(f, "Effect::Race({})", n.len()),
580 Self::Catch { .. } => write!(f, "Effect::Catch"),
581 Self::Cancel { id } => write!(f, "Effect::Cancel({id})"),
582 }
583 }
584}
585
586pub(crate) fn run_leaf<M>(
587 effect: Effect<M>,
588 env: &Environment,
589) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
590where
591 M: Send + 'static,
592{
593 match effect {
594 Effect::Task { run, .. } => run(),
595 Effect::RegisteredTask { id } => run_registered_task(id),
596 Effect::EnvTask { run, .. } => run(env),
597 Effect::RegisteredEnvTask { id } => run_registered_env_task(env, id),
598 Effect::Cancel { .. } => Box::pin(async move {
599 Err(EffectError::Cancelled)
600 }),
601 other => Box::pin(async move {
602 Err(EffectError::Other(format!("non-leaf effect: {other:?}")))
603 }),
604 }
605}
606
607#[cfg(test)]
608#[allow(dead_code, clippy::redundant_closure, clippy::type_complexity)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn merge_and_concatenate_constructors() {
614 let a = Effect::<i32>::none();
615 let b = Effect::<i32>::none();
616 assert!(matches!(Effect::merge([a, b]), Effect::Batch(_)));
617 assert!(matches!(Effect::concatenate([] as [Effect<i32>; 0]), Effect::None));
618 }
619
620 #[test]
621 fn result_task_maps_ok_and_err() {
622 fn load() -> Pin<Box<dyn Future<Output = Result<Result<i32, &'static str>, EffectError>> + Send>> {
623 Box::pin(async { Ok(Ok(7)) })
624 }
625 fn fail() -> Pin<Box<dyn Future<Output = Result<Result<i32, &'static str>, EffectError>> + Send>> {
626 Box::pin(async { Ok(Err("nope")) })
627 }
628 fn ok(n: i32) -> String {
629 format!("ok:{n}")
630 }
631 fn err(e: &'static str) -> String {
632 format!("err:{e}")
633 }
634
635 let ok_effect = Effect::result_task(load, ok, err);
636 let err_effect = Effect::result_task(fail, ok, err);
637 assert!(matches!(ok_effect, Effect::RegisteredTask { .. }));
638 assert!(matches!(err_effect, Effect::RegisteredTask { .. }));
639 }
640
641 #[test]
642 fn debounce_and_throttle_constructors() {
643 let inner = Effect::<i32>::task(1, || Box::pin(async { Ok(1) }));
644 assert!(matches!(
645 Effect::debounce(9, Duration::from_millis(50), inner.clone()),
646 Effect::Debounce { id: 9, .. }
647 ));
648 assert!(matches!(
649 Effect::throttle(9, Duration::from_millis(50), true, inner),
650 Effect::Throttle {
651 id: 9,
652 latest: true,
653 ..
654 }
655 ));
656 }
657}