-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This ports the `Memoize` view from old xilem, slightly enhances it, by checking whether the given view callback is a non-capturing closure and not a function pointer (by asserting `std::mem::size_of::<F>() == 0`) It also ports the `Arc<impl View>` and `Arc<dyn AnyMasonryView>` from #164 including the example there to show how these two forms of memoization can be used.
- Loading branch information
Showing
10 changed files
with
292 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use std::sync::Arc; | ||
use xilem::view::{button, flex, memoize}; | ||
use xilem::{AnyMasonryView, MasonryView, Xilem}; | ||
|
||
// There are currently two ways to do memoization | ||
|
||
struct AppState { | ||
count: i32, | ||
increase_button: MemoizedArcView<i32>, | ||
} | ||
|
||
#[derive(Default)] | ||
struct MemoizedArcView<D> { | ||
data: D, | ||
// When TAITs are stabilized this can be a non-erased concrete type | ||
view: Option<Arc<dyn AnyMasonryView<AppState>>>, | ||
} | ||
|
||
// The following is an example to do memoization with an Arc | ||
fn increase_button(state: &mut AppState) -> Arc<dyn AnyMasonryView<AppState>> { | ||
if state.count != state.increase_button.data || state.increase_button.view.is_none() { | ||
let view = Arc::new(button( | ||
format!("current count is {}", state.count), | ||
|state: &mut AppState| { | ||
state.count += 1; | ||
}, | ||
)); | ||
state.increase_button.data = state.count; | ||
state.increase_button.view = Some(view.clone()); | ||
view | ||
} else { | ||
state.increase_button.view.as_ref().unwrap().clone() | ||
} | ||
} | ||
|
||
// This is the alternative with Memoize | ||
// Note how this requires a closure that returns the memoized view, while Arc does not | ||
fn decrease_button(state: &AppState) -> impl MasonryView<AppState> { | ||
memoize(state.count, |count| { | ||
button( | ||
format!("decrease the count: {count}"), | ||
|data: &mut AppState| data.count -= 1, | ||
) | ||
}) | ||
} | ||
|
||
fn reset_button() -> impl MasonryView<AppState> { | ||
button("reset", |data: &mut AppState| data.count = 0) | ||
} | ||
|
||
fn app_logic(state: &mut AppState) -> impl MasonryView<AppState> { | ||
flex(( | ||
increase_button(state), | ||
decrease_button(state), | ||
reset_button(), | ||
)) | ||
} | ||
|
||
fn main() { | ||
let data = AppState { | ||
count: 0, | ||
increase_button: Default::default(), | ||
}; | ||
|
||
let app = Xilem::new(data, app_logic); | ||
app.run_windowed("Memoization".into()).unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use std::{any::Any, ops::Deref, sync::Arc}; | ||
|
||
use masonry::widget::WidgetMut; | ||
|
||
use crate::{MasonryView, MessageResult, ViewCx, ViewId}; | ||
|
||
impl<State: 'static, Action: 'static, V: MasonryView<State, Action>> MasonryView<State, Action> | ||
for Arc<V> | ||
{ | ||
type ViewState = V::ViewState; | ||
|
||
type Element = V::Element; | ||
|
||
fn build(&self, cx: &mut ViewCx) -> (masonry::WidgetPod<Self::Element>, Self::ViewState) { | ||
self.deref().build(cx) | ||
} | ||
|
||
fn rebuild( | ||
&self, | ||
view_state: &mut Self::ViewState, | ||
cx: &mut ViewCx, | ||
prev: &Self, | ||
element: WidgetMut<Self::Element>, | ||
) { | ||
if !Arc::ptr_eq(self, prev) { | ||
self.deref().rebuild(view_state, cx, prev.deref(), element); | ||
} | ||
} | ||
|
||
fn message( | ||
&self, | ||
view_state: &mut Self::ViewState, | ||
id_path: &[ViewId], | ||
message: Box<dyn Any>, | ||
app_state: &mut State, | ||
) -> MessageResult<Action> { | ||
self.deref() | ||
.message(view_state, id_path, message, app_state) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Copyright 2024 the Xilem Authors | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
use std::any::Any; | ||
|
||
use masonry::{widget::WidgetMut, WidgetPod}; | ||
|
||
use crate::{MasonryView, MessageResult, ViewCx, ViewId}; | ||
|
||
pub struct Memoize<D, F> { | ||
data: D, | ||
child_cb: F, | ||
} | ||
|
||
pub struct MemoizeState<T, A, V: MasonryView<T, A>> { | ||
view: V, | ||
view_state: V::ViewState, | ||
dirty: bool, | ||
} | ||
|
||
impl<D, V, F> Memoize<D, F> | ||
where | ||
F: Fn(&D) -> V, | ||
{ | ||
const ASSERT_CONTEXTLESS_FN: () = { | ||
assert!( | ||
std::mem::size_of::<F>() == 0, | ||
" | ||
It's not possible to use function pointers or captured context in closures, | ||
as this potentially messes up the logic of memoize or produces unwanted effects. | ||
For example a different kind of view could be instantiated with a different callback, while the old one is still memoized, but it's not updated then. | ||
It's not possible in Rust currently to check whether the (content of the) callback has changed with the `Fn` trait, which would make this otherwise possible. | ||
" | ||
); | ||
}; | ||
|
||
pub fn new(data: D, child_cb: F) -> Self { | ||
#[allow(clippy::let_unit_value)] | ||
let _ = Self::ASSERT_CONTEXTLESS_FN; | ||
Memoize { data, child_cb } | ||
} | ||
} | ||
|
||
impl<State, Action, D, V, F> MasonryView<State, Action> for Memoize<D, F> | ||
where | ||
D: PartialEq + Send + Sync + 'static, | ||
V: MasonryView<State, Action>, | ||
F: Fn(&D) -> V + Send + Sync + 'static, | ||
{ | ||
type ViewState = MemoizeState<State, Action, V>; | ||
|
||
type Element = V::Element; | ||
|
||
fn build(&self, cx: &mut ViewCx) -> (WidgetPod<Self::Element>, Self::ViewState) { | ||
let view = (self.child_cb)(&self.data); | ||
let (element, view_state) = view.build(cx); | ||
let memoize_state = MemoizeState { | ||
view, | ||
view_state, | ||
dirty: false, | ||
}; | ||
(element, memoize_state) | ||
} | ||
|
||
fn rebuild( | ||
&self, | ||
view_state: &mut Self::ViewState, | ||
cx: &mut ViewCx, | ||
prev: &Self, | ||
element: WidgetMut<Self::Element>, | ||
) { | ||
if std::mem::take(&mut view_state.dirty) || prev.data != self.data { | ||
let view = (self.child_cb)(&self.data); | ||
view.rebuild(&mut view_state.view_state, cx, &view_state.view, element); | ||
view_state.view = view; | ||
} | ||
} | ||
|
||
fn message( | ||
&self, | ||
view_state: &mut Self::ViewState, | ||
id_path: &[ViewId], | ||
message: Box<dyn Any>, | ||
app_state: &mut State, | ||
) -> MessageResult<Action> { | ||
let r = view_state | ||
.view | ||
.message(&mut view_state.view_state, id_path, message, app_state); | ||
if matches!(r, MessageResult::RequestRebuild) { | ||
view_state.dirty = true; | ||
} | ||
r | ||
} | ||
} | ||
|
||
/// A static view, all of the content of the `view` should be constant, as this function is only run once | ||
pub fn static_view<V, F>(view: F) -> Memoize<(), impl Fn(&()) -> V> | ||
where | ||
F: Fn() -> V + Send + 'static, | ||
{ | ||
Memoize::new((), move |_: &()| view()) | ||
} | ||
|
||
/// Memoize the view, until the `data` changes (in which case `view` is called again) | ||
pub fn memoize<D, V, F>(data: D, view: F) -> Memoize<D, F> | ||
where | ||
F: Fn(&D) -> V + Send, | ||
{ | ||
Memoize::new(data, view) | ||
} |
Oops, something went wrong.