-
-
Notifications
You must be signed in to change notification settings - Fork 64
Fix ForEach Collection change rendered View correctness #245
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
Fix ForEach Collection change rendered View correctness #245
Conversation
|
Edit: Fixed oh, apparently I mixed some of my branches… there are changes that not belong to this pr… I’m going to try and remove them... |
…ntifiable No non-identifiable support in this commit
StressTestExample is broken
…leanup sadly an argument name seems to be required on menuitem forEach initializer, the compiler is apparently unable to infer the right child from the context.
…ntifiable No non-identifiable support in this commit # Conflicts: # Examples/Bundler.toml # Examples/Package.swift # Examples/Sources/ForEachExample/ForEachApp.swift # Sources/SwiftCrossUI/Views/ForEach.swift
StressTestExample is broken
996b537 to
743a488
Compare
stackotter
left a comment
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'll review ForEach once I get the time. For now I've just reviewed all of the other files changed by the PR. I unfortunately ran out of time
Examples/Sources/GreetingGeneratorExample/GreetingGeneratorApp.swift
Outdated
Show resolved
Hide resolved
… pulling packages unsupported on swift 5.10 tooling
# Conflicts: # Sources/DummyBackend/DummyBackend.swift # Sources/SwiftCrossUI/Views/ForEach.swift
stackotter
left a comment
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.
Just requested a few small changes this time. I'll also just have a quick look over all of the changes again because it's been a while since I last looked at this PR (and for this review I just looked at the 'changes since last viewed')
stackotter
left a comment
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've gone through the code again, and I'm happy to merge once remaining comments have been resolved and once I've tested locally.
However, there some are some things that I think can be improved about the algorithm, which of course could be done in this PR, but could also be done by yourself or someone else in a future PR given that this PR does already fix the correctness issues that you set out to fix.
My main issue is that we're removing+adding a lot of nodes, which will impact certain backends (such as GtkBackend) quite heavily. In GtkBackend's case that is slow because it'll remove all signal handlers and re-add them again. We should probably be surgically moving things by index instead (if all backends are able to support that).
If we use moves/swaps instead of removing+adding, we may be able to update the algorithm to first produce a series of swaps, then a series of insertions and removals (strictly for new or removed elements).
But again, I'm happy for that to be left as a future direction in order to get your existing ForEach improvements merged.
Good point, I was not aware of this being that bad on Gtk. I’m going to create an issue for the required update and fix it in a future PR. |
…kLayoutCache, moved inserted inside the for loop
# Conflicts: # Package.resolved # Package.swift
stackotter
left a comment
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.
Looks good now, thanks!
|
Actually, I just realised that we should have the original init with some fallback implementation and a deprecation warning. I'll do that now. |
706957e
into
stackotter:stackotter/add_foreach_deprecated_init
…ess (#245) * Fixed reordering, insertion and removal in ForEach where Element: Identifiable No non-identifiable support in this commit * Made it work for non identifiables StressTestExample is broken * added fallback to old code where no keypath is specified * added node reusing bypass after discovering first duplicate * Fixed ForEach [MenuItem] compatibility, documentation improvement & cleanup sadly an argument name seems to be required on menuitem forEach initializer, the compiler is apparently unable to infer the right child from the context. * added tvOS excluding compiler flag for Slider Component in ForEachExample * Fixed reordering, insertion and removal in ForEach where Element: Identifiable No non-identifiable support in this commit # Conflicts: # Examples/Bundler.toml # Examples/Package.swift # Examples/Sources/ForEachExample/ForEachApp.swift # Sources/SwiftCrossUI/Views/ForEach.swift * Made it work for non identifiables StressTestExample is broken * should fix remaining rebase problems * Changed version of swift-collections to 1.2.1 to match swift 5.10 * Implemented requested changes, potentially additional ForEach initializer pending * fixed my merge messup * replaced upToNextMajor with upToNextMinor because major would lead to pulling packages unsupported on swift 5.10 tooling * requested changes: ignored notes.json, directly passed &children.stackLayoutCache, moved inserted inside the for loop
This pr fixes the issue of only the last items getting removed from the rendered content, even if they werent the ones changing and item insertion/reordering correctness like mentioned in #243
I added apple/swift-collections as a dependency, I’m using OrderedSet in the changes.
If a duplicate identifier is detected every node gets replaced with a new one, diffing is not possible.
For unique identifiers:
If it was included in the previous update, the node gets reused.
It checks if there is an identifier it doesn’t know from the previous update anywhere before it, if there is, ongoing every existing node gets removed from the view graph and reinserted at its new place.
if it wasn’t included, a new node gets created and ongoing every existing node gets readded.
if its the first update every node gets added to the view graph
if there was no duplicate the nodes included in the previous update not included in the current one get removed from the view graph
if there were duplicate(s) every node gets replaced
Also I added some new initializers:
for Identifiable, setting the identifier key path to .id if not specified otherwise
for existing identifier a new one allowing customization of the id KeyPath
Sadly this PR needs to be a breaking change. I was forced to add labels to the elements property on some existing initializers. Otherwise the Swift Compiler wouldn’t select the right initializer (or even select one). disfavouredOverload didn’t help. While updating you can decide between adding the label (if needed) or a keyPath. Choosing the label option uses the same update method as before. With adding a keyPath for the Identifier you choose the new, improved update method. ForEach with Range or [Identifiable] automatically recieve the new method.
I added a new Example App, ForEachExample, showcasing deletion, insertion, appending and reordering.
I tested iOS, macOS, macCatalyst, GtkBackend on Linux and WinUIBackend on Windows successfully.
While it works with non-unique Identifiers I strongly recommend using unique and constant Identifiers. It should be considerably more performant due to it making as few as possible operations on the view graph.
In my tests it still was consistently about 11% faster than the previous implementation.
As soon as the reason for a view update is available ForEach’s update method should be optimized to do only whats necessary. For Example the whole diffing, reordering,… is obsolete when only the size changed. For now at least the correctness got fixed and performance at least slightly improved.