#[cfg(feature = "no_std")]
use alloc::boxed::Box;
use core::future::Future;
use async_channel::{bounded, Receiver};
use crate::{Effect, Event as EventTrait};
use crate::{Emitter, MvuLogic, Renderer};
pub const DEFAULT_EVENT_CAPACITY: usize = 32;
pub struct MvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
init_model: Model,
logic: Logic,
renderer: Render,
spawner: Spawn,
capacity: usize,
_event: core::marker::PhantomData<Event>,
_props: core::marker::PhantomData<Props>,
}
impl<Event, Model, Props, Logic, Render, Spawn>
MvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn build(self) -> MvuRuntime<Event, Model, Props, Logic, Render, Spawn> {
let (event_sender, event_receiver) = bounded(self.capacity);
let emitter = Emitter::new(event_sender);
MvuRuntime {
logic: self.logic,
renderer: self.renderer,
event_receiver,
model: self.init_model,
emitter,
spawner: self.spawner,
_props: core::marker::PhantomData,
}
}
}
use core::pin::Pin;
pub trait Spawner {
fn spawn(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>);
}
impl<F> Spawner for F
where
F: Fn(Pin<Box<dyn Future<Output = ()> + Send>>),
{
fn spawn(&self, future: Pin<Box<dyn Future<Output = ()> + Send>>) {
self(future)
}
}
pub struct MvuRuntime<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
logic: Logic,
renderer: Render,
event_receiver: Receiver<Event>,
model: Model,
emitter: Emitter<Event>,
spawner: Spawn,
_props: core::marker::PhantomData<Props>,
}
impl<Event, Model, Props, Logic, Render, Spawn>
MvuRuntime<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
pub fn builder(
init_model: Model,
logic: Logic,
renderer: Render,
spawner: Spawn,
) -> MvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn> {
MvuRuntimeBuilder {
init_model,
logic,
renderer,
spawner,
capacity: DEFAULT_EVENT_CAPACITY,
_event: core::marker::PhantomData,
_props: core::marker::PhantomData,
}
}
pub async fn run(mut self) {
let (init_model, init_effect) = self.logic.init(self.model.clone());
let initial_props = {
let emitter = &self.emitter;
self.logic.view(&init_model, emitter)
};
self.renderer.render(initial_props);
Self::spawn_effect(&self.spawner, &self.emitter, init_effect);
while let Ok(event) = self.event_receiver.recv().await {
self.step(event);
}
}
fn step(&mut self, event: Event) {
let (new_model, effect) = self.logic.update(event, &self.model);
let props = self.logic.view(&new_model, &self.emitter);
self.renderer.render(props);
self.model = new_model;
Self::spawn_effect(&self.spawner, &self.emitter, effect);
}
pub fn spawn_effect(spawner: &Spawn, emitter: &Emitter<Event>, effect: Effect<Event>) {
match effect {
Effect::None => {}
Effect::Just(event) => {
let emitter = emitter.clone();
spawner.spawn(Box::pin(async move { emitter.emit(event).await }));
}
Effect::Async(boxed_future) => spawner.spawn(boxed_future.call_box(emitter)),
Effect::Batch(effects) => {
for effect in effects {
Self::spawn_effect(spawner, emitter, effect);
}
}
}
}
}
#[cfg(any(test, feature = "testing"))]
pub fn test_spawner_fn(fut: Pin<Box<dyn Future<Output = ()> + Send>>) {
futures::executor::block_on(fut);
}
#[cfg(any(test, feature = "testing"))]
pub fn create_test_spawner() -> fn(Pin<Box<dyn Future<Output = ()> + Send>>) {
test_spawner_fn
}
#[cfg(any(test, feature = "testing"))]
pub struct TestMvuDriver<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
_runtime: TestMvuRuntime<Event, Model, Props, Logic, Render, Spawn>,
}
#[cfg(any(test, feature = "testing"))]
impl<Event, Model, Props, Logic, Render, Spawn>
TestMvuDriver<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
pub fn process_events(&mut self) {
self._runtime.process_queued_events();
}
}
#[cfg(any(test, feature = "testing"))]
pub struct TestMvuRuntime<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
runtime: MvuRuntime<Event, Model, Props, Logic, Render, Spawn>,
}
#[cfg(any(test, feature = "testing"))]
pub struct TestMvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
init_model: Model,
logic: Logic,
renderer: Render,
spawner: Spawn,
capacity: usize,
_event: core::marker::PhantomData<Event>,
_props: core::marker::PhantomData<Props>,
}
#[cfg(any(test, feature = "testing"))]
impl<Event, Model, Props, Logic, Render, Spawn>
TestMvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
pub fn capacity(mut self, capacity: usize) -> Self {
self.capacity = capacity;
self
}
pub fn build(self) -> TestMvuRuntime<Event, Model, Props, Logic, Render, Spawn> {
let (event_sender, event_receiver) = bounded(self.capacity);
TestMvuRuntime {
runtime: MvuRuntime {
logic: self.logic,
renderer: self.renderer,
event_receiver,
model: self.init_model,
emitter: Emitter::new(event_sender),
spawner: self.spawner,
_props: core::marker::PhantomData,
},
}
}
}
#[cfg(any(test, feature = "testing"))]
impl<Event, Model, Props, Logic, Render, Spawn>
TestMvuRuntime<Event, Model, Props, Logic, Render, Spawn>
where
Event: EventTrait,
Model: Clone + 'static,
Props: 'static,
Logic: MvuLogic<Event, Model, Props>,
Render: Renderer<Props>,
Spawn: Spawner,
{
pub fn builder(
init_model: Model,
logic: Logic,
renderer: Render,
spawner: Spawn,
) -> TestMvuRuntimeBuilder<Event, Model, Props, Logic, Render, Spawn> {
TestMvuRuntimeBuilder {
init_model,
logic,
renderer,
spawner,
capacity: DEFAULT_EVENT_CAPACITY,
_event: core::marker::PhantomData,
_props: core::marker::PhantomData,
}
}
pub fn run(mut self) -> TestMvuDriver<Event, Model, Props, Logic, Render, Spawn> {
let (init_model, init_effect) = self.runtime.logic.init(self.runtime.model.clone());
let initial_props = { self.runtime.logic.view(&init_model, &self.runtime.emitter) };
self.runtime.renderer.render(initial_props);
MvuRuntime::<Event, Model, Props, Logic, Render, Spawn>::spawn_effect(
&self.runtime.spawner,
&self.runtime.emitter,
init_effect,
);
TestMvuDriver { _runtime: self }
}
fn process_queued_events(&mut self) {
while let Ok(event) = self.runtime.event_receiver.try_recv() {
self.runtime.step(event);
}
}
}