diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 237cc80892..698abec787 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -34,55 +34,6 @@ private let completionKey = "CAAnimationUtils_completion" public let kCAMediaTimingFunctionSpring = "CAAnimationUtilsSpringCurve" public let kCAMediaTimingFunctionCustomSpringPrefix = "CAAnimationUtilsSpringCustomCurve" -private final class FrameRangeContext { - private var animationCount: Int = 0 - private var displayLink: CADisplayLink? - - init() { - } - - func add() { - self.animationCount += 1 - self.update() - } - - func remove() { - self.animationCount -= 1 - if self.animationCount < 0 { - self.animationCount = 0 - assertionFailure() - } - self.update() - } - - @objc func displayEvent() { - } - - private func update() { - if self.animationCount != 0 { - if self.displayLink == nil { - let displayLink = CADisplayLink(target: self, selector: #selector(self.displayEvent)) - - if #available(iOS 15.0, *) { - let maxFps = Float(UIScreen.main.maximumFramesPerSecond) - if maxFps > 61.0 { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) - } - } - - self.displayLink = displayLink - displayLink.add(to: .main, forMode: .common) - displayLink.isPaused = false - } - } else if let displayLink = self.displayLink { - self.displayLink = nil - displayLink.invalidate() - } - } -} - -private let frameRangeContext = FrameRangeContext() - public extension CAAnimation { var completion: ((Bool) -> Void)? { get { @@ -103,18 +54,16 @@ public extension CAAnimation { private func adjustFrameRate(animation: CAAnimation) { if #available(iOS 15.0, *) { - if let animation = animation as? CABasicAnimation { - if animation.keyPath == "opacity" { - return - } - } let maxFps = Float(UIScreen.main.maximumFramesPerSecond) if maxFps > 61.0 { - #if DEBUG - //let _ = frameRangeContext.add() - #endif - - animation.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: maxFps, preferred: maxFps) + var preferredFps: Float = maxFps + if let animation = animation as? CABasicAnimation { + if animation.keyPath == "opacity" { + preferredFps = 60.0 + return + } + } + animation.preferredFrameRateRange = CAFrameRateRange(minimum: 30.0, maximum: preferredFps, preferred: maxFps) } } } diff --git a/submodules/Display/Source/DisplayLinkAnimator.swift b/submodules/Display/Source/DisplayLinkAnimator.swift index 4eb0d836bf..2089800711 100644 --- a/submodules/Display/Source/DisplayLinkAnimator.swift +++ b/submodules/Display/Source/DisplayLinkAnimator.swift @@ -1,6 +1,153 @@ import Foundation import UIKit +public final class SharedDisplayLinkDriver { + public static let shared = SharedDisplayLinkDriver() + + public final class Link { + private let driver: SharedDisplayLinkDriver + public let needsHighestFramerate: Bool + let update: () -> Void + var isValid: Bool = true + public var isPaused: Bool = false { + didSet { + if self.isPaused != oldValue { + driver.requestUpdate() + } + } + } + + init(driver: SharedDisplayLinkDriver, needsHighestFramerate: Bool, update: @escaping () -> Void) { + self.driver = driver + self.needsHighestFramerate = needsHighestFramerate + self.update = update + } + + public func invalidate() { + self.isValid = false + } + } + + private final class RequestContext { + weak var link: Link? + + init(link: Link) { + self.link = link + } + } + + private var displayLink: CADisplayLink? + private var hasRequestedHighestFramerate: Bool = false + private var requests: [RequestContext] = [] + + private var isInForeground: Bool = false + + private init() { + let _ = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let self else { + return + } + self.isInForeground = true + self.update() + }) + let _ = NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil, using: { [weak self] _ in + guard let self else { + return + } + self.isInForeground = false + self.update() + }) + + switch UIApplication.shared.applicationState { + case .active: + self.isInForeground = true + default: + self.isInForeground = false + } + + self.update() + } + + private func requestUpdate() { + self.update() + } + + private func update() { + var hasActiveItems = false + var needHighestFramerate = false + for request in self.requests { + if let link = request.link { + needHighestFramerate = link.needsHighestFramerate + if link.isValid && !link.isPaused { + hasActiveItems = true + break + } + } + } + + if self.isInForeground && hasActiveItems { + let displayLink: CADisplayLink + if let current = self.displayLink { + displayLink = current + } else { + displayLink = CADisplayLink(target: self, selector: #selector(self.displayLinkEvent)) + self.displayLink = displayLink + displayLink.add(to: .main, forMode: .common) + } + if #available(iOS 15.0, *) { + let frameRateRange: CAFrameRateRange + if needHighestFramerate { + frameRateRange = CAFrameRateRange(minimum: 30.0, maximum: 120.0, preferred: 120.0) + } else { + frameRateRange = .default + } + if displayLink.preferredFrameRateRange != frameRateRange { + displayLink.preferredFrameRateRange = frameRateRange + } + } + displayLink.isPaused = false + } else { + if let displayLink = self.displayLink { + self.displayLink = nil + displayLink.invalidate() + } + } + } + + @objc private func displayLinkEvent() { + var removeIndices: [Int]? + for i in 0 ..< self.requests.count { + if let link = self.requests[i].link, link.isValid { + link.update() + } else { + if removeIndices == nil { + removeIndices = [i] + } else { + removeIndices?.append(i) + } + } + } + if let removeIndices = removeIndices { + for index in removeIndices.reversed() { + self.requests.remove(at: index) + } + + if self.requests.isEmpty { + self.update() + } + } + } + + public func add(needsHighestFramerate: Bool = true, _ update: @escaping () -> Void) -> Link { + let link = Link(driver: self, needsHighestFramerate: needsHighestFramerate, update: update) + self.requests.append(RequestContext(link: link)) + + self.update() + + return link + } +} + public final class DisplayLinkTarget: NSObject { private let f: () -> Void @@ -14,7 +161,7 @@ public final class DisplayLinkTarget: NSObject { } public final class DisplayLinkAnimator { - private var displayLink: CADisplayLink! + private var displayLink: SharedDisplayLinkDriver.Link? private let duration: Double private let fromValue: CGFloat private let toValue: CGFloat @@ -32,21 +179,20 @@ public final class DisplayLinkAnimator { self.startTime = CACurrentMediaTime() - self.displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in + self.displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in self?.tick() - }), selector: #selector(DisplayLinkTarget.event)) - self.displayLink.isPaused = false - self.displayLink.add(to: RunLoop.main, forMode: .common) + } + self.displayLink?.isPaused = false } deinit { - self.displayLink.isPaused = true - self.displayLink.invalidate() + self.displayLink?.isPaused = true + self.displayLink?.invalidate() } public func invalidate() { - self.displayLink.isPaused = true - self.displayLink.invalidate() + self.displayLink?.isPaused = true + self.displayLink?.invalidate() } @objc private func tick() { @@ -60,14 +206,14 @@ public final class DisplayLinkAnimator { self.update(self.fromValue * CGFloat(1 - t) + self.toValue * CGFloat(t)) if abs(t - 1.0) < Double.ulpOfOne { self.completed = true - self.displayLink.isPaused = true + self.displayLink?.isPaused = true self.completion() } } } public final class ConstantDisplayLinkAnimator { - private var displayLink: CADisplayLink? + private var displayLink: SharedDisplayLinkDriver.Link? private let update: () -> Void private var completed = false @@ -81,26 +227,16 @@ public final class ConstantDisplayLinkAnimator { guard let displayLink = self.displayLink else { return } - if self.frameInterval == 1 { - if #available(iOS 15.0, *) { - self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0) - } - } else { - displayLink.preferredFramesPerSecond = 30 - } + let _ = displayLink } public var isPaused: Bool = true { didSet { if self.isPaused != oldValue { if !self.isPaused && self.displayLink == nil { - let displayLink = CADisplayLink(target: DisplayLinkTarget({ [weak self] in + let displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in self?.tick() - }), selector: #selector(DisplayLinkTarget.event)) - /*if #available(iOS 15.0, *) { - self.displayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: 120.0, preferred: 120.0) - }*/ - displayLink.add(to: RunLoop.main, forMode: .common) + } self.displayLink = displayLink self.updateDisplayLink() } diff --git a/submodules/Display/Source/DisplayLinkDispatcher.swift b/submodules/Display/Source/DisplayLinkDispatcher.swift index a86bc9762b..27cee48bc4 100644 --- a/submodules/Display/Source/DisplayLinkDispatcher.swift +++ b/submodules/Display/Source/DisplayLinkDispatcher.swift @@ -2,7 +2,6 @@ import Foundation import UIKit public class DisplayLinkDispatcher: NSObject { - private var displayLink: CADisplayLink! private var blocksToDispatch: [() -> Void] = [] private let limit: Int @@ -10,38 +9,13 @@ public class DisplayLinkDispatcher: NSObject { self.limit = limit super.init() - - if #available(iOS 10.0, *) { - //self.displayLink.preferredFramesPerSecond = 60 - } else { - self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) - self.displayLink.isPaused = true - self.displayLink.add(to: RunLoop.main, forMode: .common) - } } public func dispatch(f: @escaping () -> Void) { - if self.displayLink == nil { - if Thread.isMainThread { - f() - } else { - DispatchQueue.main.async(execute: f) - } + if Thread.isMainThread { + f() } else { - self.blocksToDispatch.append(f) - self.displayLink.isPaused = false - } - } - - @objc func run() { - for _ in 0 ..< (self.limit == 0 ? 1000 : self.limit) { - if self.blocksToDispatch.count == 0 { - self.displayLink.isPaused = true - break - } else { - let f = self.blocksToDispatch.removeFirst() - f() - } + DispatchQueue.main.async(execute: f) } } } diff --git a/submodules/Display/Source/ListView.swift b/submodules/Display/Source/ListView.swift index db7c427d8d..c252869ecb 100644 --- a/submodules/Display/Source/ListView.swift +++ b/submodules/Display/Source/ListView.swift @@ -814,12 +814,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - if #available(iOS 15.0, *) { - if let scrollDisplayLink = self.scroller.value(forKey: "_scrollHeartbeat") as? CADisplayLink { - let _ = scrollDisplayLink - } - } - self.isDragging = false if decelerate { self.lastContentOffsetTimestamp = CACurrentMediaTime() diff --git a/submodules/GraphCore/Sources/Helpers/DisplayLinkService.swift b/submodules/GraphCore/Sources/Helpers/DisplayLinkService.swift index e040b4b6e6..3d64ac8bfe 100644 --- a/submodules/GraphCore/Sources/Helpers/DisplayLinkService.swift +++ b/submodules/GraphCore/Sources/Helpers/DisplayLinkService.swift @@ -36,44 +36,6 @@ class DisplayLinkService { } } -// private init() { -// displayLink.add(to: .main, forMode: .common) -// displayLink.preferredFramesPerSecond = 60 -// displayLink.isPaused = true -// } -// -// // MARK: - Display Link -// private lazy var displayLink: CADisplayLink! = { CADisplayLink(target: self, selector: #selector(displayLinkDidFire)) } () -// private var previousTickTime = 0.0 -// -// private func startDisplayLink() { -// guard displayLink.isPaused else { -// return -// } -// previousTickTime = CACurrentMediaTime() -// displayLink.isPaused = false -// } -// -// @objc private func displayLinkDidFire(_ displayLink: CADisplayLink) { -// let currentTime = CACurrentMediaTime() -// let delta = currentTime - previousTickTime -// previousTickTime = currentTime -// let allListners = listners.allObjects -// var hasListners = false -// for listner in allListners { -// (listner as! DisplayLinkListner).update(delta: delta) -// hasListners = true -// } -// -// if !hasListners { -// stopDisplayLink() -// } -// } -// -// private func stopDisplayLink() { -// displayLink.isPaused = true -// } - private init() { dispatchSourceTimer.schedule(deadline: .now() + 1.0 / 60, repeating: 1.0 / 60) dispatchSourceTimer.setEventHandler { diff --git a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift index 82517c8ce0..fbc2070db4 100644 --- a/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift +++ b/submodules/ManagedAnimationNode/Sources/ManagedAnimationNode.swift @@ -145,7 +145,7 @@ open class ManagedAnimationNode: ASDisplayNode { public let intrinsicSize: CGSize private let imageNode: ASImageNode - private let displayLink: CADisplayLink + private let displayLink: SharedDisplayLinkDriver.Link public var imageUpdated: ((UIImage) -> Void)? public var image: UIImage? { @@ -179,19 +179,14 @@ open class ManagedAnimationNode: ASDisplayNode { self.imageNode.frame = CGRect(origin: CGPoint(), size: self.intrinsicSize) var displayLinkUpdate: (() -> Void)? - self.displayLink = CADisplayLink(target: DisplayLinkTarget { + self.displayLink = SharedDisplayLinkDriver.shared.add { displayLinkUpdate?() - }, selector: #selector(DisplayLinkTarget.event)) - if #available(iOS 10.0, *) { - self.displayLink.preferredFramesPerSecond = 60 } super.init() self.addSubnode(self.imageNode) - self.displayLink.add(to: RunLoop.main, forMode: .common) - displayLinkUpdate = { [weak self] in self?.updateAnimation() } @@ -199,6 +194,7 @@ open class ManagedAnimationNode: ASDisplayNode { open func advanceState() { guard !self.trackStack.isEmpty else { + self.displayLink.isPaused = true return } @@ -211,6 +207,7 @@ open class ManagedAnimationNode: ASDisplayNode { } self.didTryAdvancingState = false + self.displayLink.isPaused = false } public func updateAnimation() { @@ -219,6 +216,7 @@ open class ManagedAnimationNode: ASDisplayNode { } guard let state = self.state else { + self.displayLink.isPaused = true return } diff --git a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift index 0bcd163973..cb60ee6800 100644 --- a/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift +++ b/submodules/MediaPlayer/Sources/MediaPlayerScrubbingNode.swift @@ -300,7 +300,7 @@ private final class MediaPlayerScrubbingBufferingNode: ASDisplayNode { public final class MediaPlayerScrubbingNode: ASDisplayNode { private var contentNodes: MediaPlayerScrubbingNodeContentNodes - private var displayLink: CADisplayLink? + private var displayLink: SharedDisplayLinkDriver.Link? private var isInHierarchyValue: Bool = false private var playbackStatusValue: MediaPlayerPlaybackStatus? @@ -798,20 +798,9 @@ public final class MediaPlayerScrubbingNode: ASDisplayNode { if needsAnimation { if self.displayLink == nil { - class DisplayLinkProxy: NSObject { - var f: () -> Void - init(_ f: @escaping () -> Void) { - self.f = f - } - - @objc func displayLinkEvent() { - self.f() - } - } - let displayLink = CADisplayLink(target: DisplayLinkProxy({ [weak self] in + let displayLink = SharedDisplayLinkDriver.shared.add { [weak self] in self?.updateProgress() - }), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - displayLink.add(to: .main, forMode: RunLoop.Mode.common) + } self.displayLink = displayLink } self.displayLink?.isPaused = false diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift index 32f0a471f8..f482bd96fa 100644 --- a/submodules/TelegramUI/Sources/AppDelegate.swift +++ b/submodules/TelegramUI/Sources/AppDelegate.swift @@ -209,64 +209,8 @@ private func extractAccountManagerState(records: AccountRecordsView 61.0 { - displayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 60.0, maximum: maxFps, preferred: maxFps) - } - } - - self.displayLink = displayLink - displayLink.add(to: .main, forMode: .common) - displayLink.isPaused = false - } - } else if let displayLink = self.displayLink { - self.displayLink = nil - displayLink.invalidate() - } - } -} - @objc(AppDelegate) class AppDelegate: UIResponder, UIApplicationDelegate, PKPushRegistryDelegate, UNUserNotificationCenterDelegate { @objc var window: UIWindow? - private var animationSupportContext: AnimationSupportContext? var nativeWindow: (UIWindow & WindowHost)? var mainWindow: Window1! private var dataImportSplash: LegacyDataImportSplash? @@ -362,9 +306,6 @@ private final class AnimationSupportContext { self.window = window self.nativeWindow = window - //self.animationSupportContext = AnimationSupportContext(window: window) - //self.animationSupportContext?.add() - let clearNotificationsManager = ClearNotificationsManager(getNotificationIds: { completion in if #available(iOS 10.0, *) { UNUserNotificationCenter.current().getDeliveredNotifications(completionHandler: { notifications in diff --git a/submodules/lottie-ios/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift b/submodules/lottie-ios/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift index 5c3044ea38..f379d129ec 100644 --- a/submodules/lottie-ios/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift +++ b/submodules/lottie-ios/Sources/Private/CoreAnimation/Animations/CALayer+addAnimation.swift @@ -78,12 +78,6 @@ extension CALayer { let animation = CABasicAnimation(keyPath: property.caLayerKeypath) animation.fromValue = keyframeValue animation.toValue = keyframeValue - if #available(iOS 15.0, *) { - let maxFps = Float(UIScreen.main.maximumFramesPerSecond) - if maxFps > 61.0 { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) - } - } return animation } @@ -141,12 +135,6 @@ extension CALayer { let calculationMode = try self.calculationMode(for: keyframes, context: context) let animation = CAKeyframeAnimation(keyPath: property.caLayerKeypath) - if #available(iOS 15.0, *) { - let maxFps = Float(UIScreen.main.maximumFramesPerSecond) - if maxFps > 61.0 { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) - } - } // Position animations define a `CGPath` curve that should be followed, // instead of animating directly between keyframe point values. diff --git a/submodules/lottie-ios/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift b/submodules/lottie-ios/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift index 3b3518f169..11d4f4f358 100644 --- a/submodules/lottie-ios/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift +++ b/submodules/lottie-ios/Sources/Private/MainThread/LayerContainers/MainThreadAnimationLayer.swift @@ -113,12 +113,6 @@ final class MainThreadAnimationLayer: CALayer, RootAnimationLayer { let animation = CABasicAnimation(keyPath: event) animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) animation.fromValue = presentation()?.currentFrame - if #available(iOS 15.0, *) { - let maxFps = Float(UIScreen.main.maximumFramesPerSecond) - if maxFps > 61.0 { - animation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) - } - } return animation } return super.action(forKey: event) diff --git a/submodules/lottie-ios/Sources/Public/Animation/AnimationView.swift b/submodules/lottie-ios/Sources/Public/Animation/AnimationView.swift index 3408de4a2b..1ff17dbf1c 100644 --- a/submodules/lottie-ios/Sources/Public/Animation/AnimationView.swift +++ b/submodules/lottie-ios/Sources/Public/Animation/AnimationView.swift @@ -412,14 +412,14 @@ final public class AnimationView: AnimationViewBase { self.f() } } - self.workaroundDisplayLink = CADisplayLink(target: WorkaroundDisplayLinkTarget { [weak self] in + /*self.workaroundDisplayLink = CADisplayLink(target: WorkaroundDisplayLinkTarget { [weak self] in let _ = self?.realtimeAnimationProgress }, selector: #selector(WorkaroundDisplayLinkTarget.update)) if #available(iOS 15.0, *) { let maxFps = Float(UIScreen.main.maximumFramesPerSecond) self.workaroundDisplayLink?.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) } - self.workaroundDisplayLink?.add(to: .main, forMode: .common) + self.workaroundDisplayLink?.add(to: .main, forMode: .common)*/ } } else { if let workaroundDisplayLink = self.workaroundDisplayLink { @@ -1305,12 +1305,6 @@ final public class AnimationView: AnimationViewBase { layerAnimation.fillMode = CAMediaTimingFillMode.both layerAnimation.repeatCount = loopMode.caAnimationConfiguration.repeatCount layerAnimation.autoreverses = loopMode.caAnimationConfiguration.autoreverses - if #available(iOS 15.0, *) { - let maxFps = Float(UIScreen.main.maximumFramesPerSecond) - if maxFps > 61.0 { - layerAnimation.preferredFrameRateRange = CAFrameRateRange(minimum: maxFps, maximum: maxFps, preferred: maxFps) - } - } layerAnimation.isRemovedOnCompletion = false if timeOffset != 0 {