Skip to content

Commit

Permalink
xilem_web: Add async_repeat
Browse files Browse the repository at this point in the history
  • Loading branch information
flosse committed Aug 5, 2024
1 parent 24427bb commit 9a4dea0
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 1 deletion.
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"masonry",

"xilem_web",
"xilem_web/web_examples/async_repeat",
"xilem_web/web_examples/counter",
"xilem_web/web_examples/counter_custom_element",
"xilem_web/web_examples/elm",
Expand Down
111 changes: 111 additions & 0 deletions xilem_web/src/concurrent/async_repeat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use std::{future::Future, marker::PhantomData};

use xilem_core::{DynMessage, Message, MessageResult, NoElement, View, ViewMarker};

use crate::{context::MessageThunk, ViewCtx};

pub fn async_repeat<M, F, H, State, Action, Fut>(
future_future: F,
on_event: H,
) -> AsyncRepeat<F, H, M>
where
F: Fn(MessageThunk) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: Message + 'static,
{
const {
assert!(
core::mem::size_of::<F>() == 0,
"`async_repeat` will not be ran again when its captured variables are updated.\n\
To ignore this warning, use `async_repeat_raw`."
);
};
AsyncRepeat {
future_future,
on_event,
message: PhantomData,
}
}

pub fn async_repeat_raw<M, F, H, State, Action, Fut>(
future_future: F,
on_event: H,
) -> AsyncRepeat<F, H, M>
where
F: Fn(MessageThunk) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: Message + 'static,
{
AsyncRepeat {
future_future,
on_event,
message: PhantomData,
}
}

pub struct AsyncRepeat<F, H, M> {
future_future: F,
on_event: H,
message: PhantomData<fn() -> M>,
}

impl<F, H, M> ViewMarker for AsyncRepeat<F, H, M> {}

impl<State, Action, F, H, M, Fut> View<State, Action, ViewCtx, DynMessage> for AsyncRepeat<F, H, M>
where
State: 'static,
Action: 'static,
F: Fn(MessageThunk) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: Message + 'static,
{
type Element = NoElement;

type ViewState = ();

fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
let thunk = ctx.message_thunk();
wasm_bindgen_futures::spawn_local((self.future_future)(thunk));
(NoElement, ())
}

fn rebuild<'el>(
&self,
_: &Self,
(): &mut Self::ViewState,
_: &mut ViewCtx,
(): xilem_core::Mut<'el, Self::Element>,
) -> xilem_core::Mut<'el, Self::Element> {
// Nothing to do
}

fn teardown(
&self,
(): &mut Self::ViewState,
_: &mut ViewCtx,
_: xilem_core::Mut<'_, Self::Element>,
) {
// Nothing to do
}

fn message(
&self,
_: &mut Self::ViewState,
id_path: &[xilem_core::ViewId],
message: DynMessage,
app_state: &mut State,
) -> xilem_core::MessageResult<Action> {
debug_assert!(
id_path.is_empty(),
"id path should be empty in AsyncRepeat::message"
);
let message = message.downcast::<M>().unwrap();
MessageResult::Action((self.on_event)(app_state, *message))
}
}
6 changes: 5 additions & 1 deletion xilem_web/src/concurrent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@

//! Async views, allowing concurrent operations, like fetching data from a server
mod async_repeat;
mod memoized_await;

pub use memoized_await::{memoized_await, MemoizedAwait};
pub use self::{
async_repeat::{async_repeat, async_repeat_raw, AsyncRepeat},
memoized_await::{memoized_await, MemoizedAwait},
};
16 changes: 16 additions & 0 deletions xilem_web/web_examples/async_repeat/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "async_repeat"
version = "0.0.0" # not versioned
publish = false
license.workspace = true
edition.workspace = true

[lints]
workspace = true

[dependencies]
console_error_panic_hook = "0.1.7"
console_log = { version = "1.0.0", features = ["color"] }
gloo-timers = { version = "0.3.0", features = ["futures"] }
log = "0.4.22"
xilem_web = { path = "../.." }
7 changes: 7 additions & 0 deletions xilem_web/web_examples/async_repeat/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>Async repeat example | Xilem Web</title>
</head>
<body></body>
</html>
43 changes: 43 additions & 0 deletions xilem_web/web_examples/async_repeat/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

use gloo_timers::future::TimeoutFuture;
use xilem_web::{
concurrent::async_repeat_raw, core::fork, document_body, elements::html, interfaces::Element,
App,
};

#[derive(Default)]
struct AppState {
ping_count: usize,
}

#[derive(Debug)]
enum Message {
Ping,
}

fn app_logic(state: &mut AppState) -> impl Element<AppState> {
let task = async_repeat_raw(
|thunk| async move {
loop {
TimeoutFuture::new(1_000).await;
thunk.push_message(Message::Ping);
}
},
|state: &mut AppState, message: Message| match message {
Message::Ping => {
state.ping_count += 1;
}
},
);

fork(html::div(format!("Ping count: {}", state.ping_count)), task)
}

pub fn main() {
_ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
log::info!("Start web application");
App::new(document_body(), AppState::default(), app_logic).run();
}

0 comments on commit 9a4dea0

Please sign in to comment.