import Foundation #if os(macOS) import CoreVideo // swiftlint:disable attributes // CADisplayLink is deprecated, I've given up on making it conform to Sendable. final class DisplayLink: NSObject, @unchecked Sendable { private static let preferredFramesPerSecond = 0 var isPaused = false { didSet { guard let displayLink, oldValue != isPaused else { return } if isPaused { CVDisplayLinkStop(displayLink) } else { CVDisplayLinkStart(displayLink) } } } var preferredFramesPerSecond = DisplayLink.preferredFramesPerSecond { didSet { guard preferredFramesPerSecond != oldValue else { return } frameInterval = 1.0 / Double(preferredFramesPerSecond) } } private(set) var duration = 0.0 private(set) var timestamp: CFTimeInterval = 0 private(set) var targetTimestamp: CFTimeInterval = 0 private var selector: Selector? private var displayLink: CVDisplayLink? private var frameInterval = 0.0 private weak var delegate: NSObject? deinit { selector = nil } init(target: NSObject, selector sel: Selector) { super.init() CVDisplayLinkCreateWithActiveCGDisplays(&displayLink) guard let displayLink = displayLink else { return } self.delegate = target self.selector = sel CVDisplayLinkSetOutputHandler(displayLink) { [weak self] _, inNow, _, _, _ -> CVReturn in guard let self else { return kCVReturnSuccess } if frameInterval == 0 || frameInterval <= inNow.pointee.timestamp - self.timestamp { self.timestamp = Double(inNow.pointee.timestamp) self.targetTimestamp = self.timestamp + frameInterval _ = self.delegate?.perform(self.selector, with: self) } return kCVReturnSuccess } } func add(to runloop: RunLoop, forMode mode: RunLoop.Mode) { guard let displayLink, !isPaused else { return } CVDisplayLinkStart(displayLink) } func invalidate() { guard let displayLink, isPaused else { return } CVDisplayLinkStop(displayLink) } } extension CVTimeStamp { @inlinable @inline(__always) var timestamp: Double { Double(self.hostTime) / Double(self.videoTimeScale) } } // swiftlint:enable attributes #else import QuartzCore typealias DisplayLink = CADisplayLink #endif struct DisplayLinkTime { let timestamp: TimeInterval let targetTimestamp: TimeInterval } final class DisplayLinkChoreographer: NSObject { private static let preferredFramesPerSecond = 0 var updateFrames: AsyncStream { AsyncStream { continuation in self.continutation = continuation } } var preferredFramesPerSecond = DisplayLinkChoreographer.preferredFramesPerSecond { didSet { guard preferredFramesPerSecond != oldValue else { return } displayLink?.preferredFramesPerSecond = preferredFramesPerSecond } } private(set) var isRunning = false private var displayLink: DisplayLink? { didSet { oldValue?.invalidate() displayLink?.preferredFramesPerSecond = preferredFramesPerSecond displayLink?.isPaused = false displayLink?.add(to: .main, forMode: .common) } } private var continutation: AsyncStream.Continuation? @objc private func update(displayLink: DisplayLink) { continutation?.yield(.init(timestamp: displayLink.timestamp, targetTimestamp: displayLink.targetTimestamp)) } } extension DisplayLinkChoreographer: Runner { func startRunning() { guard !isRunning else { return } displayLink = DisplayLink(target: self, selector: #selector(self.update(displayLink:))) isRunning = true } func stopRunning() { guard isRunning else { return } isRunning = false displayLink = nil continutation?.finish() } }