perf(dock): publish the magnification pointer at most once per display frame

This commit is contained in:
Jose Quintero committed 2026-09-28 23:56:17 -06:00
1 parent bc4ceb8668
commit fa3f511ada
1 file changed
+81 -6
+81 -6
View File
@@ -8,6 +8,7 @@
// the magnification factor ever pop.
//
import AppKit
import Combine
import CoreGraphics
import Foundation
@@ -42,7 +43,33 @@ final class DockMagnificationService {
@ObservationIgnored private var rampStart: CFTimeInterval = 0
@ObservationIgnored private var rampTimer: Timer?
private init() {}
/// Newest pointer position that has not been published yet. Mouse
/// events can arrive several times per display frame; publishing each
/// one re-renders the dock for positions that never reach the screen.
@ObservationIgnored private var pendingLocation: CGPoint?
@ObservationIgnored private var lastPublishTime: CFTimeInterval = 0
@ObservationIgnored private var publishTimer: Timer?
@ObservationIgnored private var frameInterval = DockMagnificationService.fastestFrameInterval()
@ObservationIgnored private var screenObserver: NSObjectProtocol?
private init() {
screenObserver = NotificationCenter.default.addObserver(
forName: NSApplication.didChangeScreenParametersNotification,
object: nil,
queue: .main
) { [weak self] _ in
MainActor.assumeIsolated {
self?.frameInterval = Self.fastestFrameInterval()
}
}
}
/// Frame duration of the fastest attached display, so publishing is
/// never slower than any screen the dock can be on.
private static func fastestFrameInterval() -> CFTimeInterval {
let framesPerSecond = NSScreen.screens.map(\.maximumFramesPerSecond).max() ?? 60
return 1.0 / CFTimeInterval(max(framesPerSecond, 60))
}
/// Pointer has entered the dock hit region and we now have a live axis
/// coordinate to track. Called from `.onContinuousHover` with
@@ -51,20 +78,65 @@ final class DockMagnificationService {
// Sub-pixel pointer jitter would publish identical-looking values
// and re-render the dock for nothing. Round-trip suppression keeps
// mouseMoved spam from spiking CPU.
if let current = pointerLocation,
if let current = pendingLocation ?? pointerLocation,
abs(current.x - location.x) < 0.25,
abs(current.y - location.y) < 0.25 {
// Skip publishing, but still nudge the ramp in case strength
// was driving back toward zero.
} else {
pointerLocation = location
if !isTrackingPointer {
isTrackingPointer = true
}
publishCoalesced(location)
}
beginRamp(to: 1)
}
/// Publishes at most one position per display frame. A position that
/// arrives after a full frame of quiet is published immediately; during
/// a burst the newest one is held and published when the frame is up,
/// so the last position of a move is never dropped.
private func publishCoalesced(_ location: CGPoint) {
let sinceLastPublish = CACurrentMediaTime() - lastPublishTime
if publishTimer == nil, sinceLastPublish >= frameInterval {
publish(location)
return
}
pendingLocation = location
guard publishTimer == nil else { return }
let timer = Timer(timeInterval: max(0, frameInterval - sinceLastPublish), repeats: false) { [weak self] _ in
MainActor.assumeIsolated {
self?.publishPending()
}
}
RunLoop.main.add(timer, forMode: .common)
publishTimer = timer
}
private func publishPending() {
publishTimer = nil
guard let pending = pendingLocation else { return }
pendingLocation = nil
// Timers fire a little late. Counting the frame from when it was
// due, not from when the timer ran, keeps a long move publishing
// at the display rate instead of drifting below it.
let now = CACurrentMediaTime()
let due = lastPublishTime + frameInterval
publish(pending, at: now - due < frameInterval ? due : now)
}
private func publish(_ location: CGPoint, at time: CFTimeInterval = CACurrentMediaTime()) {
lastPublishTime = time
pointerLocation = location
if !isTrackingPointer {
isTrackingPointer = true
}
}
private func discardPending() {
publishTimer?.invalidate()
publishTimer = nil
pendingLocation = nil
}
/// Pointer has left the dock hit region. We keep the last known
/// location so the cosine falloff has an anchor to shrink against
/// during the ramp-down; once strength reaches zero the location is
@@ -107,6 +179,9 @@ final class DockMagnificationService {
strength = rampTarget
}
if rampTarget == 0 {
// A held position must not be published after the pointer has
// left, or tracking would restart with nothing driving it.
discardPending()
pointerLocation = nil
if isTrackingPointer {
isTrackingPointer = false