-
Notifications
You must be signed in to change notification settings - Fork 38
Add repeater widget #49
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
base: master
Are you sure you want to change the base?
Conversation
|
Just tried making this generic over any I can think of two ways to fix this, neither of which I like:
My use-case has data in It would be nice to support unordered collections, but that may be necessarily outside the scope of this PR. |
| /// A separator widget. | ||
| pub struct Separator { | ||
| size: KeyOrValue<f64>, | ||
| width: KeyOrValue<f64>, |
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.
That looks fishy, why add an unused field?
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.
Probably a bad merge. Thanks for pointing this out, I'll clean it up.
| let mut children_changed = false; | ||
|
|
||
| // Start | ||
| let mut stale = HashSet::new(); |
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'd probably have done it that way at first. And maybe what follows is moot because I'm not familiar enough with Rust internals. But assuming C++ knowledge transposes somewhat, I'd say it's unlikely that you will have such a large collection that a linear search into a Vec becomes a bottleneck. And so instead of using a complex HashSet-based add/remove, I'd simply compare 2 sorted vectors, which can be done in linear time. Most likely for moderate-sized collections this will be both simpler and faster.
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.
...and if you can use vectors, then it sounds to me like you can use itertools.merge_join_by() to greatly reduce the work on iterating, and just focus on the handling of added/removed items.
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.
You're likely right that the simplest solution is also the fastest in most cases. It could make sense to branch by collection size here? I don't want this to ever be a bottleneck since update is called a lot, but I also want to avoid unnecessary performance overhead in the average case.
I'd simply compare 2 sorted vectors
The inputs are not sorted, but I do care about their order. The correct ordering of the widgets is determined by the position of each item in the vector from data. Since data represents the most current state, my goal is to get the ordering of my widgets to match that ordering, so in a sense the only valid sorting order is the order that the vector from data is already in. But the order of the vector from old_data is important too, as it should always match the current order of my widgets when the algorithm starts. So in a sense my goal is to find what operations need to be applied to old_data's vector to turn it into data's vector, and then apply those operations to my widget list.
Aside from this pre-existing ordering, I have nothing to sort by. Each data item has an ID that can be fetched using the self.id_getter closure. This ID must be hashable but not necessarily orderable. Maybe it's not too restricting to add Ord as a constraint? But then I still have to allocate memory for two new arrays and eat some CPU and memory cycles to sort them (since I still need the original ordering), and at that point I can't imagine there are performance wins at any collection size since both methods eat memory. The advantage with the current method is that, by creating a HashMap with <ID, target_index>, you gain O(1) lookup of the target index for each item. I need to perform this lookup for the sort at the end. If that lookup becomes O(n), then the algorithm has O(log(n)*n^2) worst-case complexity, whereas it currently has worst-case O(n*log(n)).
As I said above, I think it may still be possible to squeeze out some performance using a somewhat more in-place method, but this is speculative and I do want to profile some before I commit to implementing it.
|
Hey @Secondflight, how do you feel about this PR. Are there any blockers in the main druid crates? (If there are I'll make a note of them in the top comment). |
|
Hi @derekdreery, there's nothing blocking that I know of. I'm struggling to find bandwidth for this but I'm definitely still interested in finishing it up. |
|
@Secondflight cool, all sounds good :) |
This PR adds a
Repeaterwidget. This widget is used to manage the lifecycles of a set of widgets based on a vector in the state tree.This is not quite done but it works, and I would really appreciate feedback.
This widget requires four things to function:
im::Vector<InnerState>, whereInnerStateis the state of each child widgetInnerState. This can just returnInnerStateif it is already hashableInnerStateOuterState, with the assumption that theim::Vector<InnerState>is trivially accessible. The state vector is guaranteed to have a one-to-one mapping to the widget vector. The user must lay out all children, similar to a regular layout function.In return, the repeater ensures that one widget exists per data item in the
im::Vector<InnerState>, and maintains their paint and event order.Some general questions I have:
Does this have to useim::Vector? It certainly made it easier to wrangle all the generics. What might the barrier be to generalizing it to any iterable collection?TODO: