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
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

View File

@ -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<ViewState> {
pub struct RcState<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,
}
@ -218,13 +223,13 @@ where
V: View<State, Action, Context, Message> + ?Sized,
{
type Element = V::Element;
type ViewState = ArcState<V::ViewState>;
type ViewState = RcState<V::ViewState>;
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<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) {
self.deref()
.rebuild(prev, &mut view_state.view_state, ctx, element);
@ -271,3 +275,64 @@ where
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},
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<E>(Rc<E>);
pub struct Templated<V>(Rc<V>);
#[allow(unnameable_types)] // reason: Implementation detail, public because of trait visibility rules
pub struct TemplatedState<ViewState> {
view_state: ViewState,
dirty: bool,
}
impl<E> ViewMarker for Templated<E> {}
impl<State, Action, E> View<State, Action, ViewCtx, DynMessage> for Templated<E>
impl<V> ViewMarker for Templated<V> {}
impl<State, Action, V> View<State, Action, ViewCtx, DynMessage> for Templated<V>
where
State: '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) {
let type_id = TypeId::of::<Self>();
let (element, view_state) = if let Some((template_node, view)) = ctx.templates.get(&type_id)
{
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 (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<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) || !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::Element>,
) {
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<Action, DynMessage> {
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)
}
}