From 63895a1ffd2f1272e7a216c2bb39b47412e540cc Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 04:15:43 +0100 Subject: [PATCH 01/18] xilem_web: Add MemoizedStream view --- xilem_web/src/concurrent/memoized_await.rs | 241 ++++++++++++++++++--- xilem_web/src/concurrent/mod.rs | 2 +- 2 files changed, 212 insertions(+), 31 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index dc63a69f5..6b4e677cf 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -1,16 +1,30 @@ // Copyright 2024 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 +use std::{fmt, future::Future, marker::PhantomData}; + +use futures::{Stream, StreamExt}; +use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt}; +use wasm_bindgen_futures::spawn_local; + use crate::{ core::{MessageResult, Mut, NoElement, View, ViewId, ViewMarker, ViewPathTracker}, DynMessage, OptionalAction, ViewCtx, }; -use std::{future::Future, marker::PhantomData}; -use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt}; -use wasm_bindgen_futures::spawn_local; -/// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the future. `init_future` will be invoked again, when `data` changes. Use [`memoized_await`] for construction of this [`View`] -pub struct MemoizedAwait { +/// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the future. +/// `init_future` will be invoked again, when `data` changes. Use [`memoized_await`] for construction of this [`View`] +pub struct MemoizedAwait( + MemoizedInner, +); + +/// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. +/// `init_stream` will be invoked again, when `data` changes. Use [`memoized_stream`] for construction of this [`View`] +pub struct MemoizedStream( + MemoizedInner, +); + +struct MemoizedInner { init_future: InitFuture, data: Data, callback: Callback, @@ -19,10 +33,27 @@ pub struct MemoizedAwait phantom: PhantomData (State, Action, OA, F, FOut)>, } +impl + MemoizedInner +where + FOut: fmt::Debug + 'static, + InitFuture: Fn(&Data) -> F, +{ + fn debounce_ms(mut self, milliseconds: usize) -> Self { + self.debounce_ms = milliseconds; + self + } + + fn reset_debounce_on_update(mut self, reset: bool) -> Self { + self.reset_debounce_on_update = reset; + self + } +} + impl MemoizedAwait where - FOut: std::fmt::Debug + 'static, + FOut: fmt::Debug + 'static, F: Future + 'static, InitFuture: Fn(&Data) -> F, { @@ -31,7 +62,7 @@ where /// /// The default for this is `0` pub fn debounce_ms(mut self, milliseconds: usize) -> Self { - self.debounce_ms = milliseconds; + self.0 = self.0.debounce_ms(milliseconds); self } @@ -40,19 +71,47 @@ where /// /// The default for this is `true` pub fn reset_debounce_on_update(mut self, reset: bool) -> Self { - self.reset_debounce_on_update = reset; + self.0 = self.0.reset_debounce_on_update(reset); self } +} - fn init_future(&self, ctx: &mut ViewCtx, generation: u64) { - ctx.with_id(ViewId::new(generation), |ctx| { - let thunk = ctx.message_thunk(); - let future = (self.init_future)(&self.data); - spawn_local(async move { - thunk.push_message(MemoizedAwaitMessage::::Output(future.await)); - }); +fn init_future( + m: &MemoizedInner, + ctx: &mut ViewCtx, + generation: u64, +) where + InitFuture: Fn(&Data) -> F + 'static, + FOut: fmt::Debug + 'static, + F: Future + 'static, +{ + ctx.with_id(ViewId::new(generation), |ctx| { + let thunk = ctx.message_thunk(); + let future = (m.init_future)(&m.data); + spawn_local(async move { + thunk.push_message(MemoizedAwaitMessage::::Output(future.await)); }); - } + }); +} + +fn init_stream( + m: &MemoizedInner, + ctx: &mut ViewCtx, + generation: u64, +) where + InitStream: Fn(&Data) -> F + 'static, + StreamItem: fmt::Debug + 'static, + F: Stream + 'static, +{ + ctx.with_id(ViewId::new(generation), |ctx| { + let thunk = ctx.message_thunk(); + let mut stream = Box::pin((m.init_future)(&m.data)); + spawn_local(async move { + while let Some(item) = stream.next().await { + thunk.push_message(MemoizedAwaitMessage::::Output(item)); + } + }); + }); } /// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the resolved future. `init_future` will be invoked again, when `data` changes. @@ -84,20 +143,45 @@ where State: 'static, Action: 'static, Data: PartialEq + 'static, - FOut: std::fmt::Debug + 'static, + FOut: fmt::Debug + 'static, F: Future + 'static, InitFuture: Fn(&Data) -> F + 'static, OA: OptionalAction + 'static, Callback: Fn(&mut State, FOut) -> OA + 'static, { - MemoizedAwait { + MemoizedAwait(MemoizedInner { init_future, data, callback, debounce_ms: 0, reset_debounce_on_update: true, phantom: PhantomData, - } + }) +} + +pub fn memoized_stream( + data: Data, + init_future: InitStream, + callback: Callback, +) -> MemoizedAwait +where + State: 'static, + Action: 'static, + Data: PartialEq + 'static, + StreamItem: fmt::Debug + 'static, + F: Stream + 'static, + InitStream: Fn(&Data) -> F + 'static, + OA: OptionalAction + 'static, + Callback: Fn(&mut State, StreamItem) -> OA + 'static, +{ + MemoizedAwait(MemoizedInner { + init_future, + data, + callback, + debounce_ms: 0, + reset_debounce_on_update: true, + phantom: PhantomData, + }) } #[derive(Default)] @@ -122,7 +206,7 @@ impl MemoizedAwaitState { self.schedule_update_fn = None; } - fn reset_debounce_timeout_and_schedule_update( + fn reset_debounce_timeout_and_schedule_update( &mut self, ctx: &mut ViewCtx, debounce_duration: usize, @@ -148,7 +232,7 @@ impl MemoizedAwaitState { } #[derive(Debug)] -enum MemoizedAwaitMessage { +enum MemoizedAwaitMessage { Output(Output), ScheduleUpdate, } @@ -157,6 +241,11 @@ impl ViewMarker for MemoizedAwait { } +impl ViewMarker + for MemoizedStream +{ +} + impl View for MemoizedAwait where @@ -164,7 +253,7 @@ where Action: 'static, OA: OptionalAction + 'static, InitFuture: Fn(&Data) -> F + 'static, - FOut: std::fmt::Debug + 'static, + FOut: fmt::Debug + 'static, Data: PartialEq + 'static, F: Future + 'static, CB: Fn(&mut State, FOut) -> OA + 'static, @@ -174,24 +263,116 @@ where type ViewState = MemoizedAwaitState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + self.0.build(ctx, init_future) + } + + fn rebuild( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + (): Mut, + ) { + self.0.rebuild(&prev.0, view_state, ctx, init_future); + } + + fn teardown(&self, state: &mut Self::ViewState, _: &mut ViewCtx, (): Mut) { + self.0.teardown(state); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.0.message(view_state, id_path, message, app_state) + } +} + +impl + View + for MemoizedStream +where + State: 'static, + Action: 'static, + OA: OptionalAction + 'static, + InitStream: Fn(&Data) -> F + 'static, + StreamItem: fmt::Debug + 'static, + Data: PartialEq + 'static, + F: Stream + 'static, + CB: Fn(&mut State, StreamItem) -> OA + 'static, +{ + type Element = NoElement; + + type ViewState = MemoizedAwaitState; + + fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { + self.0.build(ctx, init_stream) + } + + fn rebuild( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut ViewCtx, + (): Mut, + ) { + self.0.rebuild(&prev.0, view_state, ctx, init_stream); + } + + fn teardown(&self, state: &mut Self::ViewState, _: &mut ViewCtx, (): Mut) { + self.0.teardown(state); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[ViewId], + message: DynMessage, + app_state: &mut State, + ) -> MessageResult { + self.0.message(view_state, id_path, message, app_state) + } +} + +impl + MemoizedInner +where + State: 'static, + Action: 'static, + OA: OptionalAction + 'static, + InitFuture: Fn(&Data) -> F + 'static, + FOut: fmt::Debug + 'static, + Data: PartialEq + 'static, + F: 'static, + CB: Fn(&mut State, FOut) -> OA + 'static, +{ + fn build(&self, ctx: &mut ViewCtx, init_future: I) -> (NoElement, MemoizedAwaitState) + where + I: Fn(&Self, &mut ViewCtx, u64), + { let mut state = MemoizedAwaitState::default(); if self.debounce_ms > 0 { state.reset_debounce_timeout_and_schedule_update::(ctx, self.debounce_ms); } else { - self.init_future(ctx, state.generation); + init_future(self, ctx, state.generation); } (NoElement, state) } - fn rebuild( + fn rebuild( &self, prev: &Self, - view_state: &mut Self::ViewState, + view_state: &mut MemoizedAwaitState, ctx: &mut ViewCtx, - (): Mut, - ) { + init_future: I, + ) where + I: Fn(&Self, &mut ViewCtx, u64), + { let debounce_has_changed_and_update_is_scheduled = view_state.schedule_update && (prev.reset_debounce_on_update != self.reset_debounce_on_update || prev.debounce_ms != self.debounce_ms); @@ -221,18 +402,18 @@ where // no debounce view_state.generation += 1; view_state.update = false; - self.init_future(ctx, view_state.generation); + init_future(self, ctx, view_state.generation); } } } - fn teardown(&self, state: &mut Self::ViewState, _: &mut ViewCtx, (): Mut) { + fn teardown(&self, state: &mut MemoizedAwaitState) { state.clear_update_timeout(); } fn message( &self, - view_state: &mut Self::ViewState, + view_state: &mut MemoizedAwaitState, id_path: &[ViewId], message: DynMessage, app_state: &mut State, diff --git a/xilem_web/src/concurrent/mod.rs b/xilem_web/src/concurrent/mod.rs index d8708cd7d..d306aa4f1 100644 --- a/xilem_web/src/concurrent/mod.rs +++ b/xilem_web/src/concurrent/mod.rs @@ -10,4 +10,4 @@ mod interval; pub use interval::{interval, Interval}; mod memoized_await; -pub use memoized_await::{memoized_await, MemoizedAwait}; +pub use memoized_await::{memoized_await, memoized_stream, MemoizedAwait, MemoizedStream}; From 5c830824304b9d5ccb9f47ad3bfb942a660954b6 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 04:56:50 +0100 Subject: [PATCH 02/18] Add docs & debounce options --- xilem_web/src/concurrent/memoized_await.rs | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 6b4e677cf..7ce63f0b8 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -76,6 +76,32 @@ where } } +impl + MemoizedStream +where + StreamItem: fmt::Debug + 'static, + F: Stream + 'static, + InitStream: Fn(&Data) -> F, +{ + /// Debounce the `init_stream` function, when `data` updates, + /// when `reset_debounce_on_update == false` then this throttles updates each `milliseconds` + /// + /// The default for this is `0` + pub fn debounce_ms(mut self, milliseconds: usize) -> Self { + self.0 = self.0.debounce_ms(milliseconds); + self + } + + /// When `reset` is `true`, everytime `data` updates, the debounce timeout is cleared until `init_stream` is invoked. + /// This is only effective when `debounce > 0` + /// + /// The default for this is `true` + pub fn reset_debounce_on_update(mut self, reset: bool) -> Self { + self.0 = self.0.reset_debounce_on_update(reset); + self + } +} + fn init_future( m: &MemoizedInner, ctx: &mut ViewCtx, @@ -159,6 +185,10 @@ where }) } +/// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. +/// `init_stream` will be invoked again, when `data` changes. +/// +/// The update behavior can be controlled, by [`debounce_ms`](`MemoizedStream::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedStream::reset_debounce_on_update`) pub fn memoized_stream( data: Data, init_future: InitStream, From 3f815f7e0b717588e3b4e8fbbc4699fd5e4fb954 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 05:16:07 +0100 Subject: [PATCH 03/18] Fix + small example in docs --- xilem_web/src/concurrent/memoized_await.rs | 33 ++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 7ce63f0b8..0d292ba5d 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -189,11 +189,40 @@ where /// `init_stream` will be invoked again, when `data` changes. /// /// The update behavior can be controlled, by [`debounce_ms`](`MemoizedStream::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedStream::reset_debounce_on_update`) +/// +/// # Examples +/// +/// ``` +/// use gloo_timers::future::TimeoutFuture; +/// use async_stream::stream; +/// use xilem_web::{core::fork, concurrent::memoized_stream, elements::html::div, interfaces::Element}; +/// +/// fn app_logic(state: &mut Vec) -> impl Element> { +/// fork( +/// html::div(format!("{state:?}")), +/// memoized_stream( +/// 10, +/// |n| { +/// let range = 0..*n; +/// stream! { +/// for i in range { +/// TimeoutFuture::new(500).await; +/// yield i; +/// } +/// } +/// }, +/// |state: &mut Vec, item: usize| { +/// state.push(item); +/// }, +/// ), +/// ) +/// } +/// ``` pub fn memoized_stream( data: Data, init_future: InitStream, callback: Callback, -) -> MemoizedAwait +) -> MemoizedStream where State: 'static, Action: 'static, @@ -204,7 +233,7 @@ where OA: OptionalAction + 'static, Callback: Fn(&mut State, StreamItem) -> OA + 'static, { - MemoizedAwait(MemoizedInner { + MemoizedStream(MemoizedInner { init_future, data, callback, From 4c6b4239541933015d31a20ecd1b1bbd86ad2819 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 12:02:23 +0100 Subject: [PATCH 04/18] Add missing dependencies in example --- Cargo.lock | 24 ++++++++++++++++++++++ xilem_web/Cargo.toml | 4 ++++ xilem_web/src/concurrent/memoized_await.rs | 2 +- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9c08eda57..d5605991f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "async-task" version = "4.7.1" @@ -4698,7 +4720,9 @@ dependencies = [ name = "xilem_web" version = "0.1.0" dependencies = [ + "async-stream", "futures", + "gloo-timers", "peniko", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/xilem_web/Cargo.toml b/xilem_web/Cargo.toml index 3d813e712..f81815e49 100644 --- a/xilem_web/Cargo.toml +++ b/xilem_web/Cargo.toml @@ -181,3 +181,7 @@ features = [ default = [] # This interns some often used strings, such as element tags ("div" etc.), which slightly improves performance when creating elements at the cost of a bigger wasm binary intern_strings = ["wasm-bindgen/enable-interning"] + +[dev-dependencies] +async-stream = "0.3.6" +gloo-timers = { version = "0.3.0", features = ["futures"] } diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 0d292ba5d..58ffe94af 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -195,7 +195,7 @@ where /// ``` /// use gloo_timers::future::TimeoutFuture; /// use async_stream::stream; -/// use xilem_web::{core::fork, concurrent::memoized_stream, elements::html::div, interfaces::Element}; +/// use xilem_web::{core::fork, concurrent::memoized_stream, elements::html, interfaces::Element}; /// /// fn app_logic(state: &mut Vec) -> impl Element> { /// fork( From 014f5fe11053047b3e89695bdb2ed69a3ecd5498 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:07:50 +0100 Subject: [PATCH 05/18] Update xilem_web/src/concurrent/memoized_await.rs Co-authored-by: Philipp Mildenberger --- xilem_web/src/concurrent/memoized_await.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 58ffe94af..5ec554b54 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -24,7 +24,7 @@ pub struct MemoizedStream, ); -struct MemoizedInner { +struct MemoizedFuture { init_future: InitFuture, data: Data, callback: Callback, From eece52d23373b6b3ca568d415c4d34613f5c69c0 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:08:16 +0100 Subject: [PATCH 06/18] Update xilem_web/src/concurrent/memoized_await.rs Co-authored-by: Philipp Mildenberger --- xilem_web/src/concurrent/memoized_await.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 5ec554b54..7905b5858 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -188,7 +188,7 @@ where /// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. /// `init_stream` will be invoked again, when `data` changes. /// -/// The update behavior can be controlled, by [`debounce_ms`](`MemoizedStream::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedStream::reset_debounce_on_update`) +/// The behavior of the `init_stream` invocation by changes to `data` can be customized by [`debounce_ms`](`MemoizedStream::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedStream::reset_debounce_on_update`). /// /// # Examples /// From f2ca90dc4f506e2b8ec099e5e0a1614c4f561cab Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:08:25 +0100 Subject: [PATCH 07/18] Update xilem_web/src/concurrent/memoized_await.rs Co-authored-by: Philipp Mildenberger --- xilem_web/src/concurrent/memoized_await.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 7905b5858..f165a37b9 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -207,8 +207,8 @@ where /// stream! { /// for i in range { /// TimeoutFuture::new(500).await; -/// yield i; -/// } +/// yield i; +/// } /// } /// }, /// |state: &mut Vec, item: usize| { From cef8990fe277cf1c55cf0421179d8ea7fe4fe947 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:13:41 +0100 Subject: [PATCH 08/18] Rename MemoizedInner -> MemoizedFuture --- xilem_web/src/concurrent/memoized_await.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index f165a37b9..a379b94ee 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -15,13 +15,13 @@ use crate::{ /// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the future. /// `init_future` will be invoked again, when `data` changes. Use [`memoized_await`] for construction of this [`View`] pub struct MemoizedAwait( - MemoizedInner, + MemoizedFuture, ); /// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. /// `init_stream` will be invoked again, when `data` changes. Use [`memoized_stream`] for construction of this [`View`] pub struct MemoizedStream( - MemoizedInner, + MemoizedFuture, ); struct MemoizedFuture { @@ -34,7 +34,7 @@ struct MemoizedFuture { } impl - MemoizedInner + MemoizedFuture where FOut: fmt::Debug + 'static, InitFuture: Fn(&Data) -> F, @@ -103,7 +103,7 @@ where } fn init_future( - m: &MemoizedInner, + m: &MemoizedFuture, ctx: &mut ViewCtx, generation: u64, ) where @@ -121,7 +121,7 @@ fn init_future( } fn init_stream( - m: &MemoizedInner, + m: &MemoizedFuture, ctx: &mut ViewCtx, generation: u64, ) where @@ -175,7 +175,7 @@ where OA: OptionalAction + 'static, Callback: Fn(&mut State, FOut) -> OA + 'static, { - MemoizedAwait(MemoizedInner { + MemoizedAwait(MemoizedFuture { init_future, data, callback, @@ -233,7 +233,7 @@ where OA: OptionalAction + 'static, Callback: Fn(&mut State, StreamItem) -> OA + 'static, { - MemoizedStream(MemoizedInner { + MemoizedStream(MemoizedFuture { init_future, data, callback, @@ -397,7 +397,7 @@ where } impl - MemoizedInner + MemoizedFuture where State: 'static, Action: 'static, From 00a32ae7b5f67390be99951bc833f66e01fd5f58 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:18:07 +0100 Subject: [PATCH 09/18] Remove unnecessary remains --- xilem_web/src/concurrent/memoized_await.rs | 25 ++++------------------ 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index a379b94ee..77c8b675c 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -33,23 +33,6 @@ struct MemoizedFuture { phantom: PhantomData (State, Action, OA, F, FOut)>, } -impl - MemoizedFuture -where - FOut: fmt::Debug + 'static, - InitFuture: Fn(&Data) -> F, -{ - fn debounce_ms(mut self, milliseconds: usize) -> Self { - self.debounce_ms = milliseconds; - self - } - - fn reset_debounce_on_update(mut self, reset: bool) -> Self { - self.reset_debounce_on_update = reset; - self - } -} - impl MemoizedAwait where @@ -62,7 +45,7 @@ where /// /// The default for this is `0` pub fn debounce_ms(mut self, milliseconds: usize) -> Self { - self.0 = self.0.debounce_ms(milliseconds); + self.0.debounce_ms = milliseconds; self } @@ -71,7 +54,7 @@ where /// /// The default for this is `true` pub fn reset_debounce_on_update(mut self, reset: bool) -> Self { - self.0 = self.0.reset_debounce_on_update(reset); + self.0.reset_debounce_on_update = reset; self } } @@ -88,7 +71,7 @@ where /// /// The default for this is `0` pub fn debounce_ms(mut self, milliseconds: usize) -> Self { - self.0 = self.0.debounce_ms(milliseconds); + self.0.debounce_ms = milliseconds; self } @@ -97,7 +80,7 @@ where /// /// The default for this is `true` pub fn reset_debounce_on_update(mut self, reset: bool) -> Self { - self.0 = self.0.reset_debounce_on_update(reset); + self.0.reset_debounce_on_update = reset; self } } From d85969c6c1b2fc287e90b1389aee353af7e7998c Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sat, 23 Nov 2024 23:24:11 +0100 Subject: [PATCH 10/18] Ignore doc test --- Cargo.lock | 24 ---------------------- xilem_web/Cargo.toml | 4 ---- xilem_web/src/concurrent/memoized_await.rs | 2 +- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d5605991f..9c08eda57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,28 +361,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - [[package]] name = "async-task" version = "4.7.1" @@ -4720,9 +4698,7 @@ dependencies = [ name = "xilem_web" version = "0.1.0" dependencies = [ - "async-stream", "futures", - "gloo-timers", "peniko", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/xilem_web/Cargo.toml b/xilem_web/Cargo.toml index f81815e49..3d813e712 100644 --- a/xilem_web/Cargo.toml +++ b/xilem_web/Cargo.toml @@ -181,7 +181,3 @@ features = [ default = [] # This interns some often used strings, such as element tags ("div" etc.), which slightly improves performance when creating elements at the cost of a bigger wasm binary intern_strings = ["wasm-bindgen/enable-interning"] - -[dev-dependencies] -async-stream = "0.3.6" -gloo-timers = { version = "0.3.0", features = ["futures"] } diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 77c8b675c..a3599f12a 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -175,7 +175,7 @@ where /// /// # Examples /// -/// ``` +/// ```ignore /// use gloo_timers::future::TimeoutFuture; /// use async_stream::stream; /// use xilem_web::{core::fork, concurrent::memoized_stream, elements::html, interfaces::Element}; From 588e28b36259af78d31421846bdea92afa4591aa Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sun, 24 Nov 2024 00:06:30 +0100 Subject: [PATCH 11/18] Add StreamMessage --- xilem_web/src/concurrent/memoized_await.rs | 59 +++++++++++++++++----- xilem_web/src/concurrent/mod.rs | 4 +- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index a3599f12a..df4b5e957 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -21,7 +21,7 @@ pub struct MemoizedAwait /// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. /// `init_stream` will be invoked again, when `data` changes. Use [`memoized_stream`] for construction of this [`View`] pub struct MemoizedStream( - MemoizedFuture, + MemoizedFuture>, ); struct MemoizedFuture { @@ -98,13 +98,13 @@ fn init_future( let thunk = ctx.message_thunk(); let future = (m.init_future)(&m.data); spawn_local(async move { - thunk.push_message(MemoizedAwaitMessage::::Output(future.await)); + thunk.push_message(MemoizedFutureMessage::::Output(future.await)); }); }); } fn init_stream( - m: &MemoizedFuture, + m: &MemoizedFuture>, ctx: &mut ViewCtx, generation: u64, ) where @@ -116,13 +116,44 @@ fn init_stream( let thunk = ctx.message_thunk(); let mut stream = Box::pin((m.init_future)(&m.data)); spawn_local(async move { + let msg = StreamMessage::::started(); + thunk.push_message(msg); + while let Some(item) = stream.next().await { - thunk.push_message(MemoizedAwaitMessage::::Output(item)); + let msg = StreamMessage::::update(item); + thunk.push_message(msg); } + + let msg = StreamMessage::::finished(); + thunk.push_message(msg); }); }); } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum StreamMessage { + Started, + Update(T), + Finished, +} + +impl StreamMessage +where + T: fmt::Debug, +{ + const fn started() -> MemoizedFutureMessage> { + MemoizedFutureMessage::Output(Self::Started) + } + + const fn update(payload: T) -> MemoizedFutureMessage> { + MemoizedFutureMessage::Output(Self::Update(payload)) + } + + const fn finished() -> MemoizedFutureMessage> { + MemoizedFutureMessage::Output(Self::Finished) + } +} + /// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the resolved future. `init_future` will be invoked again, when `data` changes. /// /// The update behavior can be controlled, by [`debounce_ms`](`MemoizedAwait::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedAwait::reset_debounce_on_update`) @@ -194,8 +225,12 @@ where /// } /// } /// }, -/// |state: &mut Vec, item: usize| { -/// state.push(item); +/// |state: &mut Vec, msg: StreamMessage| match msg { +/// StreamMessage::Started => (), +/// StreamMessage::Update(item) => { +/// state.push(item); +/// }, +/// StreamMessage::Finished => () /// }, /// ), /// ) @@ -214,7 +249,7 @@ where F: Stream + 'static, InitStream: Fn(&Data) -> F + 'static, OA: OptionalAction + 'static, - Callback: Fn(&mut State, StreamItem) -> OA + 'static, + Callback: Fn(&mut State, StreamMessage) -> OA + 'static, { MemoizedStream(MemoizedFuture { init_future, @@ -257,7 +292,7 @@ impl MemoizedAwaitState { self.clear_update_timeout(); let thunk = ctx.message_thunk(); let schedule_update_fn = Closure::new(move || { - thunk.push_message(MemoizedAwaitMessage::::ScheduleUpdate); + thunk.push_message(MemoizedFutureMessage::::ScheduleUpdate); }); let handle = web_sys::window() .unwrap_throw() @@ -274,7 +309,7 @@ impl MemoizedAwaitState { } #[derive(Debug)] -enum MemoizedAwaitMessage { +enum MemoizedFutureMessage { Output(Output), ScheduleUpdate, } @@ -344,7 +379,7 @@ where StreamItem: fmt::Debug + 'static, Data: PartialEq + 'static, F: Stream + 'static, - CB: Fn(&mut State, StreamItem) -> OA + 'static, + CB: Fn(&mut State, StreamMessage) -> OA + 'static, { type Element = NoElement; @@ -463,13 +498,13 @@ where assert_eq!(id_path.len(), 1); if id_path[0].routing_id() == view_state.generation { match *message.downcast().unwrap_throw() { - MemoizedAwaitMessage::Output(future_output) => { + MemoizedFutureMessage::Output(future_output) => { match (self.callback)(app_state, future_output).action() { Some(action) => MessageResult::Action(action), None => MessageResult::Nop, } } - MemoizedAwaitMessage::ScheduleUpdate => { + MemoizedFutureMessage::ScheduleUpdate => { view_state.update = true; view_state.schedule_update = false; MessageResult::RequestRebuild diff --git a/xilem_web/src/concurrent/mod.rs b/xilem_web/src/concurrent/mod.rs index d306aa4f1..ad4cff42c 100644 --- a/xilem_web/src/concurrent/mod.rs +++ b/xilem_web/src/concurrent/mod.rs @@ -10,4 +10,6 @@ mod interval; pub use interval::{interval, Interval}; mod memoized_await; -pub use memoized_await::{memoized_await, memoized_stream, MemoizedAwait, MemoizedStream}; +pub use memoized_await::{ + memoized_await, memoized_stream, MemoizedAwait, MemoizedStream, StreamMessage, +}; From 8b2dbce36c7ba7072f191f6e6846c20f05fb022d Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sun, 24 Nov 2024 03:03:44 +0100 Subject: [PATCH 12/18] Remove the StreamMessage again --- xilem_web/src/concurrent/memoized_await.rs | 53 ++++------------------ xilem_web/src/concurrent/mod.rs | 4 +- 2 files changed, 10 insertions(+), 47 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index df4b5e957..9dce7f5f0 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -21,7 +21,7 @@ pub struct MemoizedAwait /// Await a stream returned by `init_stream` invoked with the argument `data`, `callback` is called with the items of the stream. /// `init_stream` will be invoked again, when `data` changes. Use [`memoized_stream`] for construction of this [`View`] pub struct MemoizedStream( - MemoizedFuture>, + MemoizedFuture, ); struct MemoizedFuture { @@ -104,7 +104,7 @@ fn init_future( } fn init_stream( - m: &MemoizedFuture>, + m: &MemoizedFuture, ctx: &mut ViewCtx, generation: u64, ) where @@ -116,44 +116,13 @@ fn init_stream( let thunk = ctx.message_thunk(); let mut stream = Box::pin((m.init_future)(&m.data)); spawn_local(async move { - let msg = StreamMessage::::started(); - thunk.push_message(msg); - while let Some(item) = stream.next().await { - let msg = StreamMessage::::update(item); - thunk.push_message(msg); + thunk.push_message(MemoizedFutureMessage::::Output(item)) } - - let msg = StreamMessage::::finished(); - thunk.push_message(msg); }); }); } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum StreamMessage { - Started, - Update(T), - Finished, -} - -impl StreamMessage -where - T: fmt::Debug, -{ - const fn started() -> MemoizedFutureMessage> { - MemoizedFutureMessage::Output(Self::Started) - } - - const fn update(payload: T) -> MemoizedFutureMessage> { - MemoizedFutureMessage::Output(Self::Update(payload)) - } - - const fn finished() -> MemoizedFutureMessage> { - MemoizedFutureMessage::Output(Self::Finished) - } -} - /// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the resolved future. `init_future` will be invoked again, when `data` changes. /// /// The update behavior can be controlled, by [`debounce_ms`](`MemoizedAwait::debounce_ms`) and [`reset_debounce_on_update`](`MemoizedAwait::reset_debounce_on_update`) @@ -225,14 +194,10 @@ where /// } /// } /// }, -/// |state: &mut Vec, msg: StreamMessage| match msg { -/// StreamMessage::Started => (), -/// StreamMessage::Update(item) => { -/// state.push(item); -/// }, -/// StreamMessage::Finished => () -/// }, -/// ), +/// |state: &mut Vec, item: usize| { +/// state.push(item); +/// } +/// ) /// ) /// } /// ``` @@ -249,7 +214,7 @@ where F: Stream + 'static, InitStream: Fn(&Data) -> F + 'static, OA: OptionalAction + 'static, - Callback: Fn(&mut State, StreamMessage) -> OA + 'static, + Callback: Fn(&mut State, StreamItem) -> OA + 'static, { MemoizedStream(MemoizedFuture { init_future, @@ -379,7 +344,7 @@ where StreamItem: fmt::Debug + 'static, Data: PartialEq + 'static, F: Stream + 'static, - CB: Fn(&mut State, StreamMessage) -> OA + 'static, + CB: Fn(&mut State, StreamItem) -> OA + 'static, { type Element = NoElement; diff --git a/xilem_web/src/concurrent/mod.rs b/xilem_web/src/concurrent/mod.rs index ad4cff42c..d306aa4f1 100644 --- a/xilem_web/src/concurrent/mod.rs +++ b/xilem_web/src/concurrent/mod.rs @@ -10,6 +10,4 @@ mod interval; pub use interval::{interval, Interval}; mod memoized_await; -pub use memoized_await::{ - memoized_await, memoized_stream, MemoizedAwait, MemoizedStream, StreamMessage, -}; +pub use memoized_await::{memoized_await, memoized_stream, MemoizedAwait, MemoizedStream}; From 52c8d14cd411d18d30e007770f5bc1fe3f9b4d59 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sun, 24 Nov 2024 03:17:21 +0100 Subject: [PATCH 13/18] Fix clippy lint --- xilem_web/src/concurrent/memoized_await.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 9dce7f5f0..59b950c6d 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -117,7 +117,7 @@ fn init_stream( let mut stream = Box::pin((m.init_future)(&m.data)); spawn_local(async move { while let Some(item) = stream.next().await { - thunk.push_message(MemoizedFutureMessage::::Output(item)) + thunk.push_message(MemoizedFutureMessage::::Output(item)); } }); }); From 919a6e62d4b37aeeefe6e899d93e3a8ec815b537 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Tue, 26 Nov 2024 01:59:11 +0100 Subject: [PATCH 14/18] Add an example for memoized_stream --- Cargo.lock | 39 ++++++++ Cargo.toml | 1 + xilem_web/web_examples/streams/Cargo.toml | 23 +++++ xilem_web/web_examples/streams/index.html | 7 ++ xilem_web/web_examples/streams/src/api.rs | 95 ++++++++++++++++++ xilem_web/web_examples/streams/src/main.rs | 110 +++++++++++++++++++++ 6 files changed, 275 insertions(+) create mode 100644 xilem_web/web_examples/streams/Cargo.toml create mode 100644 xilem_web/web_examples/streams/index.html create mode 100644 xilem_web/web_examples/streams/src/api.rs create mode 100644 xilem_web/web_examples/streams/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 9c08eda57..7ab90f47d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -361,6 +361,28 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "async-task" version = "4.7.1" @@ -3296,6 +3318,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "streams" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-stream", + "console_error_panic_hook", + "console_log", + "futures", + "getrandom", + "gloo-timers", + "log", + "rand", + "web-sys", + "xilem_web", +] + [[package]] name = "strict-num" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index a9fb0b033..b196072bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "xilem_web/web_examples/mathml_svg", "xilem_web/web_examples/raw_dom_access", "xilem_web/web_examples/spawn_tasks", + "xilem_web/web_examples/streams", "xilem_web/web_examples/svgtoy", "xilem_web/web_examples/svgdraw", ] diff --git a/xilem_web/web_examples/streams/Cargo.toml b/xilem_web/web_examples/streams/Cargo.toml new file mode 100644 index 000000000..c7899287d --- /dev/null +++ b/xilem_web/web_examples/streams/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "streams" +version = "0.1.0" +publish = false +license.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = "1.0.93" +async-stream = "0.3.6" +console_error_panic_hook = "0.1" +console_log = "1" +futures = "0.3.31" +getrandom = { version = "0.2.15", features = ["js"] } +gloo-timers = { version = "0.3.0", features = ["futures"] } +log = "0.4" +rand = { version = "0.8.5", features = ["small_rng"] } +web-sys = { version = "0.3.72", features = ["KeyboardEvent"] } +xilem_web = { path = "../.." } diff --git a/xilem_web/web_examples/streams/index.html b/xilem_web/web_examples/streams/index.html new file mode 100644 index 000000000..934740173 --- /dev/null +++ b/xilem_web/web_examples/streams/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/xilem_web/web_examples/streams/src/api.rs b/xilem_web/web_examples/streams/src/api.rs new file mode 100644 index 000000000..78af368be --- /dev/null +++ b/xilem_web/web_examples/streams/src/api.rs @@ -0,0 +1,95 @@ +use std::future::Future; + +use anyhow::anyhow; +use async_stream::stream; +use futures::{channel::oneshot, select, FutureExt, Stream}; +use gloo_timers::future::TimeoutFuture; +use rand::Rng; + +#[derive(Debug)] +pub(crate) enum StreamMessage { + Started(AbortHandle), + SearchResult(anyhow::Result>), + Aborted, + TimedOut, + Finished, +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub(crate) struct MockConnection; + +impl MockConnection { + pub(crate) fn search(&self, search_term: String) -> impl Stream { + let (shutdown_signal, abort_handle) = ShutdownSignal::new(); + let mut shutdown = shutdown_signal.into_future().fuse(); + stream! { + yield StreamMessage::Started(abort_handle); + loop { + let mut timeout = TimeoutFuture::new(3_000).fuse(); + let mut search_result = Box::pin(simulated_search(&search_term).fuse()); + select! { + () = shutdown => { + yield StreamMessage::Aborted; + break; + } + () = timeout => { + yield StreamMessage::TimedOut; + return; + } + result = search_result => { + let search_more = match &result { + Ok(Some(_)) => true, + Ok(None) | Err(_) => false + }; + yield StreamMessage::SearchResult(result); + if !search_more { + break; + } + } + complete => panic!("stream completed unexpectedly"), + } + } + yield StreamMessage::Finished; + } + } +} + +async fn simulated_search(search_term: &str) -> anyhow::Result> { + let mut rng = rand::thread_rng(); + + let delay_ms = rng.gen_range(5..=600); + TimeoutFuture::new(delay_ms).fuse().await; + let random_value: u8 = rng.gen_range(0..=100); + + match random_value { + 0..=10 => Err(anyhow!("Simulated error")), + 11..=50 => Ok(None), // Nothing found + _ => Ok(Some(format!("Result for '{search_term}'"))), + } +} + +#[derive(Debug)] +pub(crate) struct AbortHandle { + abort_tx: oneshot::Sender<()>, +} + +impl AbortHandle { + pub(crate) fn abort(self) { + let _ = self.abort_tx.send(()); + } +} + +struct ShutdownSignal { + shutdown_rx: oneshot::Receiver<()>, +} + +impl ShutdownSignal { + fn new() -> (Self, AbortHandle) { + let (abort_tx, shutdown_rx) = oneshot::channel(); + (Self { shutdown_rx }, AbortHandle { abort_tx }) + } + + fn into_future(self) -> impl Future { + self.shutdown_rx.map(|_| ()) + } +} diff --git a/xilem_web/web_examples/streams/src/main.rs b/xilem_web/web_examples/streams/src/main.rs new file mode 100644 index 000000000..e0fdd2fc7 --- /dev/null +++ b/xilem_web/web_examples/streams/src/main.rs @@ -0,0 +1,110 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + +//! This example demonstrates the use of [`memoized_stream`]. + +use std::pin::Pin; + +use futures::{stream, Stream}; +use xilem_web::{ + concurrent::memoized_stream, core::fork, document_body, elements::html, + input_event_target_value, interfaces::Element, App, +}; + +mod api; +use self::api::{AbortHandle, MockConnection, StreamMessage}; + +#[derive(Default)] +struct AppState { + search_term: String, + db: MockConnection, + current_search: Option, +} + +const DEBOUNCE_MILLIS: usize = 500; + +fn app_logic(state: &mut AppState) -> impl Element { + log::debug!("Run app logic"); + + let search_stream = memoized_stream( + (state.search_term.clone(), state.db.clone()), + create_search_stream, + handle_stream_message, + ) + .debounce_ms(DEBOUNCE_MILLIS) + .reset_debounce_on_update(true); + + html::div(fork( + html::div(( + html::input(()) + .on_keyup(on_search_input_keyup) + .attr("value", state.search_term.clone()) + .attr("placeholder", "Type to search"), + html::p(( + "Search is running: ", + if state.current_search.is_some() { + "yes" + } else { + "no" + }, + )), + )), + search_stream, + )) +} + +fn on_search_input_keyup(state: &mut AppState, ev: web_sys::KeyboardEvent) { + if ev.key() == "Escape" { + state.search_term.clear(); + return; + } + if let Some(abort_handle) = state.current_search.take() { + abort_handle.abort(); + }; + state.search_term = input_event_target_value(&ev).expect("input value"); +} + +fn create_search_stream( + (term, conn): &(String, MockConnection), +) -> Pin>> { + if term.is_empty() { + Box::pin(stream::empty()) + } else { + Box::pin(conn.search(term.clone())) + } +} + +fn handle_stream_message(state: &mut AppState, message: StreamMessage) { + match message { + StreamMessage::Started(abort_handle) => { + log::debug!("Search stream started"); + debug_assert!( + state.current_search.is_none(), + "The previous search should already have been canceled" + ); + state.current_search = Some(abort_handle); + } + StreamMessage::SearchResult(result) => { + log::debug!("Stream result {result:?}"); + } + StreamMessage::Aborted => { + log::debug!("Stream aborted"); + state.current_search = None; + } + StreamMessage::TimedOut => { + log::debug!("Stream timed out"); + state.current_search = None; + } + StreamMessage::Finished => { + log::debug!("Stream finished"); + state.current_search = None; + } + } +} + +fn main() { + _ = console_log::init_with_level(log::Level::Debug); + console_error_panic_hook::set_once(); + log::info!("Start application"); + App::new(document_body(), AppState::default(), app_logic).run(); +} From a28aaf311be0dc31f03b52eb1c5aa45dfcf87e7d Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Tue, 26 Nov 2024 02:01:58 +0100 Subject: [PATCH 15/18] Add missing copyright header --- xilem_web/web_examples/streams/src/api.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xilem_web/web_examples/streams/src/api.rs b/xilem_web/web_examples/streams/src/api.rs index 78af368be..22f3c4ccf 100644 --- a/xilem_web/web_examples/streams/src/api.rs +++ b/xilem_web/web_examples/streams/src/api.rs @@ -1,3 +1,6 @@ +// Copyright 2024 the Xilem Authors +// SPDX-License-Identifier: Apache-2.0 + use std::future::Future; use anyhow::anyhow; From 261aa4271fd3d03d87d1ac75bbc02bce7e3a390a Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Tue, 26 Nov 2024 13:07:49 +0100 Subject: [PATCH 16/18] Refactor --- Cargo.lock | 1 + xilem_web/src/concurrent/memoized_await.rs | 134 ++++++++++++--------- xilem_web/web_examples/fetch/Cargo.toml | 1 + xilem_web/web_examples/fetch/src/main.rs | 16 ++- xilem_web/web_examples/streams/src/api.rs | 2 +- xilem_web/web_examples/streams/src/main.rs | 7 +- 6 files changed, 95 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ab90f47d..e86384a8f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1046,6 +1046,7 @@ dependencies = [ "console_error_panic_hook", "console_log", "gloo-net", + "gloo-timers", "log", "serde", "wasm-bindgen", diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index 59b950c6d..dbb540fdc 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -1,7 +1,7 @@ // Copyright 2024 the Xilem Authors and the Druid Authors // SPDX-License-Identifier: Apache-2.0 -use std::{fmt, future::Future, marker::PhantomData}; +use std::{fmt, future::Future, marker::PhantomData, rc::Rc}; use futures::{Stream, StreamExt}; use wasm_bindgen::{closure::Closure, JsCast, UnwrapThrowExt}; @@ -9,7 +9,7 @@ use wasm_bindgen_futures::spawn_local; use crate::{ core::{MessageResult, Mut, NoElement, View, ViewId, ViewMarker, ViewPathTracker}, - DynMessage, OptionalAction, ViewCtx, + DynMessage, MessageThunk, OptionalAction, ViewCtx, }; /// Await a future returned by `init_future` invoked with the argument `data`, `callback` is called with the output of the future. @@ -38,7 +38,7 @@ impl where FOut: fmt::Debug + 'static, F: Future + 'static, - InitFuture: Fn(&Data) -> F, + InitFuture: Fn(&mut State, &Data) -> F, { /// Debounce the `init_future` function, when `data` updates, /// when `reset_debounce_on_update == false` then this throttles updates each `milliseconds` @@ -64,7 +64,7 @@ impl where StreamItem: fmt::Debug + 'static, F: Stream + 'static, - InitStream: Fn(&Data) -> F, + InitStream: Fn(&mut State, &Data) -> F, { /// Debounce the `init_stream` function, when `data` updates, /// when `reset_debounce_on_update == false` then this throttles updates each `milliseconds` @@ -87,39 +87,33 @@ where fn init_future( m: &MemoizedFuture, - ctx: &mut ViewCtx, - generation: u64, + thunk: Rc, + state: &mut State, ) where - InitFuture: Fn(&Data) -> F + 'static, + InitFuture: Fn(&mut State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, F: Future + 'static, { - ctx.with_id(ViewId::new(generation), |ctx| { - let thunk = ctx.message_thunk(); - let future = (m.init_future)(&m.data); - spawn_local(async move { - thunk.push_message(MemoizedFutureMessage::::Output(future.await)); - }); + let future = (m.init_future)(state, &m.data); + spawn_local(async move { + thunk.push_message(MemoizedFutureMessage::::Output(future.await)); }); } fn init_stream( m: &MemoizedFuture, - ctx: &mut ViewCtx, - generation: u64, + thunk: Rc, + state: &mut State, ) where - InitStream: Fn(&Data) -> F + 'static, + InitStream: Fn(&mut State, &Data) -> F + 'static, StreamItem: fmt::Debug + 'static, F: Stream + 'static, { - ctx.with_id(ViewId::new(generation), |ctx| { - let thunk = ctx.message_thunk(); - let mut stream = Box::pin((m.init_future)(&m.data)); - spawn_local(async move { - while let Some(item) = stream.next().await { - thunk.push_message(MemoizedFutureMessage::::Output(item)); - } - }); + let mut stream = Box::pin((m.init_future)(state, &m.data)); + spawn_local(async move { + while let Some(item) = stream.next().await { + thunk.push_message(MemoizedFutureMessage::::Output(item)); + } }); } @@ -137,7 +131,7 @@ fn init_stream( /// div(*state), /// memoized_await( /// 10, -/// |count| std::future::ready(*count), +/// |_, count| std::future::ready(*count), /// |state, output| *state = output, /// ) /// ) @@ -154,7 +148,7 @@ where Data: PartialEq + 'static, FOut: fmt::Debug + 'static, F: Future + 'static, - InitFuture: Fn(&Data) -> F + 'static, + InitFuture: Fn(&mut State, &Data) -> F + 'static, OA: OptionalAction + 'static, Callback: Fn(&mut State, FOut) -> OA + 'static, { @@ -185,7 +179,7 @@ where /// html::div(format!("{state:?}")), /// memoized_stream( /// 10, -/// |n| { +/// |_,n| { /// let range = 0..*n; /// stream! { /// for i in range { @@ -212,7 +206,7 @@ where Data: PartialEq + 'static, StreamItem: fmt::Debug + 'static, F: Stream + 'static, - InitStream: Fn(&Data) -> F + 'static, + InitStream: Fn(&mut State, &Data) -> F + 'static, OA: OptionalAction + 'static, Callback: Fn(&mut State, StreamItem) -> OA + 'static, { @@ -226,7 +220,6 @@ where }) } -#[derive(Default)] #[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules pub struct MemoizedAwaitState { generation: u64, @@ -235,9 +228,20 @@ pub struct MemoizedAwaitState { schedule_update_fn: Option>, schedule_update_timeout_handle: Option, update: bool, + thunk: Rc, } impl MemoizedAwaitState { + fn new(thunk: MessageThunk) -> Self { + Self { + generation: 0, + schedule_update: false, + schedule_update_fn: None, + schedule_update_timeout_handle: None, + update: false, + thunk: Rc::new(thunk), + } + } fn clear_update_timeout(&mut self) { if let Some(handle) = self.schedule_update_timeout_handle { web_sys::window() @@ -248,11 +252,13 @@ impl MemoizedAwaitState { self.schedule_update_fn = None; } - fn reset_debounce_timeout_and_schedule_update( + fn reset_debounce_timeout_and_schedule_update( &mut self, ctx: &mut ViewCtx, debounce_duration: usize, - ) { + ) where + FOut: fmt::Debug + 'static, + { ctx.with_id(ViewId::new(self.generation), |ctx| { self.clear_update_timeout(); let thunk = ctx.message_thunk(); @@ -271,12 +277,24 @@ impl MemoizedAwaitState { self.schedule_update = true; }); } + + fn request_init(&mut self, ctx: &mut ViewCtx) + where + FOut: fmt::Debug + 'static, + { + ctx.with_id(ViewId::new(self.generation), |ctx| { + self.thunk = Rc::new(ctx.message_thunk()); + self.thunk + .enqueue_message(MemoizedFutureMessage::::RequestInit); + }); + } } #[derive(Debug)] enum MemoizedFutureMessage { Output(Output), ScheduleUpdate, + RequestInit, } impl ViewMarker @@ -294,7 +312,7 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitFuture: Fn(&Data) -> F + 'static, + InitFuture: Fn(&mut State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, Data: PartialEq + 'static, F: Future + 'static, @@ -305,7 +323,7 @@ where type ViewState = MemoizedAwaitState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - self.0.build(ctx, init_future) + self.0.build(ctx) } fn rebuild( @@ -315,7 +333,7 @@ where ctx: &mut ViewCtx, (): Mut, ) { - self.0.rebuild(&prev.0, view_state, ctx, init_future); + self.0.rebuild(&prev.0, view_state, ctx); } fn teardown(&self, state: &mut Self::ViewState, _: &mut ViewCtx, (): Mut) { @@ -329,7 +347,8 @@ where message: DynMessage, app_state: &mut State, ) -> MessageResult { - self.0.message(view_state, id_path, message, app_state) + self.0 + .message(view_state, id_path, message, app_state, init_future) } } @@ -340,7 +359,7 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitStream: Fn(&Data) -> F + 'static, + InitStream: Fn(&mut State, &Data) -> F + 'static, StreamItem: fmt::Debug + 'static, Data: PartialEq + 'static, F: Stream + 'static, @@ -351,7 +370,7 @@ where type ViewState = MemoizedAwaitState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { - self.0.build(ctx, init_stream) + self.0.build(ctx) } fn rebuild( @@ -361,7 +380,7 @@ where ctx: &mut ViewCtx, (): Mut, ) { - self.0.rebuild(&prev.0, view_state, ctx, init_stream); + self.0.rebuild(&prev.0, view_state, ctx); } fn teardown(&self, state: &mut Self::ViewState, _: &mut ViewCtx, (): Mut) { @@ -375,7 +394,8 @@ where message: DynMessage, app_state: &mut State, ) -> MessageResult { - self.0.message(view_state, id_path, message, app_state) + self.0 + .message(view_state, id_path, message, app_state, init_stream) } } @@ -385,36 +405,26 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitFuture: Fn(&Data) -> F + 'static, + InitFuture: Fn(&mut State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, Data: PartialEq + 'static, F: 'static, CB: Fn(&mut State, FOut) -> OA + 'static, { - fn build(&self, ctx: &mut ViewCtx, init_future: I) -> (NoElement, MemoizedAwaitState) - where - I: Fn(&Self, &mut ViewCtx, u64), - { - let mut state = MemoizedAwaitState::default(); + fn build(&self, ctx: &mut ViewCtx) -> (NoElement, MemoizedAwaitState) { + let thunk = ctx.message_thunk(); + let mut state = MemoizedAwaitState::new(thunk); if self.debounce_ms > 0 { state.reset_debounce_timeout_and_schedule_update::(ctx, self.debounce_ms); } else { - init_future(self, ctx, state.generation); + state.request_init::(ctx); } (NoElement, state) } - fn rebuild( - &self, - prev: &Self, - view_state: &mut MemoizedAwaitState, - ctx: &mut ViewCtx, - init_future: I, - ) where - I: Fn(&Self, &mut ViewCtx, u64), - { + fn rebuild(&self, prev: &Self, view_state: &mut MemoizedAwaitState, ctx: &mut ViewCtx) { let debounce_has_changed_and_update_is_scheduled = view_state.schedule_update && (prev.reset_debounce_on_update != self.reset_debounce_on_update || prev.debounce_ms != self.debounce_ms); @@ -444,7 +454,7 @@ where // no debounce view_state.generation += 1; view_state.update = false; - init_future(self, ctx, view_state.generation); + view_state.request_init::(ctx); } } } @@ -453,13 +463,17 @@ where state.clear_update_timeout(); } - fn message( + fn message( &self, view_state: &mut MemoizedAwaitState, id_path: &[ViewId], message: DynMessage, app_state: &mut State, - ) -> MessageResult { + init_future: I, + ) -> MessageResult + where + I: Fn(&Self, Rc, &mut State), + { assert_eq!(id_path.len(), 1); if id_path[0].routing_id() == view_state.generation { match *message.downcast().unwrap_throw() { @@ -474,6 +488,10 @@ where view_state.schedule_update = false; MessageResult::RequestRebuild } + MemoizedFutureMessage::RequestInit => { + init_future(self, Rc::clone(&view_state.thunk), app_state); + MessageResult::RequestRebuild + } } } else { MessageResult::Stale(message) diff --git a/xilem_web/web_examples/fetch/Cargo.toml b/xilem_web/web_examples/fetch/Cargo.toml index a56c22d8c..2e3a3fdf6 100644 --- a/xilem_web/web_examples/fetch/Cargo.toml +++ b/xilem_web/web_examples/fetch/Cargo.toml @@ -13,6 +13,7 @@ workspace = true console_error_panic_hook = "0.1" console_log = "1" gloo-net = { version = "0.6.0", default-features = false, features = ["http", "json", "serde"] } +gloo-timers = { version = "0.3.0", features = ["futures"] } log = "0.4" serde = { version = "1", features = ["derive"] } web-sys = { version = "0.3.69", features = ["Event", "HtmlInputElement"] } diff --git a/xilem_web/web_examples/fetch/src/main.rs b/xilem_web/web_examples/fetch/src/main.rs index d5153702f..aead47c44 100644 --- a/xilem_web/web_examples/fetch/src/main.rs +++ b/xilem_web/web_examples/fetch/src/main.rs @@ -8,6 +8,7 @@ #![allow(clippy::wildcard_imports, reason = "HTML elements are an exception")] use gloo_net::http::Request; +use gloo_timers::future::TimeoutFuture; use serde::{Deserialize, Serialize}; use wasm_bindgen::{JsCast, UnwrapThrowExt}; use xilem_web::{ @@ -56,13 +57,13 @@ impl AppState { FetchState::Finished } else if self.cats_to_fetch >= TOO_MANY_CATS { FetchState::TooMany + } else if self.cats_to_fetch > 0 && self.cats_are_being_fetched { + FetchState::Fetching } else if self.debounce_in_ms > 0 && self.cats_to_fetch > 0 && self.reset_debounce_on_update { FetchState::Debounced } else if self.debounce_in_ms > 0 && self.cats_to_fetch > 0 { FetchState::Throttled - } else if self.cats_to_fetch > 0 && self.cats_are_being_fetched { - FetchState::Fetching } else { FetchState::Initial } @@ -80,6 +81,10 @@ enum FetchState { async fn fetch_cats(count: usize) -> Result, gloo_net::Error> { log::debug!("Fetch {count} cats"); + + // Simulate a delay + TimeoutFuture::new(1_000).await; + if count == 0 { return Ok(Vec::new()); } @@ -119,7 +124,11 @@ fn app_logic(state: &mut AppState) -> impl HtmlDivElement { // or debounced otherwise: // As long as updates are happening within `debounce_in_ms` ms the first closure is not invoked, and a debounce timeout which runs `debounce_in_ms` is reset. state.cats_to_fetch, - |count| fetch_cats(*count), + |state: &mut AppState, count| { + log::debug!("Create new future to fetch"); + state.cats_are_being_fetched = true; + fetch_cats(*count) + }, |state: &mut AppState, cats_result| match cats_result { Ok(cats) => { log::info!("Received {} cats", cats.len()); @@ -182,7 +191,6 @@ fn cat_fetch_controls(state: &AppState) -> impl Element { if !state.cats_are_being_fetched { state.cats.clear(); } - state.cats_are_being_fetched = true; state.cats_to_fetch = input_target(&ev).value().parse().unwrap_or(0); })), )), diff --git a/xilem_web/web_examples/streams/src/api.rs b/xilem_web/web_examples/streams/src/api.rs index 22f3c4ccf..0589d5211 100644 --- a/xilem_web/web_examples/streams/src/api.rs +++ b/xilem_web/web_examples/streams/src/api.rs @@ -18,7 +18,7 @@ pub(crate) enum StreamMessage { Finished, } -#[derive(Debug, Default, PartialEq, Clone)] +#[derive(Default)] pub(crate) struct MockConnection; impl MockConnection { diff --git a/xilem_web/web_examples/streams/src/main.rs b/xilem_web/web_examples/streams/src/main.rs index e0fdd2fc7..b62160311 100644 --- a/xilem_web/web_examples/streams/src/main.rs +++ b/xilem_web/web_examples/streams/src/main.rs @@ -27,7 +27,7 @@ fn app_logic(state: &mut AppState) -> impl Element { log::debug!("Run app logic"); let search_stream = memoized_stream( - (state.search_term.clone(), state.db.clone()), + state.search_term.clone(), create_search_stream, handle_stream_message, ) @@ -65,12 +65,13 @@ fn on_search_input_keyup(state: &mut AppState, ev: web_sys::KeyboardEvent) { } fn create_search_stream( - (term, conn): &(String, MockConnection), + state: &mut AppState, + term: &String, ) -> Pin>> { if term.is_empty() { Box::pin(stream::empty()) } else { - Box::pin(conn.search(term.clone())) + Box::pin(state.db.search(term.to_owned())) } } From 96c0ac6dc76d1c509094e76000ea873e09c16281 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sun, 22 Dec 2024 01:34:59 +0100 Subject: [PATCH 17/18] Fix Cargo.lock --- Cargo.lock | 214 +++++++++++++++++++++++++---------------------------- 1 file changed, 101 insertions(+), 113 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6de02394b..684c3a9a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f47983a1084940ba9a39c077a8c63e55c619388be5476ac04c804cfbd1e63459" dependencies = [ "accesskit", - "hashbrown 0.15.2", + "hashbrown", "immutable-chunkmap", ] @@ -57,7 +57,7 @@ checksum = "7329821f3bd1101e03a7d2e03bd339e3ac0dc64c70b4c9f9ae1949e3ba8dece1" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown 0.15.2", + "hashbrown", "objc2", "objc2-app-kit", "objc2-foundation", @@ -89,7 +89,7 @@ checksum = "24fcd5d23d70670992b823e735e859374d694a3d12bfd8dd32bd3bd8bedb5d81" dependencies = [ "accesskit", "accesskit_consumer", - "hashbrown 0.15.2", + "hashbrown", "paste", "static_assertions", "windows", @@ -382,7 +382,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -558,18 +558,18 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", @@ -622,9 +622,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.2" +version = "1.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" +checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e" dependencies = [ "jobserver", "libc", @@ -695,14 +695,14 @@ dependencies = [ [[package]] name = "console" -version = "0.15.8" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" dependencies = [ "encode_unicode", - "lazy_static", "libc", - "windows-sys 0.52.0", + "once_cell", + "windows-sys 0.59.0", ] [[package]] @@ -818,9 +818,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -916,9 +916,9 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] name = "endi" @@ -995,15 +995,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -1047,9 +1047,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" [[package]] name = "font-types" @@ -1088,13 +1088,13 @@ dependencies = [ "core-foundation", "core-text", "fontconfig-cache-parser", - "hashbrown 0.15.2", + "hashbrown", "icu_locid", "memmap2", "objc2", "objc2-foundation", "peniko", - "read-fonts 0.22.5", + "read-fonts 0.22.7", "roxmltree", "smallvec", "windows", @@ -1409,13 +1409,13 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c08c1f623a8d0b722b8b99f821eb0ba672a1618f0d3b16ddbee1cedd2dd8557" +checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" dependencies = [ "bitflags 2.6.0", "gpu-descriptor-types", - "hashbrown 0.14.5", + "hashbrown", ] [[package]] @@ -1437,16 +1437,6 @@ dependencies = [ "svg_fmt", ] -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - [[package]] name = "hashbrown" version = "0.15.2" @@ -1518,9 +1508,9 @@ checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "hyper" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" dependencies = [ "bytes", "futures-channel", @@ -1537,9 +1527,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.3" +version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", @@ -1741,7 +1731,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -1801,9 +1791,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ "once_cell", "wasm-bindgen", @@ -1844,9 +1834,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.167" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libloading" @@ -1866,7 +1856,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", ] [[package]] @@ -2032,9 +2022,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" dependencies = [ "adler2", "simd-adler32", @@ -2053,9 +2043,9 @@ dependencies = [ [[package]] name = "naga" -version = "23.0.0" +version = "23.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d5941e45a15b53aad4375eedf02033adb7a28931eedc31117faffa52e6a857e" +checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set", @@ -2411,9 +2401,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -2482,7 +2472,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.7", + "redox_syscall 0.5.8", "smallvec", "windows-targets 0.52.6", ] @@ -2494,7 +2484,7 @@ source = "git+https://github.com/linebender/parley?rev=16b62518d467487ee15cb230f dependencies = [ "accesskit", "fontique", - "hashbrown 0.15.2", + "hashbrown", "peniko", "skrifa 0.22.3", "swash", @@ -2574,9 +2564,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -2697,7 +2687,7 @@ dependencies = [ "rustc-hash 2.1.0", "rustls", "socket2", - "thiserror 2.0.4", + "thiserror 2.0.9", "tokio", "tracing", ] @@ -2716,7 +2706,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.4", + "thiserror 2.0.9", "tinyvec", "tracing", "web-time", @@ -2724,9 +2714,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.7" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a626c6807713b15cac82a6acaccd6043c9a5408c24baae07611fec3f243da" +checksum = "1c40286217b4ba3a71d644d752e6a0b71f13f1b6a2c5311acfcbe0c2418ed904" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -2800,9 +2790,9 @@ dependencies = [ [[package]] name = "read-fonts" -version = "0.22.5" +version = "0.22.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a04b892cb6f91951f144c33321843790c8574c825aafdb16d815fd7183b5229" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" dependencies = [ "bytemuck", "font-types 0.7.3", @@ -2829,9 +2819,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -2969,22 +2959,22 @@ checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" [[package]] name = "rustix" -version = "0.38.41" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.19" +version = "0.23.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" dependencies = [ "once_cell", "ring", @@ -3005,9 +2995,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" dependencies = [ "web-time", ] @@ -3091,9 +3081,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.134" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d" dependencies = [ "itoa", "memchr", @@ -3178,7 +3168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" dependencies = [ "bytemuck", - "read-fonts 0.22.5", + "read-fonts 0.22.7", ] [[package]] @@ -3426,11 +3416,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.9", ] [[package]] @@ -3446,9 +3436,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4" dependencies = [ "proc-macro2", "quote", @@ -3535,9 +3525,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" dependencies = [ "tinyvec_macros", ] @@ -3579,12 +3569,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] @@ -3711,9 +3700,9 @@ dependencies = [ [[package]] name = "tracy-client" -version = "0.17.4" +version = "0.17.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "746b078c6a09ebfd5594609049e07116735c304671eaab06ce749854d23435bc" +checksum = "73202d787346a5418f8222eddb5a00f29ea47caf3c7d38a8f2f69f8455fa7c7e" dependencies = [ "loom", "once_cell", @@ -3722,9 +3711,9 @@ dependencies = [ [[package]] name = "tracy-client-sys" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3637e734239e12ab152cd269302500bd063f37624ee210cd04b4936ed671f3b1" +checksum = "69fff37da548239c3bf9e64a12193d261e8b22b660991c6fd2df057c168f435f" dependencies = [ "cc", "windows-targets 0.52.6", @@ -3734,7 +3723,7 @@ dependencies = [ name = "tree_arena" version = "0.1.0" dependencies = [ - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -3837,7 +3826,7 @@ dependencies = [ "png", "skrifa 0.26.2", "static_assertions", - "thiserror 2.0.4", + "thiserror 2.0.9", "vello_encoding", "vello_shaders", "wgpu", @@ -3863,7 +3852,7 @@ source = "git+https://github.com/linebender/vello?rev=a71236c7c8da10a6eaad460226 dependencies = [ "bytemuck", "naga", - "thiserror 2.0.4", + "thiserror 2.0.9", "vello_encoding", ] @@ -3900,9 +3889,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -3911,13 +3900,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -3926,9 +3914,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.47" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" dependencies = [ "cfg-if", "js-sys", @@ -3939,9 +3927,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3949,9 +3937,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", @@ -3962,9 +3950,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.97" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" [[package]] name = "wayland-backend" @@ -4077,9 +4065,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.74" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" dependencies = [ "js-sys", "wasm-bindgen", @@ -4535,9 +4523,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winit" -version = "0.30.5" +version = "0.30.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be9e76a1f1077e04a411f0b989cbd3c93339e1771cb41e71ac4aee95bfd2c67" +checksum = "7c3d72dfa0f47e429290cd0d236884ca02f22dbd5dd33a43ad2b8bf4d79b6c18" dependencies = [ "ahash", "android-activity", @@ -4919,9 +4907,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" +checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" dependencies = [ "zune-core", ] From 8e012adb9422a516262284bbfec31b79a3cc6b82 Mon Sep 17 00:00:00 2001 From: Markus Kohlhase Date: Sun, 22 Dec 2024 18:22:55 +0100 Subject: [PATCH 18/18] Pass imutable reference of state to init function --- xilem_web/src/concurrent/memoized_await.rs | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/xilem_web/src/concurrent/memoized_await.rs b/xilem_web/src/concurrent/memoized_await.rs index dbb540fdc..28d176063 100644 --- a/xilem_web/src/concurrent/memoized_await.rs +++ b/xilem_web/src/concurrent/memoized_await.rs @@ -38,7 +38,7 @@ impl where FOut: fmt::Debug + 'static, F: Future + 'static, - InitFuture: Fn(&mut State, &Data) -> F, + InitFuture: Fn(State, &Data) -> F, { /// Debounce the `init_future` function, when `data` updates, /// when `reset_debounce_on_update == false` then this throttles updates each `milliseconds` @@ -64,7 +64,7 @@ impl where StreamItem: fmt::Debug + 'static, F: Stream + 'static, - InitStream: Fn(&mut State, &Data) -> F, + InitStream: Fn(State, &Data) -> F, { /// Debounce the `init_stream` function, when `data` updates, /// when `reset_debounce_on_update == false` then this throttles updates each `milliseconds` @@ -88,9 +88,9 @@ where fn init_future( m: &MemoizedFuture, thunk: Rc, - state: &mut State, + state: &State, ) where - InitFuture: Fn(&mut State, &Data) -> F + 'static, + InitFuture: Fn(&State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, F: Future + 'static, { @@ -103,9 +103,9 @@ fn init_future( fn init_stream( m: &MemoizedFuture, thunk: Rc, - state: &mut State, + state: &State, ) where - InitStream: Fn(&mut State, &Data) -> F + 'static, + InitStream: Fn(&State, &Data) -> F + 'static, StreamItem: fmt::Debug + 'static, F: Stream + 'static, { @@ -148,7 +148,7 @@ where Data: PartialEq + 'static, FOut: fmt::Debug + 'static, F: Future + 'static, - InitFuture: Fn(&mut State, &Data) -> F + 'static, + InitFuture: Fn(&State, &Data) -> F + 'static, OA: OptionalAction + 'static, Callback: Fn(&mut State, FOut) -> OA + 'static, { @@ -206,7 +206,7 @@ where Data: PartialEq + 'static, StreamItem: fmt::Debug + 'static, F: Stream + 'static, - InitStream: Fn(&mut State, &Data) -> F + 'static, + InitStream: Fn(&State, &Data) -> F + 'static, OA: OptionalAction + 'static, Callback: Fn(&mut State, StreamItem) -> OA + 'static, { @@ -312,7 +312,7 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitFuture: Fn(&mut State, &Data) -> F + 'static, + InitFuture: Fn(&State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, Data: PartialEq + 'static, F: Future + 'static, @@ -359,7 +359,7 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitStream: Fn(&mut State, &Data) -> F + 'static, + InitStream: Fn(&State, &Data) -> F + 'static, StreamItem: fmt::Debug + 'static, Data: PartialEq + 'static, F: Stream + 'static, @@ -405,7 +405,7 @@ where State: 'static, Action: 'static, OA: OptionalAction + 'static, - InitFuture: Fn(&mut State, &Data) -> F + 'static, + InitFuture: Fn(&State, &Data) -> F + 'static, FOut: fmt::Debug + 'static, Data: PartialEq + 'static, F: 'static, @@ -472,7 +472,7 @@ where init_future: I, ) -> MessageResult where - I: Fn(&Self, Rc, &mut State), + I: Fn(&Self, Rc, &State), { assert_eq!(id_path.len(), 1); if id_path[0].routing_id() == view_state.generation {