70 lines
2.1 KiB
Swift
70 lines
2.1 KiB
Swift
|
|
import Foundation
|
||
|
|
import ForgeGrade
|
||
|
|
|
||
|
|
/// Rate-limits camera look pushes. Preview updates instantly elsewhere;
|
||
|
|
/// camera gets at most one push per interval, trailing edge guaranteed —
|
||
|
|
/// final panel position always lands on camera.
|
||
|
|
public actor DebouncedPusher {
|
||
|
|
private let driver: any CameraDriver
|
||
|
|
private let interval: Duration
|
||
|
|
private let latticeSize: Int
|
||
|
|
|
||
|
|
private var latestGrade: GradeStack?
|
||
|
|
private var lastPushAt: ContinuousClock.Instant?
|
||
|
|
private var pending: Task<Void, Never>?
|
||
|
|
private var stopped = false
|
||
|
|
|
||
|
|
public init(driver: any CameraDriver, interval: Duration = .milliseconds(150), latticeSize: Int = 33) {
|
||
|
|
self.driver = driver
|
||
|
|
self.interval = interval
|
||
|
|
self.latticeSize = latticeSize
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Record new grade; push now if interval elapsed, else schedule trailing push.
|
||
|
|
public func update(grade: GradeStack) {
|
||
|
|
guard !stopped else { return }
|
||
|
|
latestGrade = grade
|
||
|
|
|
||
|
|
let now = ContinuousClock.now
|
||
|
|
if let last = lastPushAt, last.duration(to: now) < interval {
|
||
|
|
scheduleTrailing(after: interval - last.duration(to: now))
|
||
|
|
} else {
|
||
|
|
pushNow()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func scheduleTrailing(after delay: Duration) {
|
||
|
|
guard pending == nil else { return } // one trailing push covers all updates
|
||
|
|
pending = Task {
|
||
|
|
try? await Task.sleep(for: delay)
|
||
|
|
await self.firePending()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private func firePending() {
|
||
|
|
pending = nil
|
||
|
|
guard !stopped else { return }
|
||
|
|
pushNow()
|
||
|
|
}
|
||
|
|
|
||
|
|
private func pushNow() {
|
||
|
|
guard let grade = latestGrade else { return }
|
||
|
|
lastPushAt = ContinuousClock.now
|
||
|
|
let caps = driver.capabilities
|
||
|
|
let look = Flattener.flatten(
|
||
|
|
stack: grade,
|
||
|
|
latticeSize: caps.lut3dSizes.contains(latticeSize) ? latticeSize : (caps.lut3dSizes.max() ?? latticeSize),
|
||
|
|
splitLeadingCDL: caps.supportsNativeCDL)
|
||
|
|
let driver = self.driver
|
||
|
|
Task {
|
||
|
|
try? await driver.push(look: look)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public func stop() {
|
||
|
|
stopped = true
|
||
|
|
pending?.cancel()
|
||
|
|
pending = nil
|
||
|
|
}
|
||
|
|
}
|