1use std::collections::{HashMap, HashSet};
2use std::sync::atomic::{AtomicBool, Ordering};
3use std::sync::Arc;
4use std::thread::{self, JoinHandle};
5use std::time::Duration;
6
7use crossbeam_channel::RecvTimeoutError;
8use parking_lot::Mutex;
9use tokio::runtime::Runtime as TokioRuntime;
10use tokio::task::JoinHandle as TokioJoinHandle;
11use tokio::time::timeout;
12
13use crate::bus::{Bus, BusSender};
14use crate::cmd::Cmd;
15use crate::effect::{
16 run_leaf, run_registered_env_task, run_registered_run, run_registered_task, Effect, EffectId,
17};
18use crate::env::Environment;
19use crate::error::EffectError;
20use crate::interp::flatten_effects;
21use crate::program::{Program, ReducerProgram};
22use crate::reducer::Reducer;
23use crate::store::{catch_reduce_panic, StoreBackend, StoreWorkUnwindGuard};
24use crate::sub::Sub;
25
26pub(crate) struct InterpreterState<M> {
27 pub cancel_tokens: Mutex<HashMap<EffectId, tokio::task::AbortHandle>>,
28 debounce_timers: Mutex<HashMap<EffectId, TokioJoinHandle<()>>>,
29 throttle_gates: Mutex<HashMap<EffectId, ThrottleGate<M>>>,
30}
31
32impl<M> InterpreterState<M> {
33 fn new() -> Arc<Self> {
34 Arc::new(Self {
35 cancel_tokens: Mutex::new(HashMap::new()),
36 debounce_timers: Mutex::new(HashMap::new()),
37 throttle_gates: Mutex::new(HashMap::new()),
38 })
39 }
40}
41
42struct ThrottleGate<M> {
43 latest: bool,
44 pending: Option<Effect<M>>,
45 timer: Option<TokioJoinHandle<()>>,
46}
47
48pub struct Runtime<S, M> {
50 pub state: Arc<Mutex<S>>,
51 pub bus: Bus<M>,
52 pub env: Environment,
53 backend: StoreBackend<S, M>,
54 shutdown: Arc<AtomicBool>,
55 _thread: Option<JoinHandle<()>>,
56 _tokio: Arc<TokioRuntime>,
57}
58
59impl<S, M> Runtime<S, M>
60where
61 S: Send + Sync + 'static,
62 M: Send + 'static,
63{
64 pub fn from_program(program: Program<S, M>, env: Environment, bus_capacity: usize) -> Self {
65 let update = program.update;
66 Self::bootstrap(
67 program.init,
68 move |state, msg| update(state, msg),
69 program.subscriptions,
70 env,
71 bus_capacity,
72 )
73 }
74
75 pub fn from_reducer_program<R>(
76 program: ReducerProgram<R>,
77 env: Environment,
78 bus_capacity: usize,
79 ) -> Self
80 where
81 R: Reducer<State = S, Action = M> + Send + Sync + 'static,
82 {
83 let reducer = Arc::new(program.reducer);
84 let init = program.init;
85 let subscriptions = program.subscriptions;
86 Self::bootstrap(
87 init,
88 move |state, msg| reducer.reduce(state, msg),
89 subscriptions,
90 env,
91 bus_capacity,
92 )
93 }
94
95 fn bootstrap(
96 init: fn() -> (S, Cmd<M>),
97 update: impl Fn(&mut S, M) -> Cmd<M> + Send + Sync + 'static,
98 subscriptions: fn(&S) -> Sub<M>,
99 env: Environment,
100 bus_capacity: usize,
101 ) -> Self {
102 let (initial_state, init_cmd) = init();
103 let state = Arc::new(Mutex::new(initial_state));
104 let bus = Bus::new(bus_capacity);
105 let shutdown = Arc::new(AtomicBool::new(false));
106 let tokio = Arc::new(
107 TokioRuntime::new().expect("failed to create Tokio runtime for rust-elm"),
108 );
109 let interpreter = Arc::new(InterpreterState {
110 cancel_tokens: Mutex::new(HashMap::new()),
111 debounce_timers: Mutex::new(HashMap::new()),
112 throttle_gates: Mutex::new(HashMap::new()),
113 });
114 let backend = StoreBackend::new(state.clone(), bus.sender(), interpreter);
115 let tx = bus.sender();
116 let init_handles = tokio.block_on(interpret_effects_async(
117 init_cmd.into_effects(),
118 tx.clone(),
119 env.clone(),
120 tokio.handle().clone(),
121 backend.clone(),
122 ));
123 tokio.handle().spawn(async move {
124 for join in init_handles {
125 let _ = join.await;
126 }
127 });
128
129 let receiver = bus.receiver().clone();
130 let subs_fn = subscriptions;
131 let state_for_thread = state.clone();
132 let env_for_thread = env.clone();
133 let shutdown_for_thread = shutdown.clone();
134 let tokio_for_thread = tokio.clone();
135 let backend_for_thread = backend.clone();
136 let tx_for_thread = tx;
137
138 let thread = thread::spawn(move || {
139 let rt = tokio_for_thread;
140 while !shutdown_for_thread.load(Ordering::Relaxed) {
141 match receiver.recv_timeout(Duration::from_millis(50)) {
142 Ok(msg) => {
143 let mut unwind = StoreWorkUnwindGuard::new(&backend_for_thread);
144 let cmd = {
145 let mut guard = state_for_thread.lock();
146 match catch_reduce_panic(&mut *guard, |s, a| update(s, a), msg) {
147 Ok(cmd) => cmd,
148 Err(_) => {
149 drop(guard);
150 backend_for_thread.notify_state();
151 continue;
152 }
153 }
154 };
155 unwind.disarm();
156 backend_for_thread.notify_state();
157 let handles = rt.block_on(interpret_effects_async(
158 cmd.into_effects(),
159 tx_for_thread.clone(),
160 env_for_thread.clone(),
161 rt.handle().clone(),
162 backend_for_thread.clone(),
163 ));
164 if handles.is_empty() {
165 backend_for_thread.end_store_work();
166 } else {
167 let backend_wait = backend_for_thread.clone();
168 rt.handle().spawn(async move {
169 for join in handles {
170 let _ = join.await;
171 }
172 backend_wait.end_store_work();
173 });
174 }
175
176 let sub = {
177 let guard = state_for_thread.lock();
178 subs_fn(&guard)
179 };
180 let mut active_subs = HashSet::new();
181 diff_subscriptions(&sub, &mut active_subs);
182 }
183 Err(RecvTimeoutError::Timeout) => continue,
184 Err(RecvTimeoutError::Disconnected) => break,
185 }
186 }
187 });
188
189 Self {
190 state,
191 bus,
192 env,
193 backend,
194 shutdown,
195 _thread: Some(thread),
196 _tokio: tokio,
197 }
198 }
199
200 pub fn store(&self) -> crate::Store<S, M>
201 where
202 S: Clone + Send + Sync + 'static,
203 {
204 self.backend.store()
205 }
206
207 pub fn dispatch(&self, msg: M) {
208 let _ = self.bus.sender().send_blocking(msg);
209 }
210
211 pub fn sender(&self) -> BusSender<M> {
212 self.bus.sender()
213 }
214
215 pub fn cancel(&self, id: EffectId) {
216 if let Some(handle) = self.backend.interpreter.cancel_tokens.lock().remove(&id) {
217 handle.abort();
218 }
219 }
220
221 pub fn shutdown(self) {
222 self.shutdown.store(true, Ordering::Relaxed);
223 if let Some(handle) = self._thread {
224 let _ = handle.join();
225 }
226 }
227}
228
229fn dispatch_from_effect<S, M>(
230 backend: &StoreBackend<S, M>,
231 tx: &BusSender<M>,
232 msg: M,
233) where
234 S: Send + 'static,
235 M: Send + 'static,
236{
237 backend.begin_store_work();
238 let _ = tx.send_blocking(msg);
239}
240
241async fn interpret_effects_async<S, M>(
242 effects: Vec<Effect<M>>,
243 tx: BusSender<M>,
244 env: Environment,
245 handle: tokio::runtime::Handle,
246 backend: StoreBackend<S, M>,
247) -> Vec<TokioJoinHandle<()>>
248where
249 S: Send + 'static,
250 M: Send + 'static,
251{
252 let interpreter = backend.interpreter.clone();
253 let mut handles = Vec::new();
254 for effect in effects {
255 for leaf in flatten_effects(effect) {
256 handles.extend(spawn_effect(
257 leaf,
258 tx.clone(),
259 env.clone(),
260 handle.clone(),
261 interpreter.clone(),
262 backend.clone(),
263 ));
264 }
265 }
266 handles
267}
268
269fn spawn_effect<S, M>(
270 effect: Effect<M>,
271 tx: BusSender<M>,
272 env: Environment,
273 handle: tokio::runtime::Handle,
274 interpreter: Arc<InterpreterState<M>>,
275 backend: StoreBackend<S, M>,
276) -> Vec<TokioJoinHandle<()>>
277where
278 S: Send + 'static,
279 M: Send + 'static,
280{
281 let mut batch = Vec::new();
282 spawn_effect_inner(
283 effect,
284 tx,
285 env,
286 handle,
287 interpreter,
288 backend,
289 &mut batch,
290 );
291 batch
292}
293
294fn track_spawn<M>(
295 join: TokioJoinHandle<()>,
296 cancel_id: Option<EffectId>,
297 interpreter: &InterpreterState<M>,
298 batch: &mut Vec<TokioJoinHandle<()>>,
299) {
300 if let Some(id) = cancel_id {
301 interpreter
302 .cancel_tokens
303 .lock()
304 .insert(id, join.abort_handle());
305 }
306 batch.push(join);
307}
308
309fn spawn_effect_inner<S, M>(
310 effect: Effect<M>,
311 tx: BusSender<M>,
312 env: Environment,
313 handle: tokio::runtime::Handle,
314 interpreter: Arc<InterpreterState<M>>,
315 backend: StoreBackend<S, M>,
316 batch: &mut Vec<TokioJoinHandle<()>>,
317) where
318 S: Send + 'static,
319 M: Send + 'static,
320{
321 match effect {
322 Effect::None => {}
323 Effect::Cancel { id } => {
324 if let Some(old) = interpreter.cancel_tokens.lock().remove(&id) {
325 old.abort();
326 }
327 }
328 Effect::Task { id, run } => {
329 let tx = tx.clone();
330 let backend = backend.clone();
331 track_spawn(
332 handle.spawn(async move {
333 if let Ok(msg) = run().await {
334 dispatch_from_effect(&backend, &tx, msg);
335 }
336 }),
337 Some(id),
338 &interpreter,
339 batch,
340 );
341 }
342 Effect::RegisteredTask { id } => {
343 let tx = tx.clone();
344 let backend = backend.clone();
345 track_spawn(
346 handle.spawn(async move {
347 if let Ok(msg) = run_registered_task::<M>(id).await {
348 dispatch_from_effect(&backend, &tx, msg);
349 }
350 }),
351 Some(id),
352 &interpreter,
353 batch,
354 );
355 }
356 Effect::RegisteredRun { id } => {
357 let tx = tx.clone();
358 track_spawn(
359 handle.spawn(async move {
360 let _ = run_registered_run::<M>(id, tx.clone()).await;
361 }),
362 Some(id),
363 &interpreter,
364 batch,
365 );
366 }
367 Effect::EnvTask { id, run } => {
368 let tx = tx.clone();
369 let env = env.clone();
370 let backend = backend.clone();
371 track_spawn(
372 handle.spawn(async move {
373 if let Ok(msg) = run(&env).await {
374 dispatch_from_effect(&backend, &tx, msg);
375 }
376 }),
377 Some(id),
378 &interpreter,
379 batch,
380 );
381 }
382 Effect::RegisteredEnvTask { id } => {
383 let tx = tx.clone();
384 let env = env.clone();
385 let backend = backend.clone();
386 track_spawn(
387 handle.spawn(async move {
388 if let Ok(msg) = run_registered_env_task::<M>(&env, id).await {
389 dispatch_from_effect(&backend, &tx, msg);
390 }
391 }),
392 Some(id),
393 &interpreter,
394 batch,
395 );
396 }
397 Effect::Batch(items) | Effect::Race(items) => {
398 for item in items {
399 spawn_effect_inner(
400 item,
401 tx.clone(),
402 env.clone(),
403 handle.clone(),
404 interpreter.clone(),
405 backend.clone(),
406 batch,
407 );
408 }
409 }
410 Effect::Sequence(items) => {
411 let tx = tx.clone();
412 let env = env.clone();
413 let backend = backend.clone();
414 track_spawn(
415 handle.spawn(async move {
416 for item in items {
417 if let Ok(msg) = run_effect_once(item, env.clone()).await {
418 dispatch_from_effect(&backend, &tx, msg);
419 }
420 }
421 }),
422 None,
423 &interpreter,
424 batch,
425 );
426 }
427 Effect::Cancellable {
428 id,
429 cancel_in_flight,
430 inner,
431 } => {
432 if cancel_in_flight {
433 if let Some(old) = interpreter.cancel_tokens.lock().remove(&id) {
434 old.abort();
435 }
436 }
437 spawn_effect_inner(*inner, tx, env, handle, interpreter, backend, batch);
438 }
439 Effect::Debounce { id, duration, inner } => {
440 if let Some(old) = interpreter.debounce_timers.lock().remove(&id) {
441 old.abort();
442 }
443 let inner = *inner;
444 let tx = tx.clone();
445 let env = env.clone();
446 let handle_worker = handle.clone();
447 let interpreter_worker = interpreter.clone();
448 let backend_worker = backend.clone();
449 let join = handle.spawn(async move {
450 tokio::time::sleep(duration).await;
451 interpreter_worker.debounce_timers.lock().remove(&id);
452 spawn_effect(
453 inner,
454 tx,
455 env,
456 handle_worker,
457 interpreter_worker,
458 backend_worker,
459 );
460 });
461 interpreter.debounce_timers.lock().insert(id, join);
462 }
463 Effect::Throttle {
464 id,
465 duration,
466 latest,
467 inner,
468 } => {
469 let mut gates = interpreter.throttle_gates.lock();
470 let gate = gates.entry(id).or_insert(ThrottleGate {
471 latest,
472 pending: None,
473 timer: None,
474 });
475 gate.latest = latest;
476
477 if gate.timer.is_none() {
478 if latest {
479 gate.pending = Some(*inner);
480 } else {
481 let tx_now = tx.clone();
482 let env_now = env.clone();
483 let handle_now = handle.clone();
484 let interpreter_now = interpreter.clone();
485 let backend_now = backend.clone();
486 batch.extend(spawn_effect(
487 *inner,
488 tx_now,
489 env_now,
490 handle_now,
491 interpreter_now,
492 backend_now,
493 ));
494 }
495 let tx_timer = tx.clone();
496 let env_timer = env.clone();
497 let handle_timer = handle.clone();
498 let interpreter_timer = interpreter.clone();
499 let backend_timer = backend.clone();
500 let join = handle.spawn(async move {
501 tokio::time::sleep(duration).await;
502 let effect_to_run = {
503 let mut gates = interpreter_timer.throttle_gates.lock();
504 if let Some(g) = gates.get_mut(&id) {
505 g.timer = None;
506 if g.latest {
507 g.pending.take()
508 } else {
509 None
510 }
511 } else {
512 None
513 }
514 };
515 if let Some(effect) = effect_to_run {
516 spawn_effect(
517 effect,
518 tx_timer,
519 env_timer,
520 handle_timer,
521 interpreter_timer,
522 backend_timer,
523 );
524 } else {
525 interpreter_timer.throttle_gates.lock().remove(&id);
526 }
527 });
528 gate.timer = Some(join);
529 } else if latest {
530 gate.pending = Some(*inner);
531 }
532 }
533 Effect::Provide { env: layer, inner } => {
534 let scoped = env.scoped_with(layer);
535 spawn_effect_inner(*inner, tx, scoped, handle, interpreter, backend, batch);
536 }
537 Effect::Retry { attempts, inner } => {
538 let tx = tx.clone();
539 let env = env.clone();
540 let backend = backend.clone();
541 let inner = *inner;
542 track_spawn(
543 handle.spawn(async move {
544 for _ in 0..attempts.max(1) {
545 match run_effect_once(inner.clone(), env.clone()).await {
546 Ok(msg) => {
547 dispatch_from_effect(&backend, &tx, msg);
548 break;
549 }
550 Err(_) => continue,
551 }
552 }
553 }),
554 None,
555 &interpreter,
556 batch,
557 );
558 }
559 Effect::Timeout { duration, inner } => {
560 let tx = tx.clone();
561 let env = env.clone();
562 let backend = backend.clone();
563 track_spawn(
564 handle.spawn(async move {
565 match timeout(duration, run_effect_once(*inner, env)).await {
566 Ok(Ok(msg)) => {
567 dispatch_from_effect(&backend, &tx, msg);
568 }
569 _ => {}
570 }
571 }),
572 None,
573 &interpreter,
574 batch,
575 );
576 }
577 Effect::Catch { inner, recover } => {
578 let tx = tx.clone();
579 let env = env.clone();
580 let handle_worker = handle.clone();
581 let backend_worker = backend.clone();
582 track_spawn(
583 handle.spawn(async move {
584 match run_effect_once(*inner, env.clone()).await {
585 Ok(msg) => {
586 dispatch_from_effect(&backend_worker, &tx, msg);
587 }
588 Err(err) => {
589 for join in interpret_effects_async(
590 flatten_effects(recover(err)),
591 tx,
592 env,
593 handle_worker.clone(),
594 backend_worker,
595 )
596 .await
597 {
598 let _ = join.await;
599 }
600 }
601 }
602 }),
603 None,
604 &interpreter,
605 batch,
606 );
607 }
608 }
609}
610
611async fn run_effect_once<M>(effect: Effect<M>, env: Environment) -> Result<M, EffectError>
612where
613 M: Send + 'static,
614{
615 run_leaf(effect, &env).await
616}
617
618fn diff_subscriptions<M>(sub: &Sub<M>, active: &mut HashSet<u64>) {
619 let mut seen = HashSet::new();
620 collect_sub_ids(sub, &mut seen);
621 active.retain(|id| seen.contains(id));
622 active.extend(seen);
623}
624
625fn collect_sub_ids<M>(sub: &Sub<M>, out: &mut HashSet<u64>) {
626 match sub {
627 Sub::None => {}
628 Sub::Tick { id, .. } | Sub::Stream { id, .. } | Sub::WebSocket { id, .. } => {
629 out.insert(*id);
630 }
631 Sub::MapMsg { inner, .. } => collect_sub_ids(inner, out),
632 Sub::Batch(items) => {
633 for item in items {
634 collect_sub_ids(item, out);
635 }
636 }
637 }
638}
639
640#[cfg(test)]
641mod tests {
642 use super::*;
643 use crate::effect::Effect;
644 use crate::panic_on_state_clone;
645
646 panic_on_state_clone! {
647 #[derive(Default)]
648 struct Counter {
649 n: i32,
650 }
651 }
652
653 fn init() -> (Counter, Cmd<i32>) {
654 (Counter::default(), Cmd::none())
655 }
656
657 fn update(s: &mut Counter, msg: i32) -> Cmd<i32> {
658 s.n += msg;
659 Cmd::none()
660 }
661
662 fn subs(_: &Counter) -> Sub<i32> {
663 Sub::none()
664 }
665
666 #[test]
667 fn runtime_applies_dispatched_messages() {
668 let program = Program::new(init, update, subs);
669 let runtime = Runtime::from_program(program, Environment::new(), 16);
670 runtime.dispatch(3);
671 runtime.dispatch(4);
672 std::thread::sleep(Duration::from_millis(200));
673 assert_eq!(runtime.state.lock().n, 7);
674 runtime.shutdown();
675 }
676
677 #[test]
678 fn runtime_runs_task_effects() {
679 fn update_with_effect(s: &mut Counter, msg: i32) -> Cmd<i32> {
680 if msg == 0 {
681 Cmd::single(Effect::task(1, || Box::pin(async { Ok(10) })))
682 } else {
683 s.n += msg;
684 Cmd::none()
685 }
686 }
687 let program = Program::new(init, update_with_effect, subs);
688 let runtime = Runtime::from_program(program, Environment::new(), 16);
689 runtime.dispatch(0);
690 std::thread::sleep(Duration::from_millis(200));
691 assert_eq!(runtime.state.lock().n, 10);
692 runtime.shutdown();
693 }
694
695 #[test]
696 fn runtime_catches_reduce_panic_and_unwinds_store_work() {
697 fn panicking_update(s: &mut Counter, msg: i32) -> Cmd<i32> {
698 s.n = 99;
699 if msg < 0 {
700 panic!("reduce panic");
701 }
702 s.n = msg;
703 Cmd::none()
704 }
705
706 let program = Program::new(init, panicking_update, subs);
707 let runtime = Runtime::from_program(program, Environment::new(), 16);
708 let store = runtime.store();
709 let task = store.send(-1);
710 assert!(task.finish().is_ok());
711 assert_eq!(runtime.state.lock().n, 99);
712
713 runtime.dispatch(4);
714 std::thread::sleep(Duration::from_millis(200));
715 assert_eq!(runtime.state.lock().n, 4);
716 runtime.shutdown();
717 }
718}