Autoscroll ticks on the display's own clock — the 16ms sleeping loop retires

DragAutoScroller.run() consumes an AsyncStream of CADisplayLink
targetTimestamps from NSView.displayLink(target:selector:) on the
lane's existing anchor view (which was already the right NSView — no
new plumbing). The stream is the lifetime: cancellation ends the
iteration, termination invalidates the link, the link releases its
target. DragAutoScrollClock keeps the retired loop's two non-trivial
rules — first tick scrolls nothing, no tick integrates past 50ms (a
paused link resumes with the whole gap as its delta) — and the math is
frame-rate independent by test: one second at the edge moves 800.0
points at 120Hz and at 60Hz alike. Run-loop mode .common is
load-bearing: a drag runs AppKit's event-tracking mode.

Drag-perf card 2e08fd31.

Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
2026-08-01 20:22:42 -04:00
parent 0218ae4c21
commit 1fc8aaa249
4 changed files with 409 additions and 21 deletions
+3 -2
View File
@@ -4,8 +4,9 @@ import Testing
/// `DragAutoScrollMath` given a viewport and a pointer inside (or just outside) it, how fast, and
/// which way, should the scroll view move? Ported from the pathfinder's suite, whose numbers are
/// what was proven. The live driver is the drag session's; this is the decision it makes 60 times a
/// second (DRAG-REORDER.md § Edge autoscroll).
/// what was proven. The live driver is the drag session's; this is the decision it makes once per
/// frame of the display the lane is on (`DragAutoScrollDriverTests`, DRAG-REORDER.md § Edge
/// autoscroll).
private let length: CGFloat = 400
private let band = DragAutoScrollMath.band