Mission Control Turbo: macOS multitasking turbocharged
at main 72 lines 2.1 kB view raw
1import Foundation 2 3/// Timer-driven critically-damped spring animator. 4/// Runs at ~120Hz on the main queue, driving progress from current value toward a target. 5final class SpringAnimator { 6 private var timer: DispatchSourceTimer? 7 private var current: CGFloat 8 private var velocity: CGFloat 9 private let target: CGFloat 10 private let onUpdate: (CGFloat) -> Void 11 private let onComplete: () -> Void 12 13 // Spring parameters (critically damped) 14 private let stiffness: CGFloat 15 private let damping: CGFloat // computed for critical damping: 2 * sqrt(stiffness) 16 17 init( 18 from: CGFloat, 19 to target: CGFloat, 20 initialVelocity: CGFloat = 0, 21 stiffness: CGFloat = 300.0, 22 onUpdate: @escaping (CGFloat) -> Void, 23 onComplete: @escaping () -> Void 24 ) { 25 self.current = from 26 self.target = target 27 self.velocity = initialVelocity 28 self.stiffness = stiffness 29 self.onUpdate = onUpdate 30 self.onComplete = onComplete 31 self.damping = 2.0 * sqrt(stiffness) 32 } 33 34 func start() { 35 stop() 36 let dt: CGFloat = 1.0 / 120.0 37 let timer = DispatchSource.makeTimerSource(queue: .main) 38 timer.schedule(deadline: .now(), repeating: Double(dt)) 39 timer.setEventHandler { [weak self] in 40 self?.tick(dt: dt) 41 } 42 self.timer = timer 43 timer.resume() 44 } 45 46 func stop() { 47 timer?.cancel() 48 timer = nil 49 } 50 51 private func tick(dt: CGFloat) { 52 let displacement = current - target 53 let springForce = -stiffness * displacement 54 let dampingForce = -damping * velocity 55 let acceleration = springForce + dampingForce 56 57 velocity += acceleration * dt 58 current += velocity * dt 59 60 // Settle when close enough 61 if abs(current - target) < 0.001 && abs(velocity) < 0.01 { 62 current = target 63 velocity = 0 64 onUpdate(current) 65 stop() 66 onComplete() 67 return 68 } 69 70 onUpdate(current) 71 } 72}