perf(A2UISwiftUI): keyed reconciliation in updateTreeInPlace#52
Conversation
(cherry picked from commit 3b747b1)
There was a problem hiding this comment.
Code Review
This pull request introduces a more efficient tree reconciliation algorithm in SurfaceViewModel to avoid unnecessary SwiftUI re-renders by performing in-place updates on @Observable nodes. It also updates AGENTS.md to reflect the v0.9 architecture and adds Equatable conformance to several core schema types to support guarded assignments. Feedback was provided regarding a bug in the child reconciliation logic where checking only IDs fails to detect type changes, along with a performance improvement to avoid array allocations during that check. A minor documentation fix was also suggested.
(cherry picked from commit 72f7310)
9c50aeb to
cdc6b97
Compare
| if existing.instance != new.instance { existing.instance = new.instance } | ||
| if existing.weight != new.weight { existing.weight = new.weight } | ||
| if existing.accessibility != new.accessibility { existing.accessibility = new.accessibility } |
There was a problem hiding this comment.
Redundant. @Observable already dedupes equal writes for Equatable types, and this PR makes RawComponent/A2UIAccessibility Equatable — so a no-op produces 0 invalidations even without these guards (verified: removing them still yields 0). They just add a second comparison. Drop them and assign directly:
existing.instance = new.instance
existing.weight = new.weight
existing.accessibility = new.accessibility(Keep the children === guard below — that's an identity check the framework won't do.)
| old.type == newChild.type { | ||
| _ = reconcileNode(existing: old, new: newChild) |
There was a problem hiding this comment.
Minor: type is already checked here, then re-checked in reconcileNode's entry guard, and the returned Bool is discarded — the guard is always true on this path. Worth simplifying.
|
@fangmobile Thanks for the solid work here, and for taking the time to put together the reconcile tests — the keyed reconcile + I've left a couple of inline comments: mainly the redundant |
Drop the explicit equality guards on instance/weight/accessibility — the @observable macro already skips notification for equal writes to Equatable properties (shouldNotifyObservers, Swift 6.2+/Xcode 26 toolchain). Consume reconcileNode's result in reconcileChildren instead of duplicating the type check, and add a regression test that fails if the package is built with a toolchain lacking the macro dedup. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BBC6BAE9 All make sense. Updated PR. |
Based on detailed info in #36
Reconcile the component tree by id with equality-guarded writes so
@observable notifications only fire when something actually changed.
ComponentNode.id(O(n) HashMap reconcile), with aFlutter-style type check before reusing a node — replaces the
position+count guard that triggered a full root replacement whenever
a list template grew or shrank by one item.
instance,weight, andaccessibilityassignments behind!=, so a no-opupdateComponentsproduces zero SwiftUI invalidations.ComponentNodeidentity (and itsuiState) acrosslist growth/shrinkage; only nodes whose props actually changed
notify SwiftUI.
Equatableconformance toRawComponent,A2UIAccessibility,FunctionCall,Dynamic<T>, and refineLiteralDecodable: Equatableto unlock the equality checks above. All synthesized; no behavior
change to encoders/decoders.
Tests: new
SurfaceViewModelReconcileTestssuite covering no-opidentity preservation, list grow/shrink identity, uiState survival on
a Tabs child across list growth, single-node change isolation, and
root replacement on type change.
Agent.md: updated