-
Notifications
You must be signed in to change notification settings - Fork 124
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
Add fine-grained tree-structure tracking in xilem
and effcient tree-updates in xilem_web
#160
Add fine-grained tree-structure tracking in xilem
and effcient tree-updates in xilem_web
#160
Conversation
…Splice` trait and optimized tree-structure diffing in `xilem_web` with it
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 I mentioned in the zulip thread, I'm of two minds on this.
On the one hand, I kinda hate VecSplice and I think it's a very confusing abstraction, and I think this PR compounds the things I don't like.
On the other hand, this is good quality code, and it's an incremental improvement.
I'd be a lot more comfortable with this PR if it included unit tests. I'm honestly feeling extremely nervous about the existing VecSplice code, because after reading it, I notice that a lot of its inner complexity isn't currently used by our web and widget code. Like, skip
isn't even called anywhere.
So, given that how VecSplice is supposed to work is undocumented, I'm guessing that whether it actually works can only be known by those who can peer into the thoughts of Raph Levien.
Anyway, tests and documentation aside, my other sticking point is that I'd like the splicing code to stay out of the backend side. Since that backend is soon going to be replaced by a different crate, your changes would be easier to integrate with the Masonry merge if they interacted with the backend as little as possible. Essentially, TreeStructure
is the only datastructure I'd want the backend to be aware of.
If the backend-related concerns are addressed, I'd be okay to merge this PR. Consider everything else extra credit.
I still think it's mostly because it's not really documented that well (and it's not the easiest to digest code code admittedly, but with good documentation it should probably not be necessary to dive that deep into the details). The general idea how it's supposed to work can be extracted from the code though? I don't want to defend it as I also generally like a more mutation-log based approach as discussed on zulip. Well I can write some tests as this could also well serve as documentation (but I don't like to do unnecessary work when you think the Anyway, should #159 be just merged with/as this PR or does it make sense to do these separately? |
Fair enough. It's far outside my current priorities that I don't expect this code to go away for at least 6 months, but I do want to eventually refactor it, so I get your reluctance. I think tests would be better, but Xilem current has virtually none, so it would be unfair to hold your PR to a higher standard. Consider tests optional for getting this PR accepted. |
I think I remember why I put the code of the tree-structure changes into the widget part of the codebase. I think I once had it within the views, until I recognized that the type of the ids were different between view/widget... This makes stuff more complicated because of the I'm currently brainstorming how to get around that without shuffling a lot of code (or ugly hacks). |
So one idea I have, is extending the So taking the content of the let (id, state, element) = <V as View<T, A>>::build(self, cx);
elements.push(Pod::new(element), cx); // applys children tree-structure changes to the newly created Pod
(state, id) Does that sound feasible? |
I've done that, I'm not extremely happy about it, it introduces some kind of accidental complexity I think, and may be slightly brittle (as it requires a clean 1:1 relation of Anyway how I have "solved" this for now is by having a which is called by the view that owns the children elements. And then register them with the above function. self.mutations.push(SpliceMutation::Add(element.id()));
cx.apply_children_tree_structure_mutations(element.id());
self.splice.push(element); Maybe there's a better way (open for suggestions) |
Can you push the commits where you implemented that? I think I don't see them. |
Yeah I wanted to get general feedback first, but since the changes were mostly done, you're probably right. I've pushed them (and added a few doc comments, such that it's hopefully comprehensible). I've also added tests for the actual tree-structure mutations, since they were a low hanging fruit, and is probably more likely to survive. (I think automated tests for the actual |
Maybe I'm missing something obvious, but is
Anyway, if think your code would get a lot more robust if you skipped the mutation list and applied the mutations directly as they come. (Also, quick question, have you tried running some Xilem apps, eg the Taffy example, and checked that the child tracking actually worked?) |
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 feel much better about this code now. I definitely like the added unit tests.
If we can resolve the bit with mark_children_tree_structure_mutations
then we're good to merge.
src/view/linear_layout.rs
Outdated
@@ -84,12 +89,17 @@ impl<T, A, VT: ViewSequence<T, A>> View<T, A> for LinearLayout<T, A, VT> { | |||
element: &mut Self::Element, | |||
) -> ChangeFlags { | |||
let mut scratch = vec![]; | |||
let mut splice = VecSplice::new(&mut element.children, &mut scratch); | |||
|
|||
let mut tree_mutations = vec![]; // TODO(#160) could save some allocations by using View::State (scratch too) |
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.
Having an issue/PR number associated with TODOs is good, assuming the linked issue/PR includes enough info for an outsider contributor reading it to know how to contribute.
Since it's awkward to create issues for a PR that hasn't been merged yet, I think a good compromise would be to edit the top-level post of this PR with:
- A list of all the TODOs it adds.
- Enough info to operationalize resolving those TODOS (eg a short description of the problem and how to solve it). I know this is a lot to ask, so it's okay if this info is light, as long as it's immediately visible.
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.
Hmm, not sure about that, as I don't think a lot of people will look it up (vs the effort of creating such).
And when the TODO isn't clear in the code context (and/or is generally worth an issue) IMO it should really be an issue with a clear description after the PR has been merged.
After the recent update, I don't think such TODOs have survived (I don't think the TODO comment above is worth an issue TBH, I'll probably add a simple PR directly targeting that after this PR, to not further bloat this one).
I'm going a bit in more detail over the review later (when I got more time), thanks so far for the detailed review. One thing though:
That's unfortunately not the case, as it's the This is why it's pretty tricky currently to do this cleanly I think (without reshuffling the whole code-base). |
Ah, right, I forgot. Okay, so what I suggest is:
This adds some cruft to the codebase (we're basically tracking the same thing twice, if I understand correctly), but we'll deal with that later.
Two renames (Id -> ViewId, Id -> WidgetId) would be welcome, though probably not in this PR. |
Ok, I've updated this and applied all the review comments.
Yeah you're right, having access to I've implemented that though similarly as the
Sure, I don't don't want to merge untested code (did it via the good ol'
Yeah agree, we should keep PRs small and focused.
No that's not necessarily the case. Views can e.g. have ids while having the same |
xilem_web
xilem_web
xilem
and effcient tree-updates in xilem_web
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.
LGTM. Adding it to the merge queue.
New tracy image: ![image](https://github.com/user-attachments/assets/94e54c89-8159-4dd4-a521-4a7122f64375) New log tracing file: <details><summary>An overview of the new logs</summary> <p> ``` 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}: masonry::passes::update: RootWidget received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}: masonry::passes::update: SizedBox received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}:Flex{id=#3}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}:Flex{id=#3}:Prose{id=#1}: masonry::passes::update: Prose received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}:Flex{id=#3}:Label{id=#2}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}:Flex{id=#5}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#8}:SizedBox{id=#7}:Flex{id=#6}:Flex{id=#5}:VariableLabel{id=#4}: masonry::passes::update: VariableLabel received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#10}: masonry::passes::update: Button received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#10}:Label{id=#9}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#12}: masonry::passes::update: Button received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#12}:Label{id=#11}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#14}: masonry::passes::update: Button received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#14}:Label{id=#13}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#16}: masonry::passes::update: Button received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Flex{id=#17}:Button{id=#16}:Label{id=#15}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}: masonry::passes::update: Portal received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}: masonry::passes::update: SizedBox received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}:Flex{id=#20}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}:Flex{id=#20}:Prose{id=#18}: masonry::passes::update: Prose received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}:Flex{id=#20}:Label{id=#19}: masonry::passes::update: Label received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}:Flex{id=#22}: masonry::passes::update: Flex received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#24}:Flex{id=#23}:Flex{id=#22}:VariableLabel{id=#21}: masonry::passes::update: VariableLabel received Update::WidgetAdded 14:37:40.365Z TRACE update_new_widgets:RootWidget{id=#165}:Flex{id=#164}:Portal{id=#163}:Flex{id=#160}:SizedBox{id=#31}: masonry::passes::update: SizedBox received Update::WidgetAdded ``` </p> </details> This was originally an experiment into caching spans, but I determined that was non-viable due to the pass names.
This is based on top of #159 (which is a requirement for this PR, and the reason why this is a draft PR)
This implements fine-grained tree-structure tracking described in #54 using a
TreeTrackerSplice
(which implements theElementsSplice
introduced in #159).Currently it's only replacing the bloom filter which is only used for accessibility. But I expect this to be more useful later.
It's inspired by the lasagna branch in
druid
.