import UIKit // ๐Ÿค– TEMPORARY diagnostic (DEBUG only): a display link that records every // dropped frame with what published/ran just before it, appended to // Documents/hitches.log โ€” pulled off the device with `devicectl device copy // from` and read on the Mac. Delete once the scroll hitch is nailed. #if DEBUG final class HitchLogger { static let shared = HitchLogger() private var link: CADisplayLink? private var lastTimestamp: CFTimeInterval = 0 private var recentNotes: [String] = [] private var pending: [String] = [] private lazy var file: URL = FileManager.default .urls(for: .documentDirectory, in: .userDomainMask)[0] .appendingPathComponent("hitches.log") func start() { guard link == nil else { return } try? FileManager.default.removeItem(at: file) let l = CADisplayLink(target: self, selector: #selector(tick)) l.add(to: .main, forMode: .common) link = l note("logger started") } /// Breadcrumb: publishes, pins, gestures โ€” whatever ran near a hitch. func note(_ what: String) { let stamp = String(format: "%.3f", CACurrentMediaTime()) recentNotes.append("\(stamp) \(what)") if recentNotes.count > 6 { recentNotes.removeFirst() } } @objc private func tick(_ l: CADisplayLink) { defer { lastTimestamp = l.timestamp } guard lastTimestamp > 0 else { return } let dt = l.timestamp - lastTimestamp let expected = l.duration > 0 ? l.duration : 1.0 / 60.0 guard dt > expected * 1.6 else { return } // a properly dropped frame let ms = Int(dt * 1000) pending.append("HITCH \(ms)ms at \(String(format: "%.3f", l.timestamp)) | recent: " + recentNotes.joined(separator: " ยท ")) flush() } private func flush() { guard !pending.isEmpty else { return } let text = pending.joined(separator: "\n") + "\n" pending.removeAll() let file = self.file // Off main โ€” the logger's own synchronous writes were adding stalls // to the very storms it measures. writeQueue.async { if let handle = try? FileHandle(forWritingTo: file) { handle.seekToEndOfFile() handle.write(Data(text.utf8)) try? handle.close() } else { try? Data(text.utf8).write(to: file) } } } private let writeQueue = DispatchQueue(label: "hitch-log", qos: .utility) } #endif