Skip to content

perf(A2UISwiftUI): keyed reconciliation in updateTreeInPlace#52

Merged
BBC6BAE9 merged 3 commits into
BBC6BAE9:mainfrom
fangmobile:swiftui-render
Jun 12, 2026
Merged

perf(A2UISwiftUI): keyed reconciliation in updateTreeInPlace#52
BBC6BAE9 merged 3 commits into
BBC6BAE9:mainfrom
fangmobile:swiftui-render

Conversation

@fangmobile

@fangmobile fangmobile commented May 19, 2026

Copy link
Copy Markdown
Contributor

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.

  • Match children by ComponentNode.id (O(n) HashMap reconcile), with a
    Flutter-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.
  • Guard instance, weight, and accessibility assignments behind
    !=, so a no-op updateComponents produces zero SwiftUI invalidations.
  • Preserve ComponentNode identity (and its uiState) across
    list growth/shrinkage; only nodes whose props actually changed
    notify SwiftUI.
  • Add Equatable conformance to RawComponent, A2UIAccessibility,
    FunctionCall, Dynamic<T>, and refine LiteralDecodable: Equatable
    to unlock the equality checks above. All synthesized; no behavior
    change to encoders/decoders.

Tests: new SurfaceViewModelReconcileTests suite covering no-op
identity 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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread Sources/A2UISwiftUI/SurfaceViewModel.swift Outdated
Comment thread Sources/A2UISwiftUI/SurfaceViewModel.swift Outdated
(cherry picked from commit 72f7310)
@BBC6BAE9 BBC6BAE9 force-pushed the swiftui-render branch 2 times, most recently from 9c50aeb to cdc6b97 Compare June 8, 2026 14:03
@BBC6BAE9 BBC6BAE9 self-requested a review June 8, 2026 14:20
@BBC6BAE9 BBC6BAE9 linked an issue Jun 8, 2026 that may be closed by this pull request
Comment on lines +410 to +412
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 }

@BBC6BAE9 BBC6BAE9 Jun 8, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +432 to +433
old.type == newChild.type {
_ = reconcileNode(existing: old, new: newChild)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@BBC6BAE9

BBC6BAE9 commented Jun 8, 2026

Copy link
Copy Markdown
Owner

@fangmobile Thanks for the solid work here, and for taking the time to put together the reconcile tests — the keyed reconcile + Equatable direction is the right call. 🙏

I've left a couple of inline comments: mainly the redundant != guards in reconcileNode (the Equatable conformance already covers no-op dedupe), plus a minor cleanup.

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>
@fangmobile

Copy link
Copy Markdown
Contributor Author

@fangmobile Thanks for the solid work here, and for taking the time to put together the reconcile tests — the keyed reconcile + Equatable direction is the right call. 🙏

I've left a couple of inline comments: mainly the redundant != guards in reconcileNode (the Equatable conformance already covers no-op dedupe), plus a minor cleanup.

@BBC6BAE9 All make sense. Updated PR.

@BBC6BAE9 BBC6BAE9 merged commit 5dd0cbe into BBC6BAE9:main Jun 12, 2026
@fangmobile fangmobile deleted the swiftui-render branch June 24, 2026 20:13
@fangmobile fangmobile restored the swiftui-render branch June 24, 2026 20:13
@fangmobile fangmobile deleted the swiftui-render branch June 24, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

perf: ComponentNode tree update triggers unnecessary SwiftUI re-renders

2 participants