Skip to content

Commit

Permalink
Implement output type adaptation
Browse files Browse the repository at this point in the history
  • Loading branch information
kmicklas committed Jun 21, 2024
1 parent 9a38ad4 commit 094ead9
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 4 deletions.
17 changes: 14 additions & 3 deletions examples/tutorial/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::BTreeMap;

use ravel::{with, with_local};
use ravel::{adapt_ref, with, with_local};
use ravel_web::{
any, attr,
collections::{btree_map, slice},
Expand Down Expand Up @@ -123,11 +123,22 @@ fn local_state() -> View!(Model) {
// We provide an initialization callback, which is only run when the
// component is constructed for the first time.
|| 0,
// Inside the body, we have a reference to the current local state.
// Because we now have access to both the global and local state types
// we need to produce a `View!((Model, usize))`.
|cx, local_count| {
// Inside the body, we have a reference to the current local state.
/// Displays the value. Since this returns `View!(Model)` (like any
/// other component in our application), we can no longer call it
/// directly here.
fn display_counter(count: usize) -> View!(Model) {
el::p(("Local count: ", display(count)))
}

cx.build((
el::h2("Local state"),
el::p(("Local count: ", display(*local_count))),
// To use [`display_counter`], we need to "adapt" it to the
// correct type.
adapt_ref(display_counter(*local_count), |(model, _)| model),
el::p(el::button((
"Increment local count",
// Although we have a reference to the current value, we
Expand Down
3 changes: 2 additions & 1 deletion ravel-web/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::sync::Arc;

use atomic_waker::AtomicWaker;
use dom::Position;
use ravel::{Builder, Cx, CxRep, WithLocalState};
use ravel::{AdaptState, Builder, Cx, CxRep, WithLocalState};

mod any;
pub mod attr;
Expand Down Expand Up @@ -45,6 +45,7 @@ pub struct RebuildCx<'cx> {
pub trait ViewMarker {}

impl<T: 'static, S: ViewMarker> ViewMarker for WithLocalState<T, S> {}
impl<S: ViewMarker, F> ViewMarker for AdaptState<S, F> {}

macro_rules! tuple_state {
($($a:ident),*) => {
Expand Down
109 changes: 109 additions & 0 deletions ravel/src/adapt.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::marker::PhantomData;

use crate::{Builder, CxRep, State};

/// A [`Builder`] created from [`adapt`].
pub struct Adapt<B, F, S, Output> {
builder: B,
f: F,
phantom: PhantomData<(S, Output)>,
}

impl<R: CxRep, B, F, S, Output> Builder<R> for Adapt<B, F, S, Output>
where
B: Builder<R>,
{
type State = AdaptState<B::State, F>;

fn build(self, cx: R::BuildCx<'_>) -> Self::State {
AdaptState {
inner: self.builder.build(cx),
f: self.f,
}
}

fn rebuild(self, cx: R::RebuildCx<'_>, state: &mut Self::State) {
state.f = self.f;
self.builder.rebuild(cx, &mut state.inner)
}
}

/// A reference to a [`State`] which must be [`State::run`].
pub struct Thunk<'s, S> {
state: &'s mut S,
}

/// The result of [`Thunk::run`].
pub struct ThunkResult<S> {
phantom: PhantomData<S>,
}

impl<'s, S> Thunk<'s, S> {
/// Consumes the [`Thunk`], invoking [`State::run`] on `S` with `output`.
pub fn run<Output>(self, output: &mut Output) -> ThunkResult<S>
where
S: State<Output>,
{
self.state.run(output);
ThunkResult {
phantom: PhantomData,
}
}
}

/// The state of an [`Adapt`].
pub struct AdaptState<S, F> {
inner: S,
f: F,
}

impl<S: 'static, F, Output> State<Output> for AdaptState<S, F>
where
F: 'static + FnMut(Thunk<S>, &mut Output) -> ThunkResult<S>,
{
fn run(&mut self, output: &mut Output) {
(self.f)(
Thunk {
state: &mut self.inner,
},
output,
);
}
}

/// Adapts a [`Builder`] so that its [`State`] is compatible with a different
/// `Output` type.
///
/// The provided callback must call [`Thunk::run`] with an adapted reference.
pub fn adapt<B, F, S, Output>(builder: B, f: F) -> Adapt<B, F, S, Output>
where
F: 'static + FnMut(Thunk<S>, &mut Output) -> ThunkResult<S>,
{
Adapt {
builder,
f,
phantom: PhantomData,
}
}

/// Adapts a [`Builder`] so that its [`State`] is compatible with a different
/// `Output` type.
///
/// This is a simpler variant of [`adapt`] for the common case where the adapted
/// reference can be borrowed from `Output`.
pub fn adapt_ref<B, F, S, Output, R>(
builder: B,
mut f: F,
) -> Adapt<
B,
// TODO: Remove this impl trait
impl 'static + FnMut(Thunk<S>, &mut Output) -> ThunkResult<S>,
S,
Output,
>
where
F: 'static + FnMut(&mut Output) -> &mut R,
S: State<R>,
{
adapt(builder, move |thunk, output| thunk.run(f(output)))
}
2 changes: 2 additions & 0 deletions ravel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use std::{marker::PhantomData, mem::MaybeUninit};

use paste::paste;

mod adapt;
mod any;
mod local;

pub use adapt::*;
pub use any::*;
pub use local::*;

Expand Down

0 comments on commit 094ead9

Please sign in to comment.