Skip to content

Commit 5167247

Browse files
rickyznikitabobko
authored andcommitted
Coalesce idempotent refreshSession calls triggered by event handlers
On operations like workspace changes, AeroSpace receives a stampede of accessibility events, each of which triggers a serial call to `refreshSession()` on the main thread. Since these `refreshSession()` calls are idempotent, we can coalesce these calls so that multiple accessibility events can be handled by a single `refreshSession()`. With this change, operations like workspace switch go from O(num_source_windows + num_dest_windows) to O(1) `refreshSession()` calls (with similar improvements for operations like closing windows or resizing windows). closes nikitabobko/AeroSpace#1249 related: nikitabobko/AeroSpace#131
1 parent 5359770 commit 5167247

File tree

5 files changed

+17
-9
lines changed

5 files changed

+17
-9
lines changed

Sources/AppBundle/GlobalObserver.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class GlobalObserver {
1111
}
1212
let notifName = notification.name.rawValue
1313
MainActor.assumeIsolated {
14-
refreshAndLayout(.globalObserver(notifName), screenIsDefinitelyUnlocked: false)
14+
scheduleRefreshAndLayout(.globalObserver(notifName), screenIsDefinitelyUnlocked: false)
1515
}
1616
}
1717

@@ -67,7 +67,7 @@ class GlobalObserver {
6767
// Detect close button clicks for unfocused windows. Yes, kAXUIElementDestroyedNotification is that unreliable
6868
// And trigger new window detection that could be delayed due to mouseDown event
6969
default:
70-
refreshAndLayout(.globalObserverLeftMouseUp, screenIsDefinitelyUnlocked: true)
70+
scheduleRefreshAndLayout(.globalObserverLeftMouseUp, screenIsDefinitelyUnlocked: true)
7171
}
7272
}
7373
}

Sources/AppBundle/initAppBundle.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import Foundation
2323
AXUIElementSetMessagingTimeout(AXUIElementCreateSystemWide(), 1.0)
2424
startUnixSocketServer()
2525
GlobalObserver.initObserver()
26-
refreshAndLayout(.startup1, screenIsDefinitelyUnlocked: false, startup: true)
26+
refreshSession(.startup1, screenIsDefinitelyUnlocked: false, startup: true, body: {})
2727
refreshSession(.startup2, screenIsDefinitelyUnlocked: false) {
2828
if serverArgs.startedAtLogin {
2929
_ = config.afterLoginCommand.runCmdSeq(.defaultEnv, .emptyStdin)

Sources/AppBundle/layout/refresh.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,17 @@ func refreshSession<T>(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked:
4242
return result
4343
}
4444

45+
@MainActor private var havePendingRefresh = false
46+
4547
@MainActor
46-
func refreshAndLayout(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked: Bool, startup: Bool = false) {
47-
refreshSession(event, screenIsDefinitelyUnlocked: screenIsDefinitelyUnlocked, startup: startup, body: {})
48+
func scheduleRefreshAndLayout(_ event: RefreshSessionEvent, screenIsDefinitelyUnlocked: Bool = false) {
49+
if screenIsDefinitelyUnlocked { resetClosedWindowsCache() }
50+
if havePendingRefresh { return }
51+
havePendingRefresh = true
52+
DispatchQueue.main.async { @MainActor in
53+
havePendingRefresh = false
54+
refreshSession(event, screenIsDefinitelyUnlocked: false, startup: false, body: {})
55+
}
4856
}
4957

5058
@MainActor
@@ -81,7 +89,7 @@ func refreshObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: Unsaf
8189
check(Thread.isMainThread)
8290
let notif = notif as String
8391
MainActor.assumeIsolated {
84-
refreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
92+
scheduleRefreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
8593
}
8694
}
8795

Sources/AppBundle/mouse/moveWithMouse.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ func movedObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: UnsafeM
99
if let windowId, let window = Window.get(byId: windowId), TrayMenuModel.shared.isEnabled {
1010
moveWithMouseIfTheCase(window)
1111
}
12-
refreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
12+
scheduleRefreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
1313
}
1414
}
1515

Sources/AppBundle/mouse/resizeWithMouse.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ func resizedObs(_ obs: AXObserver, ax: AXUIElement, notif: CFString, data: Unsaf
99
if let windowId, let window = Window.get(byId: windowId), TrayMenuModel.shared.isEnabled {
1010
resizeWithMouseIfTheCase(window)
1111
}
12-
refreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
12+
scheduleRefreshAndLayout(.ax(notif), screenIsDefinitelyUnlocked: false)
1313
}
1414
}
1515

@@ -21,7 +21,7 @@ func resetManipulatedWithMouseIfPossible() {
2121
for workspace in Workspace.all {
2222
workspace.resetResizeWeightBeforeResizeRecursive()
2323
}
24-
refreshAndLayout(.resetManipulatedWithMouse, screenIsDefinitelyUnlocked: true)
24+
scheduleRefreshAndLayout(.resetManipulatedWithMouse, screenIsDefinitelyUnlocked: true)
2525
}
2626
}
2727

0 commit comments

Comments
 (0)