Skip to content

perf(window): remove forced layoutSubtreeIfNeeded + display in viewWillAppear#1030

Closed
datlechin wants to merge 1 commit intomainfrom
perf/remove-forced-sidebar-layout
Closed

perf(window): remove forced layoutSubtreeIfNeeded + display in viewWillAppear#1030
datlechin wants to merge 1 commit intomainfrom
perf/remove-forced-sidebar-layout

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

Time Profiler captured during connect-from-dock-menu (cold launch) showed a 1.71s spike with 1.36s on the main thread, where 388ms (22.7%) lived inside NSDisplayCycleObserverInvoke driving a recursive _layoutSubtreeWithOldSize cascade through SwiftUI's SizeFittingLayoutComputer and NSAttributedString.MetricsCache.metrics.

The cascade is amplified by two synchronous calls in MainSplitViewController.viewWillAppear:

sidebarContainer.view.layoutSubtreeIfNeeded()
sidebarContainer.view.display()

These were added in PR #1000 (sidebar refactor), likely to prevent a brief unrendered-sidebar flash. But they:

  1. Force a synchronous layout pass on the main thread before the window is on-screen — work that AppKit's display cycle would have done asynchronously when the window orders front
  2. Force a full redraw on top of the layout pass, blocking the run loop until completion
  3. Trigger _layoutSubtreeWithOldSize recursion through every NSHostingController in the sidebar's SwiftUI tree, each evaluating body and negotiating intrinsic sizes via SwiftUI's LayoutEngineBox.sizeThatFits (which itself measures attributed strings via TKerningEngine)

Recursive cascade visible in the trace:

_layoutSubtreeWithOldSize  221 ms
  → _layoutSubtreeWithOldSize  143 ms
    → _layoutSubtreeWithOldSize   95 ms
      → _layoutSubtreeWithOldSize   75 ms
        → _layoutSubtreeWithOldSize   44 ms
          → _layoutSubtreeWithOldSize   44 ms
              → SizeFittingLayoutComputer.Engine.sizeThatFits
                  → NSAttributedString.MetricsCache.metrics
                      → TKerningEngine::PositionGlyphs

Fix

Remove the two forced calls. AppKit handles layout and display naturally as part of the window's display cycle — no manual flush needed, and the natural cycle batches related layout passes instead of forcing one-at-a-time.

  if let currentSession, let coordinator = sessionState?.coordinator {
      sidebarContainer.updateSidebarState(...)
  }

- sidebarContainer.view.layoutSubtreeIfNeeded()
- sidebarContainer.view.display()
-
  installObservers()

Test plan

  • Cold launch + open connection from dock menu: window appears, sidebar populates with table list once data arrives
  • Welcome → click connection → main window opens, sidebar shows tables (no longer-than-before flash)
  • Switch between native window tabs: sidebar stays consistent
  • swiftlint --strict on changed file: 0 violations
  • xcodebuild Debug arm64: BUILD SUCCEEDED

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin closed this May 6, 2026
@datlechin datlechin deleted the perf/remove-forced-sidebar-layout branch May 6, 2026 16:33
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.

1 participant