1use std::io;
7use std::time::Duration;
8
9use ratatui::backend::Backend;
10use ratatui::layout::Rect;
11use ratatui::{Frame, Terminal};
12use tokio::sync::mpsc;
13use tokio_util::sync::CancellationToken;
14
15use crate::bus::{process_raw_event, spawn_event_poller, RawEvent};
16use crate::debug::{DebugLayer, DebugState};
17use crate::effect::{DispatchResult, EffectStore, EffectStoreWithMiddleware};
18use crate::event::EventKind;
19use crate::store::{Middleware, Reducer, Store, StoreWithMiddleware};
20use crate::{Action, ActionParams};
21
22#[cfg(feature = "subscriptions")]
23use crate::subscriptions::Subscriptions;
24#[cfg(feature = "tasks")]
25use crate::tasks::TaskManager;
26
27#[derive(Debug, Clone, Copy)]
29pub struct PollerConfig {
30 pub poll_timeout: Duration,
32 pub loop_sleep: Duration,
34}
35
36impl Default for PollerConfig {
37 fn default() -> Self {
38 Self {
39 poll_timeout: Duration::from_millis(10),
40 loop_sleep: Duration::from_millis(16),
41 }
42 }
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
47pub struct EventOutcome<A> {
48 pub actions: Vec<A>,
50 pub needs_render: bool,
52}
53
54#[derive(Debug, Clone, Copy, Default)]
56pub struct RenderContext {
57 pub debug_enabled: bool,
59}
60
61impl RenderContext {
62 pub fn is_focused(self) -> bool {
64 !self.debug_enabled
65 }
66}
67
68impl<A> EventOutcome<A> {
69 pub fn ignored() -> Self {
71 Self {
72 actions: Vec::new(),
73 needs_render: false,
74 }
75 }
76
77 pub fn needs_render() -> Self {
79 Self {
80 actions: Vec::new(),
81 needs_render: true,
82 }
83 }
84
85 pub fn action(action: A) -> Self {
87 Self {
88 actions: vec![action],
89 needs_render: false,
90 }
91 }
92
93 pub fn actions<I>(actions: I) -> Self
95 where
96 I: IntoIterator<Item = A>,
97 {
98 Self {
99 actions: actions.into_iter().collect(),
100 needs_render: false,
101 }
102 }
103
104 pub fn with_render(mut self) -> Self {
106 self.needs_render = true;
107 self
108 }
109}
110
111impl<A> Default for EventOutcome<A> {
112 fn default() -> Self {
113 Self::ignored()
114 }
115}
116
117impl<A> From<A> for EventOutcome<A> {
118 fn from(action: A) -> Self {
119 Self::action(action)
120 }
121}
122
123impl<A> From<Vec<A>> for EventOutcome<A> {
124 fn from(actions: Vec<A>) -> Self {
125 Self {
126 actions,
127 needs_render: false,
128 }
129 }
130}
131
132impl<A> From<Option<A>> for EventOutcome<A> {
133 fn from(action: Option<A>) -> Self {
134 match action {
135 Some(action) => Self::action(action),
136 None => Self::ignored(),
137 }
138 }
139}
140
141trait DebugAdapter<S, A>: 'static {
142 fn render(
143 &mut self,
144 frame: &mut Frame,
145 state: &S,
146 render_ctx: RenderContext,
147 render_fn: &mut dyn FnMut(&mut Frame, Rect, &S, RenderContext),
148 );
149
150 fn handle_event(
151 &mut self,
152 event: &EventKind,
153 state: &S,
154 action_tx: &mpsc::UnboundedSender<A>,
155 ) -> Option<bool>;
156
157 fn log_action(&mut self, action: &A);
158 fn is_enabled(&self) -> bool;
159}
160
161impl<S, A> DebugAdapter<S, A> for DebugLayer<A>
162where
163 S: DebugState,
164 A: Action + ActionParams,
165{
166 fn render(
167 &mut self,
168 frame: &mut Frame,
169 state: &S,
170 render_ctx: RenderContext,
171 render_fn: &mut dyn FnMut(&mut Frame, Rect, &S, RenderContext),
172 ) {
173 self.render_state(frame, state, |f, area| {
174 render_fn(f, area, state, render_ctx);
175 });
176 }
177
178 fn handle_event(
179 &mut self,
180 event: &EventKind,
181 state: &S,
182 action_tx: &mpsc::UnboundedSender<A>,
183 ) -> Option<bool> {
184 self.handle_event_with_state(event, state)
185 .dispatch_queued(|action| {
186 let _ = action_tx.send(action);
187 })
188 }
189
190 fn log_action(&mut self, action: &A) {
191 DebugLayer::log_action(self, action);
192 }
193
194 fn is_enabled(&self) -> bool {
195 DebugLayer::is_enabled(self)
196 }
197}
198
199pub trait DispatchStore<S, A: Action> {
201 fn dispatch(&mut self, action: A) -> bool;
203 fn state(&self) -> &S;
205}
206
207impl<S, A: Action> DispatchStore<S, A> for Store<S, A> {
208 fn dispatch(&mut self, action: A) -> bool {
209 Store::dispatch(self, action)
210 }
211
212 fn state(&self) -> &S {
213 Store::state(self)
214 }
215}
216
217impl<S, A: Action, M: Middleware<A>> DispatchStore<S, A> for StoreWithMiddleware<S, A, M> {
218 fn dispatch(&mut self, action: A) -> bool {
219 StoreWithMiddleware::dispatch(self, action)
220 }
221
222 fn state(&self) -> &S {
223 StoreWithMiddleware::state(self)
224 }
225}
226
227pub trait EffectStoreLike<S, A: Action, E> {
229 fn dispatch(&mut self, action: A) -> DispatchResult<E>;
231 fn state(&self) -> &S;
233}
234
235impl<S, A: Action, E> EffectStoreLike<S, A, E> for EffectStore<S, A, E> {
236 fn dispatch(&mut self, action: A) -> DispatchResult<E> {
237 EffectStore::dispatch(self, action)
238 }
239
240 fn state(&self) -> &S {
241 EffectStore::state(self)
242 }
243}
244
245impl<S, A: Action, E, M: Middleware<A>> EffectStoreLike<S, A, E>
246 for EffectStoreWithMiddleware<S, A, E, M>
247{
248 fn dispatch(&mut self, action: A) -> DispatchResult<E> {
249 EffectStoreWithMiddleware::dispatch(self, action)
250 }
251
252 fn state(&self) -> &S {
253 EffectStoreWithMiddleware::state(self)
254 }
255}
256
257pub struct DispatchRuntime<S, A: Action, St: DispatchStore<S, A> = Store<S, A>> {
259 store: St,
260 action_tx: mpsc::UnboundedSender<A>,
261 action_rx: mpsc::UnboundedReceiver<A>,
262 poller_config: PollerConfig,
263 debug: Option<Box<dyn DebugAdapter<S, A>>>,
264 should_render: bool,
265 _state: std::marker::PhantomData<S>,
266}
267
268impl<S: 'static, A: Action> DispatchRuntime<S, A, Store<S, A>> {
269 pub fn new(state: S, reducer: Reducer<S, A>) -> Self {
271 Self::from_store(Store::new(state, reducer))
272 }
273}
274
275impl<S: 'static, A: Action, St: DispatchStore<S, A>> DispatchRuntime<S, A, St> {
276 pub fn from_store(store: St) -> Self {
278 let (action_tx, action_rx) = mpsc::unbounded_channel();
279 Self {
280 store,
281 action_tx,
282 action_rx,
283 poller_config: PollerConfig::default(),
284 debug: None,
285 should_render: true,
286 _state: std::marker::PhantomData,
287 }
288 }
289
290 pub fn with_debug(mut self, debug: DebugLayer<A>) -> Self
292 where
293 S: DebugState,
294 A: ActionParams,
295 {
296 let adapter: Box<dyn DebugAdapter<S, A>> = Box::new(debug);
297 self.debug = Some(adapter);
298 self
299 }
300
301 pub fn with_event_poller(mut self, config: PollerConfig) -> Self {
303 self.poller_config = config;
304 self
305 }
306
307 pub fn enqueue(&self, action: A) {
309 let _ = self.action_tx.send(action);
310 }
311
312 pub fn action_tx(&self) -> mpsc::UnboundedSender<A> {
314 self.action_tx.clone()
315 }
316
317 pub fn state(&self) -> &S {
319 self.store.state()
320 }
321
322 pub async fn run<B, FRender, FEvent, FQuit, R>(
324 &mut self,
325 terminal: &mut Terminal<B>,
326 mut render: FRender,
327 mut map_event: FEvent,
328 mut should_quit: FQuit,
329 ) -> io::Result<()>
330 where
331 B: Backend,
332 FRender: FnMut(&mut Frame, Rect, &S, RenderContext),
333 FEvent: FnMut(&EventKind, &S) -> R,
334 R: Into<EventOutcome<A>>,
335 FQuit: FnMut(&A) -> bool,
336 {
337 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
338 let cancel_token = CancellationToken::new();
339 let _handle = spawn_event_poller(
340 event_tx,
341 self.poller_config.poll_timeout,
342 self.poller_config.loop_sleep,
343 cancel_token.clone(),
344 );
345
346 loop {
347 if self.should_render {
348 let state = self.store.state();
349 let render_ctx = RenderContext {
350 debug_enabled: self
351 .debug
352 .as_ref()
353 .map(|debug| debug.is_enabled())
354 .unwrap_or(false),
355 };
356 terminal.draw(|frame| {
357 if let Some(debug) = self.debug.as_mut() {
358 let mut render_fn =
359 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
360 render(f, area, state, ctx);
361 };
362 debug.render(frame, state, render_ctx, &mut render_fn);
363 } else {
364 render(frame, frame.area(), state, render_ctx);
365 }
366 })?;
367 self.should_render = false;
368 }
369
370 tokio::select! {
371 Some(raw_event) = event_rx.recv() => {
372 let event = process_raw_event(raw_event);
373
374 if let Some(debug) = self.debug.as_mut() {
375 if let Some(needs_render) =
376 debug.handle_event(&event, self.store.state(), &self.action_tx)
377 {
378 self.should_render = needs_render;
379 continue;
380 }
381 }
382
383 let outcome: EventOutcome<A> = map_event(&event, self.store.state()).into();
384 if outcome.needs_render {
385 self.should_render = true;
386 }
387 for action in outcome.actions {
388 let _ = self.action_tx.send(action);
389 }
390 }
391
392 Some(action) = self.action_rx.recv() => {
393 if should_quit(&action) {
394 break;
395 }
396
397 if let Some(debug) = self.debug.as_mut() {
398 debug.log_action(&action);
399 }
400
401 self.should_render = self.store.dispatch(action);
402 }
403
404 else => {
405 break;
406 }
407 }
408 }
409
410 cancel_token.cancel();
411 Ok(())
412 }
413}
414
415pub struct EffectContext<'a, A: Action> {
417 action_tx: &'a mpsc::UnboundedSender<A>,
418 #[cfg(feature = "tasks")]
419 tasks: &'a mut TaskManager<A>,
420 #[cfg(feature = "subscriptions")]
421 subscriptions: &'a mut Subscriptions<A>,
422}
423
424impl<'a, A: Action> EffectContext<'a, A> {
425 pub fn emit(&self, action: A) {
427 let _ = self.action_tx.send(action);
428 }
429
430 pub fn action_tx(&self) -> &mpsc::UnboundedSender<A> {
432 self.action_tx
433 }
434
435 #[cfg(feature = "tasks")]
437 pub fn tasks(&mut self) -> &mut TaskManager<A> {
438 self.tasks
439 }
440
441 #[cfg(feature = "subscriptions")]
443 pub fn subscriptions(&mut self) -> &mut Subscriptions<A> {
444 self.subscriptions
445 }
446}
447
448pub struct EffectRuntime<S, A: Action, E, St: EffectStoreLike<S, A, E> = EffectStore<S, A, E>> {
450 store: St,
451 action_tx: mpsc::UnboundedSender<A>,
452 action_rx: mpsc::UnboundedReceiver<A>,
453 poller_config: PollerConfig,
454 debug: Option<Box<dyn DebugAdapter<S, A>>>,
455 should_render: bool,
456 #[cfg(feature = "tasks")]
457 tasks: TaskManager<A>,
458 #[cfg(feature = "subscriptions")]
459 subscriptions: Subscriptions<A>,
460 _state: std::marker::PhantomData<S>,
461 _effect: std::marker::PhantomData<E>,
462}
463
464impl<S: 'static, A: Action, E> EffectRuntime<S, A, E, EffectStore<S, A, E>> {
465 pub fn new(state: S, reducer: crate::effect::EffectReducer<S, A, E>) -> Self {
467 Self::from_store(EffectStore::new(state, reducer))
468 }
469}
470
471impl<S: 'static, A: Action, E, St: EffectStoreLike<S, A, E>> EffectRuntime<S, A, E, St> {
472 pub fn from_store(store: St) -> Self {
474 let (action_tx, action_rx) = mpsc::unbounded_channel();
475
476 #[cfg(feature = "tasks")]
477 let tasks = TaskManager::new(action_tx.clone());
478 #[cfg(feature = "subscriptions")]
479 let subscriptions = Subscriptions::new(action_tx.clone());
480
481 Self {
482 store,
483 action_tx,
484 action_rx,
485 poller_config: PollerConfig::default(),
486 debug: None,
487 should_render: true,
488 #[cfg(feature = "tasks")]
489 tasks,
490 #[cfg(feature = "subscriptions")]
491 subscriptions,
492 _state: std::marker::PhantomData,
493 _effect: std::marker::PhantomData,
494 }
495 }
496
497 pub fn with_debug(mut self, debug: DebugLayer<A>) -> Self
499 where
500 S: DebugState,
501 A: ActionParams,
502 {
503 let debug = {
504 let debug = debug;
505 #[cfg(feature = "tasks")]
506 let debug = debug.with_task_manager(&self.tasks);
507 #[cfg(feature = "subscriptions")]
508 let debug = debug.with_subscriptions(&self.subscriptions);
509 debug
510 };
511 let adapter: Box<dyn DebugAdapter<S, A>> = Box::new(debug);
512 self.debug = Some(adapter);
513 self
514 }
515
516 pub fn with_event_poller(mut self, config: PollerConfig) -> Self {
518 self.poller_config = config;
519 self
520 }
521
522 pub fn enqueue(&self, action: A) {
524 let _ = self.action_tx.send(action);
525 }
526
527 pub fn action_tx(&self) -> mpsc::UnboundedSender<A> {
529 self.action_tx.clone()
530 }
531
532 pub fn state(&self) -> &S {
534 self.store.state()
535 }
536
537 #[cfg(feature = "tasks")]
539 pub fn tasks(&mut self) -> &mut TaskManager<A> {
540 &mut self.tasks
541 }
542
543 #[cfg(feature = "subscriptions")]
545 pub fn subscriptions(&mut self) -> &mut Subscriptions<A> {
546 &mut self.subscriptions
547 }
548
549 #[cfg(all(feature = "tasks", feature = "subscriptions"))]
550 fn effect_context(&mut self) -> EffectContext<'_, A> {
551 EffectContext {
552 action_tx: &self.action_tx,
553 tasks: &mut self.tasks,
554 subscriptions: &mut self.subscriptions,
555 }
556 }
557
558 #[cfg(all(feature = "tasks", not(feature = "subscriptions")))]
559 fn effect_context(&mut self) -> EffectContext<'_, A> {
560 EffectContext {
561 action_tx: &self.action_tx,
562 tasks: &mut self.tasks,
563 }
564 }
565
566 #[cfg(all(not(feature = "tasks"), feature = "subscriptions"))]
567 fn effect_context(&mut self) -> EffectContext<'_, A> {
568 EffectContext {
569 action_tx: &self.action_tx,
570 subscriptions: &mut self.subscriptions,
571 }
572 }
573
574 #[cfg(all(not(feature = "tasks"), not(feature = "subscriptions")))]
575 fn effect_context(&mut self) -> EffectContext<'_, A> {
576 EffectContext {
577 action_tx: &self.action_tx,
578 }
579 }
580
581 pub async fn run<B, FRender, FEvent, FQuit, FEffect, R>(
583 &mut self,
584 terminal: &mut Terminal<B>,
585 mut render: FRender,
586 mut map_event: FEvent,
587 mut should_quit: FQuit,
588 mut handle_effect: FEffect,
589 ) -> io::Result<()>
590 where
591 B: Backend,
592 FRender: FnMut(&mut Frame, Rect, &S, RenderContext),
593 FEvent: FnMut(&EventKind, &S) -> R,
594 R: Into<EventOutcome<A>>,
595 FQuit: FnMut(&A) -> bool,
596 FEffect: FnMut(E, &mut EffectContext<A>),
597 {
598 let (event_tx, mut event_rx) = mpsc::unbounded_channel::<RawEvent>();
599 let cancel_token = CancellationToken::new();
600 let _handle = spawn_event_poller(
601 event_tx,
602 self.poller_config.poll_timeout,
603 self.poller_config.loop_sleep,
604 cancel_token.clone(),
605 );
606
607 loop {
608 if self.should_render {
609 let state = self.store.state();
610 let render_ctx = RenderContext {
611 debug_enabled: self
612 .debug
613 .as_ref()
614 .map(|debug| debug.is_enabled())
615 .unwrap_or(false),
616 };
617 terminal.draw(|frame| {
618 if let Some(debug) = self.debug.as_mut() {
619 let mut render_fn =
620 |f: &mut Frame, area: Rect, state: &S, ctx: RenderContext| {
621 render(f, area, state, ctx);
622 };
623 debug.render(frame, state, render_ctx, &mut render_fn);
624 } else {
625 render(frame, frame.area(), state, render_ctx);
626 }
627 })?;
628 self.should_render = false;
629 }
630
631 tokio::select! {
632 Some(raw_event) = event_rx.recv() => {
633 let event = process_raw_event(raw_event);
634
635 if let Some(debug) = self.debug.as_mut() {
636 if let Some(needs_render) =
637 debug.handle_event(&event, self.store.state(), &self.action_tx)
638 {
639 self.should_render = needs_render;
640 continue;
641 }
642 }
643
644 let outcome: EventOutcome<A> = map_event(&event, self.store.state()).into();
645 if outcome.needs_render {
646 self.should_render = true;
647 }
648 for action in outcome.actions {
649 let _ = self.action_tx.send(action);
650 }
651 }
652
653 Some(action) = self.action_rx.recv() => {
654 if should_quit(&action) {
655 break;
656 }
657
658 if let Some(debug) = self.debug.as_mut() {
659 debug.log_action(&action);
660 }
661
662 let result = self.store.dispatch(action);
663 if result.has_effects() {
664 let mut ctx = self.effect_context();
665 for effect in result.effects {
666 handle_effect(effect, &mut ctx);
667 }
668 }
669 self.should_render = result.changed;
670 }
671
672 else => {
673 break;
674 }
675 }
676 }
677
678 cancel_token.cancel();
679 #[cfg(feature = "subscriptions")]
680 self.subscriptions.cancel_all();
681 #[cfg(feature = "tasks")]
682 self.tasks.cancel_all();
683
684 Ok(())
685 }
686}