mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
278 lines
7.2 KiB
Swift
278 lines
7.2 KiB
Swift
import Foundation
|
|
import VGSL
|
|
|
|
final class DivTimerController {
|
|
typealias RunActions = ([DivAction]) -> Void
|
|
typealias UpdateCard = () -> Void
|
|
|
|
enum State {
|
|
case started
|
|
case stopped
|
|
case paused
|
|
}
|
|
|
|
private(set) var state: State = .stopped
|
|
|
|
private let cardId: DivCardID
|
|
private let divTimer: DivTimer
|
|
private let timerScheduler: Scheduling
|
|
private let timeMeasuring: TimeMeasuring
|
|
private let runActions: RunActions
|
|
private let updateCard: UpdateCard
|
|
private let variablesStorage: DivVariablesStorage
|
|
private let functionsStorage: DivFunctionsStorage
|
|
private let persistentValuesStorage: DivPersistentValuesStorage
|
|
private let reporter: DivReporter
|
|
|
|
private var savedDuration: TimeInterval?
|
|
private var savedInterval: TimeInterval?
|
|
private var tickTimer: TimerType?
|
|
private var endTimer: TimerType?
|
|
private var expectedTickCount: Int = 0
|
|
private var actualTickCount: Int = 0
|
|
private var tickBeforeEndIsNeeded = false
|
|
|
|
init(
|
|
cardId: DivCardID,
|
|
divTimer: DivTimer,
|
|
timerScheduler: Scheduling,
|
|
timeMeasuring: TimeMeasuring,
|
|
runActions: @escaping RunActions,
|
|
updateCard: @escaping UpdateCard,
|
|
variablesStorage: DivVariablesStorage,
|
|
functionsStorage: DivFunctionsStorage,
|
|
persistentValuesStorage: DivPersistentValuesStorage,
|
|
reporter: DivReporter
|
|
) {
|
|
self.cardId = cardId
|
|
self.divTimer = divTimer
|
|
self.timerScheduler = timerScheduler
|
|
self.timeMeasuring = timeMeasuring
|
|
self.runActions = runActions
|
|
self.updateCard = updateCard
|
|
self.variablesStorage = variablesStorage
|
|
self.functionsStorage = functionsStorage
|
|
self.persistentValuesStorage = persistentValuesStorage
|
|
self.reporter = reporter
|
|
}
|
|
|
|
deinit {
|
|
cancel()
|
|
}
|
|
|
|
func start() {
|
|
if state != .stopped {
|
|
DivKitLogger.error("Timer '\(divTimer.id)' can't start because it has state '\(state)'.")
|
|
return
|
|
}
|
|
let expressionResolver = ExpressionResolver(
|
|
path: cardId.path,
|
|
variablesStorage: variablesStorage,
|
|
functionsStorage: functionsStorage,
|
|
persistentValuesStorage: persistentValuesStorage,
|
|
reporter: reporter
|
|
)
|
|
guard divTimer.parametersAreValid(expressionResolver) else {
|
|
DivKitLogger.error("Timer '\(divTimer.id)' is not valid.")
|
|
return
|
|
}
|
|
state = .started
|
|
if let duration = divTimer.getDuration(expressionResolver),
|
|
let tickInterval = divTimer.getTickInterval(expressionResolver) {
|
|
expectedTickCount = duration / tickInterval
|
|
tickBeforeEndIsNeeded = duration % tickInterval == 0
|
|
} else {
|
|
expectedTickCount = 0
|
|
tickBeforeEndIsNeeded = false
|
|
}
|
|
savedDuration = divTimer.getDuration(expressionResolver)?.toTimeInterval
|
|
savedInterval = divTimer.getTickInterval(expressionResolver)?.toTimeInterval
|
|
actualTickCount = 0
|
|
setVariable(0)
|
|
timeMeasuring.start()
|
|
makeTickTimer()
|
|
makeEndTimer()
|
|
}
|
|
|
|
func stop() {
|
|
if state == .stopped {
|
|
DivKitLogger.error("Timer '\(divTimer.id)' already stopped.")
|
|
return
|
|
}
|
|
state = .stopped
|
|
invalidateTimers()
|
|
if let endActions = divTimer.endActions {
|
|
runActions(endActions)
|
|
}
|
|
updateCard()
|
|
}
|
|
|
|
func pause() {
|
|
if state != .started {
|
|
DivKitLogger.error("Timer '\(divTimer.id)' can't pause because it has state '\(state)'.")
|
|
return
|
|
}
|
|
state = .paused
|
|
timeMeasuring.pause()
|
|
setVariable(timeMeasuring.passedInterval())
|
|
invalidateTimers()
|
|
}
|
|
|
|
func resume() {
|
|
if state != .paused {
|
|
DivKitLogger.error("Timer '\(divTimer.id)' can't resume because it has state '\(state)'.")
|
|
return
|
|
}
|
|
state = .started
|
|
makeRemainderTickTimer()
|
|
makeEndTimer()
|
|
timeMeasuring.resume()
|
|
}
|
|
|
|
func cancel() {
|
|
state = .stopped
|
|
invalidateTimers()
|
|
}
|
|
|
|
func reset() {
|
|
cancel()
|
|
start()
|
|
}
|
|
|
|
private func makeEndTimer() {
|
|
guard let duration = savedDuration else {
|
|
return
|
|
}
|
|
let delay = duration - timeMeasuring.passedInterval()
|
|
guard delay > 0 else {
|
|
return
|
|
}
|
|
endTimer = timerScheduler.makeTimer(
|
|
delay: delay,
|
|
handler: { [weak self] in
|
|
self?.onEnd(duration: duration)
|
|
}
|
|
)
|
|
}
|
|
|
|
private func makeRemainderTickTimer() {
|
|
guard let tickInterval = savedInterval, tickInterval > 0 else {
|
|
return
|
|
}
|
|
let remainder = timeMeasuring.passedInterval().truncatingRemainder(dividingBy: tickInterval)
|
|
guard remainder > ACCURACY else {
|
|
makeTickTimer()
|
|
return
|
|
}
|
|
tickTimer = timerScheduler.makeTimer(
|
|
delay: tickInterval - remainder,
|
|
handler: { [weak self] in
|
|
self?.makeTickTimer()
|
|
self?.onTick()
|
|
}
|
|
)
|
|
}
|
|
|
|
private func makeTickTimer() {
|
|
guard let tickInterval = savedInterval, tickInterval > 0 else {
|
|
return
|
|
}
|
|
tickTimer = timerScheduler.makeRepeatingTimer(
|
|
interval: tickInterval,
|
|
handler: { [weak self] in
|
|
self?.onTick()
|
|
}
|
|
)
|
|
}
|
|
|
|
private func onTick() {
|
|
guard state == .started else {
|
|
return
|
|
}
|
|
let passedInterval = timeMeasuring.passedInterval()
|
|
if let duration = savedDuration, passedInterval >= duration {
|
|
return
|
|
}
|
|
actualTickCount += 1
|
|
setVariable(passedInterval)
|
|
if let tickActions = divTimer.tickActions {
|
|
runActions(tickActions)
|
|
}
|
|
updateCard()
|
|
}
|
|
|
|
private func onEnd(duration: TimeInterval) {
|
|
guard state == .started else {
|
|
return
|
|
}
|
|
setVariable(duration)
|
|
if tickBeforeEndIsNeeded, actualTickCount < expectedTickCount {
|
|
if let tickActions = divTimer.tickActions {
|
|
runActions(tickActions)
|
|
}
|
|
}
|
|
stop()
|
|
}
|
|
|
|
private func setVariable(_ value: TimeInterval) {
|
|
if let variableName = divTimer.valueVariable {
|
|
variablesStorage.update(
|
|
cardId: cardId,
|
|
name: DivVariableName(rawValue: variableName),
|
|
value: String(format: "%.0f", value * 1000)
|
|
)
|
|
}
|
|
}
|
|
|
|
private func invalidateTimers() {
|
|
tickTimer?.invalidate()
|
|
endTimer?.invalidate()
|
|
tickTimer = nil
|
|
endTimer = nil
|
|
}
|
|
}
|
|
|
|
extension DivTimer {
|
|
fileprivate func getDuration(_ expressionResolver: ExpressionResolver) -> Int? {
|
|
let duration = resolveDuration(expressionResolver)
|
|
guard duration > 0 else {
|
|
return nil
|
|
}
|
|
return duration
|
|
}
|
|
|
|
fileprivate func getTickInterval(_ expressionResolver: ExpressionResolver) -> Int? {
|
|
guard let tickInterval = resolveTickInterval(expressionResolver),
|
|
tickInterval > 0 else {
|
|
return nil
|
|
}
|
|
return tickInterval
|
|
}
|
|
|
|
fileprivate func parametersAreValid(_ expressionResolver: ExpressionResolver) -> Bool {
|
|
let duration = resolveDuration(expressionResolver)
|
|
if duration > 0 {
|
|
return true
|
|
}
|
|
guard let tickInterval = resolveTickInterval(expressionResolver), tickInterval > 0 else {
|
|
DivKitLogger.error("Timer '\(self.id)' parameters is not valid: tick_interval not set")
|
|
return false
|
|
}
|
|
if tickActions == nil, valueVariable == nil {
|
|
DivKitLogger.error(
|
|
"Timer '\(self.id)' parameters is not valid: set tickActions or valueVariable"
|
|
)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
|
|
extension Int {
|
|
var toTimeInterval: TimeInterval {
|
|
TimeInterval(Double(self) / 1000)
|
|
}
|
|
}
|
|
|
|
private let ACCURACY = 0.01
|