xilem_core: implement `View` for `Rc<impl View>` (#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>
This commit is contained in:
Philipp Mildenberger 2024-11-11 15:10:47 +01:00 committed by GitHub
parent 73d33ee1c9
commit 23f04ca370
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 42 deletions

View File

@ -17,9 +17,10 @@ pub enum MessageResult<Action, Message = DynMessage> {
/// ///
/// This allows for sub-sections of your app to use an elm-like architecture /// This allows for sub-sections of your app to use an elm-like architecture
Action(Action), Action(Action),
// TODO: What does this mean? /// A view has requested a rebuild, even though its value hasn't changed.
/// This message's handler needs a rebuild to happen. ///
/// The exact semantics of this method haven't been determined. /// 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, RequestRebuild,
#[default] #[default]
/// This event had no impact on the app state, or the impact it did have /// This event had no impact on the app state, or the impact it did have

View File

@ -4,6 +4,7 @@
//! The primary view trait and associated trivial implementations. //! The primary view trait and associated trivial implementations.
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::rc::Rc;
use alloc::sync::Arc; use alloc::sync::Arc;
use core::ops::Deref; use core::ops::Deref;
@ -205,8 +206,12 @@ where
} }
#[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules #[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules
pub struct ArcState<ViewState> { pub struct RcState<ViewState> {
view_state: ViewState, 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, dirty: bool,
} }
@ -218,13 +223,13 @@ where
V: View<State, Action, Context, Message> + ?Sized, V: View<State, Action, Context, Message> + ?Sized,
{ {
type Element = V::Element; type Element = V::Element;
type ViewState = ArcState<V::ViewState>; type ViewState = RcState<V::ViewState>;
fn build(&self, ctx: &mut Context) -> (Self::Element, Self::ViewState) { fn build(&self, ctx: &mut Context) -> (Self::Element, Self::ViewState) {
let (element, view_state) = self.deref().build(ctx); let (element, view_state) = self.deref().build(ctx);
( (
element, element,
ArcState { RcState {
view_state, view_state,
dirty: false, dirty: false,
}, },
@ -238,7 +243,6 @@ where
ctx: &mut Context, ctx: &mut Context,
element: Mut<Self::Element>, element: Mut<Self::Element>,
) { ) {
// 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) { if core::mem::take(&mut view_state.dirty) || !Arc::ptr_eq(self, prev) {
self.deref() self.deref()
.rebuild(prev, &mut view_state.view_state, ctx, element); .rebuild(prev, &mut view_state.view_state, ctx, element);
@ -271,3 +275,64 @@ where
message_result message_result
} }
} }
impl<V: ?Sized> ViewMarker for Rc<V> {}
/// An implementation of [`View`] which only runs rebuild if the states are different
impl<State, Action, Context, Message, V> View<State, Action, Context, Message> for Rc<V>
where
Context: ViewPathTracker,
V: View<State, Action, Context, Message> + ?Sized,
{
type Element = V::Element;
type ViewState = RcState<V::ViewState>;
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<Self::Element>,
) {
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::Element>,
) {
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<Action, Message> {
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
}
}

View File

@ -5,35 +5,29 @@ use crate::{
core::{MessageResult, Mut, View, ViewId, ViewMarker}, core::{MessageResult, Mut, View, ViewId, ViewMarker},
DomView, DynMessage, PodMut, ViewCtx, DomView, DynMessage, PodMut, ViewCtx,
}; };
use std::{any::TypeId, ops::Deref as _, rc::Rc}; use std::{any::TypeId, rc::Rc};
use wasm_bindgen::UnwrapThrowExt; 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. /// 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<E>(Rc<E>); pub struct Templated<V>(Rc<V>);
#[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules impl<V> ViewMarker for Templated<V> {}
pub struct TemplatedState<ViewState> { impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Templated<V>
view_state: ViewState,
dirty: bool,
}
impl<E> ViewMarker for Templated<E> {}
impl<State, Action, E> View<State, Action, ViewCtx, DynMessage> for Templated<E>
where where
State: 'static, State: 'static,
Action: 'static, Action: 'static,
E: DomView<State, Action>, V: DomView<State, Action>,
{ {
type Element = E::Element; type Element = V::Element;
type ViewState = TemplatedState<E::ViewState>; type ViewState = <Rc<V> as View<State, Action, ViewCtx, DynMessage>>::ViewState;
fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) { fn build(&self, ctx: &mut ViewCtx) -> (Self::Element, Self::ViewState) {
let type_id = TypeId::of::<Self>(); let type_id = TypeId::of::<Self>();
let (element, view_state) = if let Some((template_node, view)) = ctx.templates.get(&type_id) let (element, view_state) = if let Some((template_node, view)) = ctx.templates.get(&type_id)
{ {
let prev = view.clone(); let prev = view.clone();
let prev = prev.downcast_ref::<E>().unwrap_throw(); let prev = prev.downcast_ref::<Rc<V>>().unwrap_throw();
let node = template_node.clone_node_with_deep(true).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)); let (mut el, mut state) = ctx.with_hydration_node(node, |ctx| prev.build(ctx));
el.apply_changes(); el.apply_changes();
@ -50,14 +44,11 @@ where
.clone_node_with_deep(true) .clone_node_with_deep(true)
.unwrap_throw(); .unwrap_throw();
ctx.templates.insert(type_id, (template, self.0.clone())); ctx.templates
.insert(type_id, (template, Rc::new(self.0.clone())));
(element, state) (element, state)
}; };
let state = TemplatedState { (element, view_state)
view_state,
dirty: false,
};
(element, state)
} }
fn rebuild( fn rebuild(
@ -67,12 +58,7 @@ where
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
element: Mut<Self::Element>, element: Mut<Self::Element>,
) { ) {
// If this is the same value, or no rebuild was forced, there's no need to rebuild self.0.rebuild(&prev.0, view_state, ctx, element);
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);
}
} }
fn teardown( fn teardown(
@ -81,7 +67,7 @@ where
ctx: &mut ViewCtx, ctx: &mut ViewCtx,
element: Mut<Self::Element>, element: Mut<Self::Element>,
) { ) {
self.0.teardown(&mut view_state.view_state, ctx, element); self.0.teardown(view_state, ctx, element);
} }
fn message( fn message(
@ -91,14 +77,7 @@ where
message: DynMessage, message: DynMessage,
app_state: &mut State, app_state: &mut State,
) -> MessageResult<Action, DynMessage> { ) -> MessageResult<Action, DynMessage> {
let message_result = self.0.message(view_state, id_path, message, app_state)
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
} }
} }