Skip to content

Commit

Permalink
Implement local state
Browse files Browse the repository at this point in the history
  • Loading branch information
kmicklas committed Jun 15, 2024
1 parent c14868b commit cbcab1e
Show file tree
Hide file tree
Showing 15 changed files with 294 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ trunk serve examples/todomvc/index.html

### Features

- [ ] Local state
- [x] Local state
- [ ] Memoization
- [ ] `async` actions
- [ ] Support "message"/"reducer" architecture rather than direct model mutation
Expand Down
36 changes: 35 additions & 1 deletion 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;
use ravel::{with, with_local};
use ravel_web::{
any, attr,
collections::{btree_map, slice},
Expand Down Expand Up @@ -112,6 +112,39 @@ fn events() -> View!(Model) {
)
}

/// Sometimes, we might not want to store all state in our global [`Model`].
///
/// This is useful when it would be tedious to write down uninteresting state
/// types, or when you want to encapsulate the behavior of a reusable component.
/// However, it generally increases complexity and makes testing harder.
fn local_state() -> View!(Model) {
with_local(
// We provide an initialization callback, which is only run when the
// component is constructed for the first time.
|| 0,
|cx, local_count| {
// Inside the body, we have a reference to the current local state.
cx.build((
el::h2("Local state"),
el::p(("Local count: ", display(*local_count))),
el::p(el::button((
"Increment local count",
// Although we have a reference to the current value, we
// cannot mutate it, or store it in an event handler (which
// must remain `'static`).
//
// Instead, [`with_local`] changes our state type to be a
// tuple which has both the outer state ([`Model`]) and our
// local state type.
on_(event::Click, move |(_model, local_count): &mut _| {
*local_count += 1;
}),
))),
))
},
)
}

/// All of our views so far have had a static structure. Sometimes, we need to
/// swap out or hide various components.
fn dynamic_view(model: &Model) -> View!(Model, '_) {
Expand Down Expand Up @@ -198,6 +231,7 @@ fn tutorial(model: &Model) -> View!(Model, '_) {
basic_html(),
state(model),
events(),
local_state(),
dynamic_view(model),
lists(model),
)
Expand Down
3 changes: 2 additions & 1 deletion ravel-web/src/any.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{any::Any, marker::PhantomData, ops::DerefMut};

use ravel::Float;
use web_sys::wasm_bindgen::UnwrapThrowExt as _;

use crate::{
Expand Down Expand Up @@ -71,7 +72,7 @@ pub struct AnyState<Output> {
}

impl<Output: 'static> State<Output> for AnyState<Output> {
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
self.state.run(output)
}
}
Expand Down
8 changes: 4 additions & 4 deletions ravel-web/src/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::marker::PhantomData;

use ravel::Builder;
use ravel::{Builder, Float};
use web_sys::wasm_bindgen::UnwrapThrowExt as _;

use crate::{BuildCx, RebuildCx, State, Web};
Expand Down Expand Up @@ -63,7 +63,7 @@ impl AttrState {
}

impl<Output> State<Output> for AttrState {
fn run(&mut self, _: &mut Output) {}
fn run(&mut self, _: &mut Float<Output>) {}
}

/// An arbitrary attribute.
Expand Down Expand Up @@ -198,7 +198,7 @@ pub struct AttrClassState<Value> {
}

impl<Value: 'static, Output> State<Output> for AttrClassState<Value> {
fn run(&mut self, _: &mut Output) {}
fn run(&mut self, _: &mut Float<Output>) {}
}

/// `class` attribute.
Expand Down Expand Up @@ -254,7 +254,7 @@ impl BooleanAttrState {
}

impl<Output> State<Output> for BooleanAttrState {
fn run(&mut self, _: &mut Output) {}
fn run(&mut self, _: &mut Float<Output>) {}
}

/// An arbitrary boolean attribute.
Expand Down
4 changes: 2 additions & 2 deletions ravel-web/src/collections/btree_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{
cmp::Ordering, collections::BTreeMap, marker::PhantomData, ops::Bound,
};

use ravel::{with, Token};
use ravel::{with, Float, Token};
use web_sys::wasm_bindgen::UnwrapThrowExt;

use crate::{
Expand Down Expand Up @@ -128,7 +128,7 @@ impl<K: 'static + Ord, S, Output> State<Output> for BTreeMapState<K, S>
where
S: State<Output>,
{
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
for entry in self.data.values_mut() {
entry.state.run(output);
}
Expand Down
4 changes: 2 additions & 2 deletions ravel-web/src/collections/slice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{cmp::Ordering, marker::PhantomData};

use ravel::{with, Token};
use ravel::{with, Float, Token};
use web_sys::wasm_bindgen::UnwrapThrowExt;

use crate::{
Expand Down Expand Up @@ -96,7 +96,7 @@ impl<S, Output> State<Output> for SliceState<S>
where
S: State<Output>,
{
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
for entry in self.data.iter_mut() {
entry.state.run(output);
}
Expand Down
3 changes: 2 additions & 1 deletion ravel-web/src/el.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::marker::PhantomData;

use ravel::Float;
use web_sys::wasm_bindgen::{JsValue, UnwrapThrowExt};

use crate::{
Expand Down Expand Up @@ -50,7 +51,7 @@ impl<Output, S> State<Output> for ElState<S>
where
S: State<Output>,
{
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
self.body.run(output)
}
}
Expand Down
5 changes: 3 additions & 2 deletions ravel-web/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use std::{cell::RefCell, marker::PhantomData, ops::DerefMut, rc::Rc};

use ravel::Float;
use web_sys::wasm_bindgen::JsValue;

use crate::{BuildCx, Builder, RebuildCx, State, Web};
Expand Down Expand Up @@ -75,10 +76,10 @@ pub struct OnState<Action> {
impl<Action: 'static + FnMut(&mut Output, web_sys::Event), Output: 'static>
State<Output> for OnState<Action>
{
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
let event = self.event.take();
if !event.is_null() {
(self.action)(output, event);
(self.action)(output.as_mut().unwrap(), event);
}
}
}
Expand Down
28 changes: 25 additions & 3 deletions 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};
use ravel::{Builder, Cx, CxRep, Float, WithLocalState};

mod any;
pub mod attr;
Expand Down Expand Up @@ -47,20 +47,42 @@ pub trait State<Output>: AsAny {
///
/// This method can respond to externally triggered events by changing the
/// `Output`.
fn run(&mut self, output: &mut Output);
fn run(&mut self, output: &mut Float<Output>);
}

impl<Output, T: 'static, S> State<Output> for WithLocalState<T, S>
where
S: State<(Output, T)>,
{
fn run(&mut self, output: &mut Float<Output>) {
output
.float_(|output| {
self.value
.float(|value| {
let mut data = Float::new((output, value));
self.inner.run(&mut data);
let (output, value) = data.into_inner().unwrap();
(value, output)
})
.unwrap()
})
.unwrap()
}
}

/// A marker trait for the [`State`] types of a [`trait@View`].
pub trait ViewMarker {}

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

macro_rules! tuple_state {
($($a:ident),*) => {
#[allow(non_camel_case_types)]
impl<$($a,)* O> State<O> for ($($a,)*)
where
$($a: State<O>,)*
{
fn run(&mut self, _output: &mut O) {
fn run(&mut self, _output: &mut Float<O>) {
let ($($a,)*) = self;
$($a.run(_output);)*
}
Expand Down
3 changes: 2 additions & 1 deletion ravel-web/src/option.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use ravel::Float;
use web_sys::wasm_bindgen::UnwrapThrowExt as _;

use crate::{
Expand Down Expand Up @@ -51,7 +52,7 @@ impl<S, Output> State<Output> for OptionState<S>
where
S: State<Output>,
{
fn run(&mut self, output: &mut Output) {
fn run(&mut self, output: &mut Float<Output>) {
let Some(state) = &mut self.state else { return };
state.run(output)
}
Expand Down
26 changes: 14 additions & 12 deletions ravel-web/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use std::sync::Arc;

use atomic_waker::AtomicWaker;
use ravel::{with, Builder, Token};
use ravel::{with, Builder, Float, Token};
use web_sys::wasm_bindgen::JsValue;

use crate::{dom::Position, BuildCx, Cx, RebuildCx, State, Web};
Expand All @@ -23,7 +23,7 @@ use crate::{dom::Position, BuildCx, Cx, RebuildCx, State, Web};
/// 1. `sync` the `Data` (for example, write updates to an external data store).
pub async fn run<Data, Sync, Render, S, R>(
parent: &web_sys::Element,
data: &mut Data,
data: &mut Float<Data>,
mut sync: Sync,
mut render: Render,
) -> R
Expand All @@ -35,23 +35,24 @@ where
let waker = &Arc::new(AtomicWaker::new());
waker.register(&futures_micro::waker().await);

let mut state = with(|cx| render(cx, data)).build(BuildCx {
position: Position {
parent,
insert_before: &JsValue::NULL.into(),
waker,
},
});
let mut state =
with(|cx| render(cx, data.as_ref().unwrap())).build(BuildCx {
position: Position {
parent,
insert_before: &JsValue::NULL.into(),
waker,
},
});

loop {
futures_micro::sleep().await;

state.run(data);
if let Some(result) = sync(data) {
if let Some(result) = sync(data.as_mut().unwrap()) {
return result;
}

with(|cx| render(cx, data))
with(|cx| render(cx, data.as_ref().unwrap()))
.rebuild(RebuildCx { parent, waker }, &mut state);

waker.register(&futures_micro::waker().await);
Expand All @@ -64,7 +65,7 @@ where
/// This is a convenience wrapper around [`run`], to run a complete application,
/// which will never abort.
pub fn spawn_body<Data: 'static, Sync, Render, S>(
mut data: Data,
data: Data,
mut sync: Sync,
render: Render,
) where
Expand All @@ -74,6 +75,7 @@ pub fn spawn_body<Data: 'static, Sync, Render, S>(
{
let body = gloo_utils::body();
wasm_bindgen_futures::spawn_local(async move {
let mut data = Float::new(data);
run(
&body,
&mut data,
Expand Down
6 changes: 3 additions & 3 deletions ravel-web/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
fmt::{Arguments, Write},
};

use ravel::Builder;
use ravel::{Builder, Float};
use web_sys::wasm_bindgen::UnwrapThrowExt;

use crate::{BuildCx, RebuildCx, State, ViewMarker, Web};
Expand Down Expand Up @@ -45,7 +45,7 @@ pub struct TextState<Value> {
}

impl<Output, Value: 'static> State<Output> for TextState<Value> {
fn run(&mut self, _: &mut Output) {}
fn run(&mut self, _: &mut Float<Output>) {}
}

impl<Value> ViewMarker for TextState<Value> {}
Expand Down Expand Up @@ -145,7 +145,7 @@ pub struct DisplayState<T: ToString + PartialEq> {
impl<T: 'static + ToString + PartialEq, Output> State<Output>
for DisplayState<T>
{
fn run(&mut self, _: &mut Output) {}
fn run(&mut self, _: &mut Float<Output>) {}
}

impl<T: ToString + PartialEq> ViewMarker for DisplayState<T> {}
Expand Down
Loading

0 comments on commit cbcab1e

Please sign in to comment.