-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
xilem: Add Memoization views (Memoize
and Arc<impl View>
)
#267
xilem: Add Memoization views (Memoize
and Arc<impl View>
)
#267
Conversation
b321cd7
to
3c748a3
Compare
Memoize
and Arc<impl View>
)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.
3c748a3
to
cd34133
Compare
xilem/src/view/memoize.rs
Outdated
assert!( | ||
std::mem::size_of::<F>() == 0, | ||
"The callback is not allowed to be a function pointer or a closure capturing context" | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could be turned into a compile-time check by moving it to the inherent impl
above (the one containing pub fn new
) as
const _ASSERT_CONTEXTLESS_FN: () = {
assert!(
std::mem::size_of::<F>() == 0,
"The callback is not allowed to be a function pointer or a closure capturing context"
);
};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh pretty cool idea, I had compile-time check in mind, but thought it would be more complicated and requires unstable Rust, turns out, not the case, thanks!
I had to add ASSERT_CONTEXTLESS_FN
as statement into the new
function though, otherwise the compiler would just optimize it away (not sure whether that's a compiler bug, as I think it should also work without, because const
needs to be evaluated anyways?
xilem/src/view/memoize.rs
Outdated
const ASSERT_CONTEXTLESS_FN: () = { | ||
assert!( | ||
std::mem::size_of::<F>() == 0, | ||
"The callback is not allowed to be a function pointer or a closure capturing context" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should explain why here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the content of the assert in the last commit better?
xilem/examples/memoization.rs
Outdated
count: i32, | ||
// When TAITs are stabilized this can be a non-erased concrete type | ||
count_view: Option<Arc<dyn AnyMasonryView<AppState>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth making a generic type which encapsulates this?
I.e. which owns the count and the Option<Arc<V>>
And so when the data is updated, it invalidates the view.
Because it seems incredibly easy to forget otherwise. IMO, that seems like a better fit for the Xilem threading model, although it's complicated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah agree. But I'm not sure if I understand you completely, do you mean something like in the last commit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I mean as a library type
It isn't quite a View, but it's a utility on top of views
It also should be able to better encapsulate the behaviour
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically Memoize
but with an Arc
instead? I'm still not quite sure what you mean TBH.
So something like
struct MemoizedArcView<F, D, State> {
data: D,
// When TAITs are stabilized this can be a non-erased concrete type
view: Option<Arc<dyn AnyMasonryView<State>>>,
view_factory: F,
}
impl<State, D: PartialEq, F: Fn(&D) -> Arc<dyn AnyMasonryView<State>>>
MemoizedArcView<F, D, State>
{
fn view(&mut self) -> Arc<dyn AnyMasonryView<State>> {
if let Some(view) = &self.view {
view.clone()
} else {
let view = (self.view_factory)(&self.data);
self.view = Some(view.clone());
view
}
}
fn set_data(&mut self, data: D) {
if self.data != data {
self.data = data;
self.view = None;
}
}
}
fn memoized_arc_view<State, D, F>(data: D, view_factory: F) -> MemoizedArcView<F, D, State> {
MemoizedArcView {
data,
view: None,
view_factory,
}
}
?
Though, I think having more direct control over invalidation with an Arc
is an advantage over Memoize
(not that this utility you're talking about is mutually exclusive though).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't quite what I was trying to describe, but that's probably better as a follow-up (probably related to #235)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes might be, alright, keeping it in the example for now, we can still refactor later...
@@ -146,7 +146,7 @@ where | |||
event_loop_runner::run(window_attributes, self.root_widget, self.driver) | |||
} | |||
} | |||
pub trait MasonryView<State, Action = ()>: Send + 'static { | |||
pub trait MasonryView<State, Action = ()>: Send + Sync + 'static { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think requiring Sync
here makes sense, although we don't require it for anything other than Arc
s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah or views derived from Arc
as well or anything, that actually is shared between the runtime and the AppState
, but that'll likely be mostly Arc
s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed in office hours, I'm personally much more comfortable with the Arc
impls, and I find the normal memoisation view a bit concerning from a conceptual threading point of view (this is also important for hot reloading, if the view creation and rebuilding happen in different processes)
But I don't think that opinion is shared by others, and so we agreed to land this.
(Quoting as well from zulip)
I may not yet get all the details of hot-reloading (have not dug into that yet), but maybe we could just check if the I think all of this is still kinda fluid, so in case |
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 assertingstd::mem::size_of::<F>() == 0
)It also ports the
Arc<impl View>
andArc<dyn AnyMasonryView>
from #164 including the example there to show how these two forms of memoization can be used.