2023-11-09 22:28:02 +04:00

103 lines
3.2 KiB
Swift

import Foundation
import UIKit
public final class SharedDisplayLink {
private final class DisplayLinkTarget: NSObject {
let f: () -> Void
init(_ f: @escaping () -> Void) {
self.f = f
}
@objc func event() {
self.f()
}
}
public enum FramesPerSecond {
case fps(Double)
case max
}
public final class Subscription {
fileprivate final class Target {
let event: () -> Void
let framesPerSecond: FramesPerSecond
var lastDuration: Double = 0.0
var totalTicks: Int = 0
var acceptedTicks: Int = 0
init(event: @escaping () -> Void, framesPerSecond: FramesPerSecond) {
self.event = event
self.framesPerSecond = framesPerSecond
}
}
fileprivate let target: Target
fileprivate init(event: @escaping () -> Void, framesPerSecond: FramesPerSecond) {
self.target = Target(event: event, framesPerSecond: framesPerSecond)
}
deinit {
SharedDisplayLink.shared.remove(target: self.target)
}
}
public static let shared: SharedDisplayLink = {
return SharedDisplayLink()
}()
private var displayLink: CADisplayLink?
private var subscriptions: [Subscription.Target] = []
private init() {
self.displayLink = CADisplayLink(target: DisplayLinkTarget { [weak self] in
guard let self, let displayLink = self.displayLink else {
return
}
self.displayLinkEvent(timestamp: displayLink.timestamp, duration: displayLink.duration)
}, selector: #selector(DisplayLinkTarget.event))
if #available(iOS 15.0, *) {
self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0)
}
self.displayLink?.add(to: .main, forMode: .common)
}
private func displayLinkEvent(timestamp: Double, duration: Double) {
loop: for subscription in self.subscriptions {
subscription.totalTicks += 1
switch subscription.framesPerSecond {
case let .fps(value):
let secondsPerFrame = 1.0 / value
subscription.lastDuration += duration
if subscription.lastDuration >= secondsPerFrame * 0.99 {
} else {
continue loop
}
case .max:
break
}
subscription.lastDuration = 0.0
subscription.acceptedTicks += 1
subscription.event()
}
}
public func add(framesPerSecond: FramesPerSecond = .max, _ event: @escaping () -> Void) -> Subscription {
let subscription = Subscription(event: event, framesPerSecond: framesPerSecond)
self.subscriptions.append(subscription.target)
return subscription
}
private func remove(target: Subscription.Target) {
if let index = self.subscriptions.firstIndex(where: { $0 === target }) {
self.subscriptions.remove(at: index)
}
}
}