oxide_mvu/
lib.rs

1#![cfg_attr(feature = "no_std", no_std)]
2
3//! A lightweight Model-View-Update (MVU) runtime for Rust with `no_std` support.
4//!
5//! Implements the MVU pattern for building predictable, testable applications with
6//! unidirectional data flow and controlled side effects.
7//!
8//! ## Example
9//!
10//! ```rust
11//! use oxide_mvu::{Emitter, Effect, MvuLogic, MvuRuntime, Renderer};
12//!
13//! #[derive(Clone)]
14//! enum Event {
15//!     AccumulateClicked,
16//! }
17//!
18//! #[derive(Clone)]
19//! struct Model {
20//!     count: i32,
21//! }
22//!
23//! struct Props {
24//!     count: i32,
25//!     on_accumulate_click: Box<dyn Fn()>,
26//! }
27//!
28//! struct MyLogic;
29//!
30//! impl MvuLogic<Event, Model, Props> for MyLogic {
31//!     fn init(&self, model: Model) -> (Model, Effect<Event>) {
32//!         (model, Effect::none())
33//!     }
34//!
35//!     fn update(&self, event: Event, model: &Model) -> (Model, Effect<Event>) {
36//!         match event {
37//!             Event::AccumulateClicked => {
38//!                 let new_model = Model {
39//!                     count: model.count + 1,
40//!                     ..model.clone()
41//!                 };
42//!                 (new_model, Effect::none())
43//!             }
44//!         }
45//!     }
46//!
47//!     fn view(&self, model: &Model, emitter: &Emitter<Event>) -> Props {
48//!         let emitter = emitter.clone();
49//!         Props {
50//!             count: model.count,
51//!             on_accumulate_click: Box::new(move || {
52//!                 emitter.try_emit(Event::AccumulateClicked);
53//!             }),
54//!         }
55//!     }
56//! }
57//!
58//! struct MyRenderer;
59//!
60//! impl Renderer<Props> for MyRenderer {
61//!     fn render(&mut self, _props: Props) {}
62//! }
63//!
64//! async fn main_async() {
65//!     // Create a spawner for your async runtime.
66//!     // This is how `Effect`s are executed.
67//!     let spawner = |fut| {
68//!         // Spawn the future on your chosen runtime.
69//!         // Examples:
70//!         // tokio::spawn(fut);
71//!         // async_std::task::spawn(fut);
72//!         let _ = fut;
73//!     };
74//!
75//!     let runtime = MvuRuntime::new(
76//!         Model { count: 0 },
77//!         MyLogic,
78//!         MyRenderer,
79//!         spawner,
80//!     );
81//!
82//!     // `run()` returns a Future representing the event loop.
83//!     // It must be awaited inside an async context.
84//!     runtime.run().await;
85//! }
86//! ```
87//!
88//! In a real application, `main_async` would be executed by your async runtime
89//! (e.g. via `#[tokio::main]`, `async_std::main`, or an embedded executor).
90
91#[cfg(feature = "no_std")]
92extern crate alloc;
93
94/// Trait alias for event type constraints.
95///
96/// All events must implement these bounds to work with the MVU runtime.
97pub trait Event: Send + Sync + Clone + 'static {}
98
99/// Blanket implementation for any type that satisfies the bounds.
100impl<T> Event for T where T: Send + Sync + Clone + 'static {}
101
102// Module declarations
103mod effect;
104mod emitter;
105mod logic;
106mod renderer;
107mod runtime;
108
109// Public re-exports
110pub use effect::Effect;
111pub use emitter::Emitter;
112pub use logic::MvuLogic;
113pub use renderer::Renderer;
114pub use runtime::{MvuRuntime, Spawner};
115
116// Test utilities (only available with 'testing' feature or during tests)
117#[cfg(any(test, feature = "testing"))]
118pub use renderer::TestRenderer;
119#[cfg(any(test, feature = "testing"))]
120pub use runtime::{create_test_spawner, TestMvuDriver, TestMvuRuntime};