-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Optimise layout performance #1722
Conversation
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.
Hey, thanks for this exploration!
Important changes to the library (like changes to the Widget
API) need to go through the RFC process.
There is a lot to unpack here. As far as I understand, the motivation behind these changes is an external crate that you are working on.
If we are going to let an external crate drive further design of the library, we need to find an agreement on the use cases that this external crate is tackling and why it is the best approach for the library.
If the point being made is that these changes will benefit the library independently of the external crate, then we need to specify exactly how (e.g. listing use cases and examples, etc.) and consider any tradeoffs.
This should be the first step before we can consider writing any code. Feel free to open a discussion on Discord!
That said, have you considered using an extension trait to add the measure
method in your own crate?
Ah right. I'll look into creating an RFC then.
That won't work unfortunately, because I need the measure method to be implemented by Iced's built-in types. Otherwise mixing and matching say an Some quick responses, as it might take a while to write up an RFC:
Yes. These changes are driven by the (performance) needs of iced_taffy, a library that implements Taffy's Flexbox and CSS Grid layout modes as Iced widgets. Not everyone likes CSS layout modes, but lots of people do, so it seems sensible to offer it as an external library that people can choose to use if they wish to. The Grid functionality would be particularly useful as Iced doesn't doesn't currently have a grid layout widget (and the one in
Indeed it is. Nothing in this PR is taffy-specific. It just contains performance optimisations for layout which allow the initial passes of multi-pass layout modes (which would include Iced's existing The tradeoff is an extra method to implement (although it could be given a default implementation to avoid the need to implement it for widgets where full layout is fast (e.g. an image widget)). |
I'm not sure we want to invest effort in making multi-pass layout modes a thing. If anything, I'd prefer to see efforts that go in the opposite direction! The current layout engine is inspired by We should explore all of the possibilities of the single-pass approach and understand its limitations. Defaulting to support multi-pass because Flexbox and CSS Grid do it and lots of people like it isn't a good enough argument. Caching also opens a brand new can of worms. We need to talk cache invalidation! How are layouts reused? How do we efficiently detect when to reuse them? We have changed Keep all of this in mind for the RFC! |
Actually it is a great argument IMO. Flexbox/Grid, simplifies the layout a lot versus the 2000's era aged system of constraints. |
I disagree. Flexbox and CSS are full of pitfalls and suffering. Brittle, full of redundancy, and a bunch of structure must be in place to make everything work. Web developers are so used to the pain that they have become numb to the torture devices they use every day. I was one of them for 15 years. It wasn't until I tried Elm that I found how much happier I was with a better set of tools.
I am not proposing we go back to using tables to layout your UI. The example I provided above is Flutter, which was originally released in 2017. |
😆😆 I think we are getting a little distracted with CSS here, what I meant is these days a good UI toolkit should support items wrapping on the next row and would be great if styles could conditionally apply at various view port sizes (similar to CSS @media). Any respectable UI needs to be responsive at any view port size, these days apps go in car dashboard screens and TVs and foldables and what not. I've worked on web apps where things animated into place as you were resizing the browser window and have been easily ported to large number of platforms because of the doctrine, design once, run everywhere. Not having this feels like a step backward. |
Hah, of course. I was being hyperbolic. Or was I? Most of the experienced web developers I know that have seen the grass on the other side would agree on the utter insanity of the web stack. In any case, a Responsiveness can also be achieved already with the |
I think fundamentally my point of view is: let people lay things out however they like. The framework doesn't need to be opinionated about layout, so why should it. If people want to use a less efficient method then they can. That's on them. Chances are it won't cause problems in practice, and if it does then they can optimise that code. If they want to use a more efficient method from the start then they also can. It seems that your view is that Iced ought to pick One True Way to do layout. Which which I suppose fits given it's stated influence from Elm. What I would point out is that this is exactly why very few people use Elm these days: they were overly opinionated about their stack and refused to include escape hatches to allow people to deal with real-world constraints pragmatically. So people used something else that would let them do that. Perhaps that will be less of an issue on Desktop where you have more control over the underlying platform. But it's definitely possible to create a framework that's both opinionated and flexible by exposing opinionated high-level APIs on top of less opinionated low-level APIs. Laravel is my favourite example of this. IMO it's the best of both worlds, and I would definitely encourage you to go down that route (while respecting that it's your framework, and you can choose whichever approach you want to).
I think I can somewhat agree that web layout is unnecessarily quirky. I would be pretty happy with Flutter style layout. However, Iced doesn't currently have that, and there are lots of things that can't currently be done with Iced's layout widgets. For example, percentage based sizing or complex grid layouts. I want these kind of things available ASAP so I can get on with laying out my app. And wiring up the web layout that already exists as a library is going to be by far the quickest way to get there. |
Not at all. The current layout system isn't opinionated. This isn't the reason I have issues with the changes. My first comment reflects my view:
Before we write any generic code, we need to look at multiple use cases individually to evaluate if their similarities warrant a generic solution. Maybe the best approach is to tackle each specific use case directly instead of writing a generic solution! Use cases come first, then specific solutions. A pattern may arise and a generic solution may be adequate, but it's not guaranteed. You argued that nothing in this PR is specific to your use case. Therefore, it is supposed to be a generic change. In order to justify a generic solution, we need to prove it is arising somewhat organically from a bunch of use cases (not just yours!). We can't let a single use case drive a change to the generic
Percentage-based sizing should be possible with "Complex grid layouts" sounds too generic. We have a What is probably very hard to build is a completely generic and powerful solution for any kind of "complex grid layout" (a la Flexbox grid). This is why I'd like to see more specific use cases tackled before we conclude we need to change the generic |
@hecrj Still intending to submit an RFC, but see this benchmark ( The benchmark measures a nested layout which alternates between Row / Column widgets at each level (until the bottom level which contains a custom fixed size (20px x 20px) rectangle widget) and uses
In a production-ready implementation you'd want to invalidate the cache based on the passed in |
@nicoburns As I said, I just prefer to remove |
Just opened #1735 to remove |
I think it's fair to conclude based on the success of Flutter that multipass layout absolutely isn't necessary, even though it can be convenient at times. I guess I figured it was a pretty small change to support it, so why not. But if you'd rather not then I don't think I have a good argument against that. I do disagree that it's a large effort to do so - I've spent maybe 4 hours on this and it's basically done (the only remaining change would be optimising/caching text layout, which shouldn't be too hard but I'm loathe to work on while the text implementation is otherwise in flux due to https://github.com/iced-rs/iced/tree/advanced-text). It does introduce caching (with an associated memory overhead) which wouldn't be necessary within a single frame for single-pass layout. If you ever wanted to introduce layout that is incremental between frames than that would require all that logic anyway, but I suppose if layout is fast enough then perhaps you don't need that. |
@nicoburns It's not about the amount of work it takes to get there. A new design encourages certain new practices, new APIs need to be maintained, future ideas need to be reconsidered, etc. There is a lot more to consider than the work needed for an implementation. Code is the easy part! |
Objective
Context
#34 (comment)
Changes made
layout
method on theWidget
trait take&mut self
instead of&self
(this enables caching the resultant layout)draw
method on theWidget
trait take&mut self
instead of&self
(this was necessary because the tooltip component callslayout
fromdraw
)measure
method to theWidget
trait which has the semantics "compute the size of this widget without performing a full recursive layout unless necessary". This method has a default implementation with just callslayout
.measure
forrow
,column
, and `container.set_size (&mut self, size: Size)
andinto_children(self) -> Vec<Node>
methods tolayout::Node
This PR (along with associated changes in iced_taffy) improves the performance of an iced_taffy stress test of a 7000+ node nested grid by 20x (from 200ms for layout down to 12ms).
Further work
Further work will be needed to make things really fast for realistic layouts. Including:
measure
methods to other widgets where appropriate (esp. other container widgets if there are any)Feedback wanted
layout
method to take&mut self
acceptable? Converting the widgets to work with this was fairly easy, and it seems like the cleanest solution to enabling caching to me. But caching could also be implemented usingRefCell
without this change.draw
method to take&mut self
acceptable? I didn't look into whytooltip
is callinglayout
indraw
, but it seems to me that it probably shouldn't be? Iftooltip
was modified to not do this then this change could be reverted.