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 RetryBackoff {
223 attempts: u32,
224 delay: Duration,
225 max_delay: Duration,
226 inner: Box<Effect<M>>,
227 },
228 Timeout {
229 duration: Duration,
230 inner: Box<Effect<M>>,
231 },
232 Sequence(Vec<Effect<M>>),
233 Race(Vec<Effect<M>>),
234 Catch {
235 inner: Box<Effect<M>>,
236 recover: fn(EffectError) -> Effect<M>,
237 },
238 Cancel { id: EffectId },
239}
240
241impl<M> Clone for Effect<M> {
242 fn clone(&self) -> Self {
243 match self {
244 Self::None => Self::None,
245 Self::Task { id, run } => Self::Task { id: *id, run: *run },
246 Self::RegisteredTask { id } => Self::RegisteredTask { id: *id },
247 Self::EnvTask { id, run } => Self::EnvTask { id: *id, run: *run },
248 Self::RegisteredEnvTask { id } => Self::RegisteredEnvTask { id: *id },
249 Self::RegisteredRun { id } => Self::RegisteredRun { id: *id },
250 Self::Batch(items) => Self::Batch(items.clone()),
251 Self::Cancellable {
252 id,
253 cancel_in_flight,
254 inner,
255 } => Self::Cancellable {
256 id: *id,
257 cancel_in_flight: *cancel_in_flight,
258 inner: inner.clone(),
259 },
260 Self::Debounce {
261 id,
262 duration,
263 inner,
264 } => Self::Debounce {
265 id: *id,
266 duration: *duration,
267 inner: inner.clone(),
268 },
269 Self::Throttle {
270 id,
271 duration,
272 latest,
273 inner,
274 } => Self::Throttle {
275 id: *id,
276 duration: *duration,
277 latest: *latest,
278 inner: inner.clone(),
279 },
280 Self::Provide { env, inner } => Self::Provide {
281 env: env.clone(),
282 inner: inner.clone(),
283 },
284 Self::Retry { attempts, inner } => Self::Retry {
285 attempts: *attempts,
286 inner: inner.clone(),
287 },
288 Self::RetryBackoff {
289 attempts,
290 delay,
291 max_delay,
292 inner,
293 } => Self::RetryBackoff {
294 attempts: *attempts,
295 delay: *delay,
296 max_delay: *max_delay,
297 inner: inner.clone(),
298 },
299 Self::Timeout { duration, inner } => Self::Timeout {
300 duration: *duration,
301 inner: inner.clone(),
302 },
303 Self::Sequence(items) => Self::Sequence(items.clone()),
304 Self::Race(items) => Self::Race(items.clone()),
305 Self::Catch { inner, recover } => Self::Catch {
306 inner: inner.clone(),
307 recover: *recover,
308 },
309 Self::Cancel { id } => Self::Cancel { id: *id },
310 }
311 }
312}
313
314impl<M> Effect<M> {
315 pub fn none() -> Self {
316 Self::None
317 }
318
319 pub fn task(id: EffectId, run: TaskFn<M>) -> Self {
320 Self::Task { id, run }
321 }
322
323 pub fn from_fn<F>(run: F) -> Self
324 where
325 M: Send + 'static,
326 F: Fn() -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
327 + Send
328 + Sync
329 + 'static,
330 {
331 let id = register_task(run);
332 Self::RegisteredTask { id }
333 }
334
335 pub fn env_task(id: EffectId, run: EnvTaskFn<M>) -> Self {
336 Self::EnvTask { id, run }
337 }
338
339 pub fn from_env_fn<F>(run: F) -> Self
340 where
341 M: Send + 'static,
342 F: Fn(&Environment) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
343 + Send
344 + Sync
345 + 'static,
346 {
347 let id = register_env_task(run);
348 Self::RegisteredEnvTask { id }
349 }
350
351 pub fn from_run<F>(run: F) -> Self
352 where
353 M: Send + 'static,
354 F: Fn(RunSender<M>) -> Pin<Box<dyn Future<Output = Result<(), EffectError>> + Send>>
355 + Send
356 + Sync
357 + 'static,
358 {
359 let id = register_run(run);
360 Self::RegisteredRun { id }
361 }
362
363 pub fn task_try(id: EffectId, run: TaskFn<M>) -> Self {
365 Self::task(id, run)
366 }
367
368 pub fn result_task<T, E>(run: TaskFn<Result<T, E>>, on_ok: fn(T) -> M, on_err: fn(E) -> M) -> Self
370 where
371 T: Send + 'static,
372 E: Send + 'static,
373 M: Send + 'static,
374 {
375 Self::from_fn(move || {
376 let fut = run();
377 Box::pin(async move {
378 match fut.await {
379 Ok(Ok(value)) => Ok(on_ok(value)),
380 Ok(Err(err)) => Ok(on_err(err)),
381 Err(effect_err) => Err(effect_err),
382 }
383 })
384 })
385 }
386
387 pub fn batch(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
388 let mut effects: Vec<_> = effects.into_iter().collect();
389 match effects.len() {
390 0 => Self::None,
391 1 => effects.pop().map_or(Self::None, |single| single),
392 _ => Self::Batch(effects),
393 }
394 }
395
396 pub fn merge(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
397 Self::batch(effects)
398 }
399
400 pub fn concatenate(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
401 let effects: Vec<_> = effects.into_iter().collect();
402 if effects.is_empty() {
403 Self::None
404 } else {
405 Self::Sequence(effects)
406 }
407 }
408
409 pub fn map<N>(self, f: fn(M) -> N) -> Effect<N>
410 where
411 M: Send + 'static,
412 N: Send + 'static,
413 {
414 match self {
415 Self::None => Effect::None,
416 Self::Task { run, .. } => Effect::from_fn(move || {
417 let fut = run();
418 Box::pin(async move { fut.await.map(f) })
419 }),
420 Self::RegisteredTask { id } => Effect::from_fn(move || {
421 let fut = run_registered_task::<M>(id);
422 Box::pin(async move { fut.await.map(f) })
423 }),
424 Self::EnvTask { run, .. } => Effect::from_env_fn(move |env| {
425 let fut = run(env);
426 Box::pin(async move { fut.await.map(f) })
427 }),
428 Self::RegisteredEnvTask { id } => Effect::from_env_fn(move |env| {
429 let fut = run_registered_env_task::<M>(env, id);
430 Box::pin(async move { fut.await.map(f) })
431 }),
432 Self::RegisteredRun { id } => Effect::RegisteredRun { id },
433 Self::Batch(items) => Effect::Batch(items.into_iter().map(|e| e.map(f)).collect()),
434 Self::Cancellable {
435 id,
436 cancel_in_flight,
437 inner,
438 } => Effect::Cancellable {
439 id,
440 cancel_in_flight,
441 inner: Box::new(inner.map(f)),
442 },
443 Self::Debounce {
444 id,
445 duration,
446 inner,
447 } => Effect::Debounce {
448 id,
449 duration,
450 inner: Box::new(inner.map(f)),
451 },
452 Self::Throttle {
453 id,
454 duration,
455 latest,
456 inner,
457 } => Effect::Throttle {
458 id,
459 duration,
460 latest,
461 inner: Box::new(inner.map(f)),
462 },
463 Self::Provide { env, inner } => Effect::Provide {
464 env,
465 inner: Box::new(inner.map(f)),
466 },
467 Self::Retry { attempts, inner } => Effect::Retry {
468 attempts,
469 inner: Box::new(inner.map(f)),
470 },
471 Self::RetryBackoff {
472 attempts,
473 delay,
474 max_delay,
475 inner,
476 } => Effect::RetryBackoff {
477 attempts,
478 delay,
479 max_delay,
480 inner: Box::new(inner.map(f)),
481 },
482 Self::Timeout { duration, inner } => Effect::Timeout {
483 duration,
484 inner: Box::new(inner.map(f)),
485 },
486 Self::Sequence(items) => Effect::Sequence(items.into_iter().map(|e| e.map(f)).collect()),
487 Self::Race(items) => Effect::Race(items.into_iter().map(|e| e.map(f)).collect()),
488 Self::Catch { inner, recover: _ } => inner.map(f),
489 Self::Cancel { id } => Effect::Cancel { id },
490 }
491 }
492
493 pub fn cancellable(id: EffectId, inner: Effect<M>) -> Self {
494 Self::cancellable_with(id, true, inner)
495 }
496
497 pub fn cancellable_with(id: EffectId, cancel_in_flight: bool, inner: Effect<M>) -> Self {
498 Self::Cancellable {
499 id,
500 cancel_in_flight,
501 inner: Box::new(inner),
502 }
503 }
504
505 pub fn debounce(id: EffectId, duration: Duration, inner: Effect<M>) -> Self {
506 Self::Debounce {
507 id,
508 duration,
509 inner: Box::new(inner),
510 }
511 }
512
513 pub fn throttle(id: EffectId, duration: Duration, latest: bool, inner: Effect<M>) -> Self {
514 Self::Throttle {
515 id,
516 duration,
517 latest,
518 inner: Box::new(inner),
519 }
520 }
521
522 pub fn cancel(id: EffectId) -> Self {
523 Self::Cancel { id }
524 }
525
526 pub fn provide(env: Environment, inner: Effect<M>) -> Self {
527 Self::Provide {
528 env,
529 inner: Box::new(inner),
530 }
531 }
532
533 pub fn provide_dependency<D: Send + Sync + 'static>(value: D, inner: Effect<M>) -> Self {
535 Self::provide(Environment::from_values(DependencyValues::new().with(value)), inner)
536 }
537
538 pub fn retry(attempts: u32, inner: Effect<M>) -> Self {
539 Self::Retry {
540 attempts,
541 inner: Box::new(inner),
542 }
543 }
544
545 pub fn retry_backoff(
550 attempts: u32,
551 delay: Duration,
552 max_delay: Duration,
553 inner: Effect<M>,
554 ) -> Self {
555 Self::RetryBackoff {
556 attempts,
557 delay,
558 max_delay,
559 inner: Box::new(inner),
560 }
561 }
562
563 pub fn timeout(duration: Duration, inner: Effect<M>) -> Self {
564 Self::Timeout {
565 duration,
566 inner: Box::new(inner),
567 }
568 }
569
570 pub fn sequence(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
571 Self::concatenate(effects)
572 }
573
574 pub fn race(effects: impl IntoIterator<Item = Effect<M>>) -> Self {
575 let effects: Vec<_> = effects.into_iter().collect();
576 if effects.is_empty() {
577 Self::None
578 } else {
579 Self::Race(effects)
580 }
581 }
582
583 pub fn catch(inner: Effect<M>, recover: fn(EffectError) -> Effect<M>) -> Self {
584 Self::Catch {
585 inner: Box::new(inner),
586 recover,
587 }
588 }
589}
590
591impl<M> PartialEq for Effect<M> {
592 fn eq(&self, other: &Self) -> bool {
593 std::mem::discriminant(self) == std::mem::discriminant(other)
594 }
595}
596
597impl<M> std::fmt::Debug for Effect<M> {
598 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599 match self {
600 Self::None => write!(f, "Effect::None"),
601 Self::Task { id, .. } => write!(f, "Effect::Task({id})"),
602 Self::RegisteredTask { id } => write!(f, "Effect::RegisteredTask({id})"),
603 Self::EnvTask { id, .. } => write!(f, "Effect::EnvTask({id})"),
604 Self::RegisteredEnvTask { id } => write!(f, "Effect::RegisteredEnvTask({id})"),
605 Self::RegisteredRun { id } => write!(f, "Effect::RegisteredRun({id})"),
606 Self::Batch(n) => write!(f, "Effect::Batch({})", n.len()),
607 Self::Cancellable {
608 id,
609 cancel_in_flight,
610 ..
611 } => write!(f, "Effect::Cancellable({id}, in_flight={cancel_in_flight})"),
612 Self::Debounce { id, duration, .. } => {
613 write!(f, "Effect::Debounce({id}, {duration:?})")
614 }
615 Self::Throttle {
616 id,
617 duration,
618 latest,
619 ..
620 } => write!(f, "Effect::Throttle({id}, {duration:?}, latest={latest})"),
621 Self::Provide { .. } => write!(f, "Effect::Provide"),
622 Self::Retry { attempts, .. } => write!(f, "Effect::Retry({attempts})"),
623 Self::RetryBackoff {
624 attempts,
625 delay,
626 max_delay,
627 ..
628 } => write!(
629 f,
630 "Effect::RetryBackoff({attempts}, {delay:?}, max={max_delay:?})"
631 ),
632 Self::Timeout { duration, .. } => write!(f, "Effect::Timeout({duration:?})"),
633 Self::Sequence(n) => write!(f, "Effect::Sequence({})", n.len()),
634 Self::Race(n) => write!(f, "Effect::Race({})", n.len()),
635 Self::Catch { .. } => write!(f, "Effect::Catch"),
636 Self::Cancel { id } => write!(f, "Effect::Cancel({id})"),
637 }
638 }
639}
640
641pub(crate) fn run_leaf<M>(
642 effect: Effect<M>,
643 env: &Environment,
644) -> Pin<Box<dyn Future<Output = Result<M, EffectError>> + Send>>
645where
646 M: Send + 'static,
647{
648 match effect {
649 Effect::Task { run, .. } => run(),
650 Effect::RegisteredTask { id } => run_registered_task(id),
651 Effect::EnvTask { run, .. } => run(env),
652 Effect::RegisteredEnvTask { id } => run_registered_env_task(env, id),
653 Effect::Cancel { .. } => Box::pin(async move {
654 Err(EffectError::Cancelled)
655 }),
656 other => Box::pin(async move {
657 Err(EffectError::Other(format!("non-leaf effect: {other:?}")))
658 }),
659 }
660}
661
662#[cfg(test)]
663#[allow(dead_code, clippy::redundant_closure, clippy::type_complexity)]
664mod tests {
665 use super::*;
666
667 #[test]
668 fn merge_and_concatenate_constructors() {
669 let a = Effect::<i32>::none();
670 let b = Effect::<i32>::none();
671 assert!(matches!(Effect::merge([a, b]), Effect::Batch(_)));
672 assert!(matches!(Effect::concatenate([] as [Effect<i32>; 0]), Effect::None));
673 }
674
675 #[test]
676 fn result_task_maps_ok_and_err() {
677 fn load() -> Pin<Box<dyn Future<Output = Result<Result<i32, &'static str>, EffectError>> + Send>> {
678 Box::pin(async { Ok(Ok(7)) })
679 }
680 fn fail() -> Pin<Box<dyn Future<Output = Result<Result<i32, &'static str>, EffectError>> + Send>> {
681 Box::pin(async { Ok(Err("nope")) })
682 }
683 fn ok(n: i32) -> String {
684 format!("ok:{n}")
685 }
686 fn err(e: &'static str) -> String {
687 format!("err:{e}")
688 }
689
690 let ok_effect = Effect::result_task(load, ok, err);
691 let err_effect = Effect::result_task(fail, ok, err);
692 assert!(matches!(ok_effect, Effect::RegisteredTask { .. }));
693 assert!(matches!(err_effect, Effect::RegisteredTask { .. }));
694 }
695
696 #[test]
697 fn debounce_and_throttle_constructors() {
698 let inner = Effect::<i32>::task(1, || Box::pin(async { Ok(1) }));
699 assert!(matches!(
700 Effect::debounce(9, Duration::from_millis(50), inner.clone()),
701 Effect::Debounce { id: 9, .. }
702 ));
703 assert!(matches!(
704 Effect::throttle(9, Duration::from_millis(50), true, inner),
705 Effect::Throttle {
706 id: 9,
707 latest: true,
708 ..
709 }
710 ));
711 }
712
713 #[test]
714 fn retry_backoff_constructor() {
715 let inner = Effect::<i32>::none();
716 match Effect::retry_backoff(
717 3,
718 Duration::from_millis(50),
719 Duration::from_secs(2),
720 inner,
721 ) {
722 Effect::RetryBackoff {
723 attempts,
724 delay,
725 max_delay,
726 ..
727 } => {
728 assert_eq!(attempts, 3);
729 assert_eq!(delay, Duration::from_millis(50));
730 assert_eq!(max_delay, Duration::from_secs(2));
731 }
732 other => panic!("expected RetryBackoff, got {other:?}"),
733 }
734 }
735}