From ae9175107d15052d98d7246ba91dd2b0902af21e Mon Sep 17 00:00:00 2001 From: millia ampora <198710911+dappermint@users.noreply.github.com> Date: Fri, 30 Oct 2026 14:23:17 +0400 Subject: [PATCH] perf(swipe): warm workspace swipe previews from settled frames warmPreviews runs after every completed layout effect plan so a swipe finds its captures ready. it built the source workspace the same way the gesture does: inside withEngineMutationScope, which commits a world event on exit, and with controller.liveFrame(for:) per window, which is a SkyLight bounds query each. a sample of 20 focus plus workspace switch round trips on Dev put 30 main thread samples under makePreparation from the warm path, 13 of them in the live frame reads, with no swipe anywhere in sight. the warm only needs frames to decide which windows intersect the working frame and what size to request; the capture cache is keyed by window handle and token, and the gesture rebuilds the preparation with live frames when it actually starts. let the warm path take the projected frames straight from the layout handlers and skip the live reads. the projection reads need no sanction, so drop the mutation scope from makeWorkspace entirely. --- .../WorkspaceSwipePresentation+Geometry.swift | 31 +++++++++++-------- .../WorkspaceSwipePresentationTests.swift | 21 +++++++++++++ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift b/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift index b437a6b85..8254cb5d6 100644 --- a/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift +++ b/Sources/OmniWM/Core/Controller/WorkspaceSwipePresentation+Geometry.swift @@ -10,7 +10,7 @@ extension WorkspaceSwipePresentation { controller.motionPolicy.animationsEnabled, controller.settings.gestures.workspaceSwipeEnabled, !controller.isOverviewOpen(), let monitor = controller.monitorForInteraction(), - let preparation = makePreparation(monitorId: monitor.id) + let preparation = makePreparation(monitorId: monitor.id, liveFrames: false) else { return } previewSurface(controller).warm( source: preparation.source.items, @@ -32,10 +32,10 @@ extension WorkspaceSwipePresentation { } } - func makePreparation(monitorId: Monitor.ID) -> Preparation? { + func makePreparation(monitorId: Monitor.ID, liveFrames: Bool = true) -> Preparation? { guard let controller, let monitor = controller.workspaceManager.monitor(byId: monitorId), let current = controller.workspaceManager.activeWorkspaceOrFirst(on: monitorId), - let source = makeWorkspace(current.id, monitor: monitor, active: true) + let source = makeWorkspace(current.id, monitor: monitor, active: true, liveFrames: liveFrames) else { return nil } let navigation = controller.workspaceNavigationHandler let previous = navigation.swipeTargetWorkspace( @@ -53,35 +53,40 @@ extension WorkspaceSwipePresentation { ) } - func makeWorkspace(_ id: WorkspaceDescriptor.ID, monitor: Monitor, active: Bool) -> Workspace? { + func makeWorkspace( + _ id: WorkspaceDescriptor.ID, monitor: Monitor, active: Bool, liveFrames: Bool = true + ) -> Workspace? { guard let controller, let refreshController else { return nil } let entries = controller.workspaceManager.entries(in: id).filter { !controller.workspaceManager.isWindowSuppressedByMacOS($0.token) } guard entries.allSatisfy({ $0.layoutReason == .standard }) else { return nil } - let frames: [WindowToken: CGRect] = controller.workspaceManager.withEngineMutationScope { - if controller.workspaceManager.activeLayoutKind(for: id) == .niri { - return refreshController.niriHandler.settledFrames(in: id, visibleOnly: true) ?? [:] - } - guard let engine = controller.dwindleEngine, + let frames: [WindowToken: CGRect] + if controller.workspaceManager.activeLayoutKind(for: id) == .niri { + frames = refreshController.niriHandler.settledFrames(in: id, visibleOnly: true) ?? [:] + } else if let engine = controller.dwindleEngine, let snapshot = refreshController.dwindleHandler.makeWorkspaceSnapshot( workspaceId: id, monitor: monitor, resolveConstraints: false, isActiveWorkspace: active - ) else { return [:] } + ) + { let plan = refreshController.dwindleHandler.buildOnDemandLayoutPlan(snapshot: snapshot, engine: engine) - return Dictionary( + frames = Dictionary( plan.diff.frameChanges.map { ($0.token, $0.frame) }, uniquingKeysWith: { _, last in last } ) + } else { + frames = [:] } + let live = active && liveFrames var items: [WorkspaceSwipePreview.Item] = [] for entry in entries { guard let handle = controller.workspaceManager.handle(for: entry.token) else { return nil } let frame: CGRect? if entry.mode == .floating { - frame = active ? controller.liveFrame(for: entry) + frame = live ? controller.liveFrame(for: entry) : controller.workspaceManager.resolvedFloatingFrame(for: entry.token, preferredMonitor: monitor) } else if let projected = frames[entry.token] { - frame = active ? (controller.liveFrame(for: entry) ?? projected) : projected + frame = live ? (controller.liveFrame(for: entry) ?? projected) : projected } else { continue } diff --git a/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift b/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift index 5ad13ee02..7b5d25b37 100644 --- a/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift +++ b/Tests/OmniWMTests/WorkspaceSwipePresentationTests.swift @@ -484,6 +484,27 @@ final class WorkspaceSwipePresentationTests: XCTestCase { XCTAssertFalse(fixture.swipe.hasPresentation) } + func testWarmingPreviewsUsesSettledFramesWithoutCommittingWorldEvents() async throws { + let fixture = try previewFixture() + fixture.controller.hasStartedServices = true + fixture.controller.settings.gestures.workspaceSwipeEnabled = true + let liveWidth = try XCTUnwrap( + fixture.swipe.makePreparation(monitorId: fixture.monitor.id)?.source.items.first?.frame.width + ) + let settledWidth = try XCTUnwrap( + fixture.swipe.makePreparation(monitorId: fixture.monitor.id, liveFrames: false)?.source.items.first? + .frame.width + ) + XCTAssertEqual(liveWidth, Self.sourceFrame.width) + XCTAssertNotEqual(settledWidth, liveWidth) + + let seq = fixture.controller.workspaceManager.worldSeq + fixture.swipe.warmPreviews() + XCTAssertEqual(fixture.controller.workspaceManager.worldSeq, seq) + try await fixture.driver.waitForStarts(1) + XCTAssertEqual(fixture.driver.streams[0].request.pixelWidth, Int(ceil(settledWidth))) + } + func testTurningSwipeOrAnimationsOffReleasesThePreviewAndItsCache() async throws { let disablers: [@MainActor (WMController) -> Void] = [ { $0.settings.gestures.workspaceSwipeEnabled = false },