forked from linebender/xilem
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
xilem_masonry: Add Memoization views (
Memoize
and Arc<impl View>
)
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 linebender#164 including the example there to show how these two forms of memoization can be used.
- Loading branch information
Showing
10 changed files
with
274 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,65 @@ | ||
use std::sync::Arc; | ||
use xilem::view::{button, flex, memoize}; | ||
use xilem::{AnyMasonryView, MasonryView, Xilem}; | ||
|
||
// There are currently two ways to do memoization | ||
|
||
fn app_logic(state: &mut AppState) -> impl MasonryView<AppState> { | ||
// The following is an example to do memoization with an Arc | ||
let increase_button = if let Some(view) = &state.count_view { | ||
view.clone() | ||
} else { | ||
let view = state.make_increase_button(); | ||
state.count_view = Some(view.clone()); | ||
view | ||
}; | ||
|
||
flex(( | ||
increase_button, | ||
// This is the alternative with Memoize | ||
// Note how this requires a closure that returns the memoized view, while Arc does not | ||
memoize(state.count, |count| { | ||
button( | ||
format!("decrease the count: {count}"), | ||
|data: &mut AppState| { | ||
data.count_view = None; | ||
data.count -= 1; | ||
}, | ||
) | ||
}), | ||
button("reset", |data: &mut AppState| { | ||
if data.count != 0 { | ||
data.count_view = None; | ||
} | ||
data.count = 0; | ||
}), | ||
)) | ||
} | ||
|
||
struct AppState { | ||
count: i32, | ||
// When TAITs are stabilized this can be a non-erased concrete type | ||
count_view: Option<Arc<dyn AnyMasonryView<AppState>>>, | ||
} | ||
|
||
impl AppState { | ||
fn make_increase_button(&self) -> Arc<dyn AnyMasonryView<AppState>> { | ||
Arc::new(button( | ||
format!("current count is {}", self.count), | ||
|state: &mut AppState| { | ||
state.count += 1; | ||
state.count_view = None; | ||
}, | ||
)) | ||
} | ||
} | ||
|
||
fn main() { | ||
let data = AppState { | ||
count: 0, | ||
count_view: None, | ||
}; | ||
|
||
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,40 @@ | ||
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,101 @@ | ||
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, | ||
{ | ||
pub fn new(data: D, child_cb: F) -> Self { | ||
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) { | ||
assert!( | ||
std::mem::size_of::<F>() == 0, | ||
"The callback is not allowed to be a function pointer or a closure capturing context" | ||
); | ||
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, | ||
{ | ||
assert!( | ||
std::mem::size_of::<F>() == 0, | ||
"The callback is not allowed to be a function pointer or a closure capturing context" | ||
); | ||
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.