Skip to content
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

Merged
merged 5 commits into from
May 20, 2024

Conversation

Philipp-M
Copy link
Contributor

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.

@Philipp-M Philipp-M force-pushed the xilem_masonry-memoization branch from b321cd7 to 3c748a3 Compare May 12, 2024 18:56
@Philipp-M Philipp-M changed the title xilem_masonry: Add Memoization views (Memoize and Arc<impl View>) xilem: Add Memoization views (Memoize and Arc<impl View>) May 12, 2024
Philipp-M added 3 commits May 12, 2024 21:01
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.
@Philipp-M Philipp-M force-pushed the xilem_masonry-memoization branch from 3c748a3 to cd34133 Compare May 12, 2024 19:01
Comment on lines 41 to 44
assert!(
std::mem::size_of::<F>() == 0,
"The callback is not allowed to be a function pointer or a closure capturing context"
);
Copy link
Member

@jplatte jplatte May 12, 2024

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"
    );
};

Copy link
Contributor Author

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?

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"
Copy link
Member

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

Copy link
Contributor Author

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?

Comment on lines 11 to 13
count: i32,
// When TAITs are stabilized this can be a non-erased concrete type
count_view: Option<Arc<dyn AnyMasonryView<AppState>>>,
Copy link
Member

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

Copy link
Contributor Author

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?

Copy link
Member

@DJMcNab DJMcNab May 13, 2024

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

Copy link
Contributor Author

@Philipp-M Philipp-M May 14, 2024

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

Copy link
Member

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)

Copy link
Contributor Author

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 {
Copy link
Member

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 Arcs

Copy link
Contributor Author

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 Arcs

Copy link
Member

@DJMcNab DJMcNab left a 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.

@Philipp-M
Copy link
Contributor Author

(Quoting as well from zulip)

this is also important for hot reloading
... it could just not memoise in hot reload mode, which would work I suppose

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 TypeId of the callback F changes, and add the TypeId of F to the (serializable) ViewState maybe (not sure how robust that is though)?

I think all of this is still kinda fluid, so in case Arc (with extensions?) proves more useful or Memoize limits us somewhat, we may also get rid of Memoize later I guess.

@Philipp-M Philipp-M added this pull request to the merge queue May 20, 2024
Merged via the queue into linebender:main with commit c6f6c78 May 20, 2024
7 checks passed
@Philipp-M Philipp-M deleted the xilem_masonry-memoization branch May 20, 2024 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants