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