From 23f04ca3701fb79054046cd10dbafd4a5af438f3 Mon Sep 17 00:00:00 2001 From: Philipp Mildenberger Date: Mon, 11 Nov 2024 15:10:47 +0100 Subject: [PATCH] xilem_core: implement `View` for `Rc` (#732) I needed this in xilem_web, I could've used `Arc` as well, but I think it's fine to add this for consistency when an `Arc` is not needed. It's basically copy-pasta of the `Arc`. (I don't think a macro is worth it here?). Had to fix some resulting issues in the `Templated` view (due to ambiguity?). --------- Co-authored-by: Daniel McNab <36049421+DJMcNab@users.noreply.github.com> --- xilem_core/src/message.rs | 7 ++-- xilem_core/src/view.rs | 73 +++++++++++++++++++++++++++++++++++--- xilem_web/src/templated.rs | 49 ++++++++----------------- 3 files changed, 87 insertions(+), 42 deletions(-) diff --git a/xilem_core/src/message.rs b/xilem_core/src/message.rs index d358d643..383f568e 100644 --- a/xilem_core/src/message.rs +++ b/xilem_core/src/message.rs @@ -17,9 +17,10 @@ pub enum MessageResult { /// /// This allows for sub-sections of your app to use an elm-like architecture Action(Action), - // TODO: What does this mean? - /// This message's handler needs a rebuild to happen. - /// The exact semantics of this method haven't been determined. + /// A view has requested a rebuild, even though its value hasn't changed. + /// + /// This can happen for example by some kind of async action. + /// An example would be an async virtualized list, which fetches new entries, and requires a rebuild for the new entries. RequestRebuild, #[default] /// This event had no impact on the app state, or the impact it did have diff --git a/xilem_core/src/view.rs b/xilem_core/src/view.rs index 99dc97c5..7f2a9f4e 100644 --- a/xilem_core/src/view.rs +++ b/xilem_core/src/view.rs @@ -4,6 +4,7 @@ //! The primary view trait and associated trivial implementations. use alloc::boxed::Box; +use alloc::rc::Rc; use alloc::sync::Arc; use core::ops::Deref; @@ -205,8 +206,12 @@ where } #[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules -pub struct ArcState { +pub struct RcState { view_state: ViewState, + /// This is a flag that is set, when an inner view signifies that it requires a rebuild (via [`MessageResult::RequestRebuild`]). + /// This can happen, e.g. when an inner view wasn't changed by the app-developer directly (i.e. it points to the same view), + /// but e.g. through some kind of async action. + /// An example would be an async virtualized list, which fetches new entries, and requires a rebuild for the new entries. dirty: bool, } @@ -218,13 +223,13 @@ where V: View + ?Sized, { type Element = V::Element; - type ViewState = ArcState; + type ViewState = RcState; fn build(&self, ctx: &mut Context) -> (Self::Element, Self::ViewState) { let (element, view_state) = self.deref().build(ctx); ( element, - ArcState { + RcState { view_state, dirty: false, }, @@ -238,7 +243,6 @@ where ctx: &mut Context, element: Mut, ) { - // If this is the same value, or no rebuild was forced, there's no need to rebuild if core::mem::take(&mut view_state.dirty) || !Arc::ptr_eq(self, prev) { self.deref() .rebuild(prev, &mut view_state.view_state, ctx, element); @@ -271,3 +275,64 @@ where message_result } } + +impl ViewMarker for Rc {} +/// An implementation of [`View`] which only runs rebuild if the states are different +impl View for Rc +where + Context: ViewPathTracker, + V: View + ?Sized, +{ + type Element = V::Element; + type ViewState = RcState; + + fn build(&self, ctx: &mut Context) -> (Self::Element, Self::ViewState) { + let (element, view_state) = self.deref().build(ctx); + ( + element, + RcState { + view_state, + dirty: false, + }, + ) + } + + fn rebuild( + &self, + prev: &Self, + view_state: &mut Self::ViewState, + ctx: &mut Context, + element: Mut, + ) { + if core::mem::take(&mut view_state.dirty) || !Rc::ptr_eq(self, prev) { + self.deref() + .rebuild(prev, &mut view_state.view_state, ctx, element); + } + } + + fn teardown( + &self, + view_state: &mut Self::ViewState, + ctx: &mut Context, + element: Mut, + ) { + self.deref() + .teardown(&mut view_state.view_state, ctx, element); + } + + fn message( + &self, + view_state: &mut Self::ViewState, + id_path: &[ViewId], + message: Message, + app_state: &mut State, + ) -> MessageResult { + let message_result = + self.deref() + .message(&mut view_state.view_state, id_path, message, app_state); + if matches!(message_result, MessageResult::RequestRebuild) { + view_state.dirty = true; + } + message_result + } +} diff --git a/xilem_web/src/templated.rs b/xilem_web/src/templated.rs index 774839de..4266f84b 100644 --- a/xilem_web/src/templated.rs +++ b/xilem_web/src/templated.rs @@ -5,35 +5,29 @@ use crate::{ core::{MessageResult, Mut, View, ViewId, ViewMarker}, DomView, DynMessage, PodMut, ViewCtx, }; -use std::{any::TypeId, ops::Deref as _, rc::Rc}; +use std::{any::TypeId, rc::Rc}; use wasm_bindgen::UnwrapThrowExt; /// This view creates an internally cached deep-clone of the underlying DOM node. When the inner view is created again, this will be done more efficiently. -pub struct Templated(Rc); +pub struct Templated(Rc); -#[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules -pub struct TemplatedState { - view_state: ViewState, - dirty: bool, -} - -impl ViewMarker for Templated {} -impl View for Templated +impl ViewMarker for Templated {} +impl View for Templated where State: 'static, Action: 'static, - E: DomView, + V: DomView, { - type Element = E::Element; + type Element = V::Element; - type ViewState = TemplatedState; + type ViewState = as View>::ViewState; fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { let type_id = TypeId::of::(); let (element, view_state) = if let Some((template_node, view)) = ctx.templates.get(&type_id) { let prev = view.clone(); - let prev = prev.downcast_ref::().unwrap_throw(); + let prev = prev.downcast_ref::>().unwrap_throw(); let node = template_node.clone_node_with_deep(true).unwrap_throw(); let (mut el, mut state) = ctx.with_hydration_node(node, |ctx| prev.build(ctx)); el.apply_changes(); @@ -50,14 +44,11 @@ where .clone_node_with_deep(true) .unwrap_throw(); - ctx.templates.insert(type_id, (template, self.0.clone())); + ctx.templates + .insert(type_id, (template, Rc::new(self.0.clone()))); (element, state) }; - let state = TemplatedState { - view_state, - dirty: false, - }; - (element, state) + (element, view_state) } fn rebuild( @@ -67,12 +58,7 @@ where ctx: &mut ViewCtx, element: Mut, ) { - // If this is the same value, or no rebuild was forced, there's no need to rebuild - if core::mem::take(&mut view_state.dirty) || !Rc::ptr_eq(&self.0, &prev.0) { - self.0 - .deref() - .rebuild(&prev.0, &mut view_state.view_state, ctx, element); - } + self.0.rebuild(&prev.0, view_state, ctx, element); } fn teardown( @@ -81,7 +67,7 @@ where ctx: &mut ViewCtx, element: Mut, ) { - self.0.teardown(&mut view_state.view_state, ctx, element); + self.0.teardown(view_state, ctx, element); } fn message( @@ -91,14 +77,7 @@ where message: DynMessage, app_state: &mut State, ) -> MessageResult { - let message_result = - self.0 - .deref() - .message(&mut view_state.view_state, id_path, message, app_state); - if matches!(message_result, MessageResult::RequestRebuild) { - view_state.dirty = true; - } - message_result + self.0.message(view_state, id_path, message, app_state) } }