CountUpTimer - compute elapsed time using real time

This commit is contained in:
Florian Renaud 2023-01-27 17:49:55 +01:00
parent a06104534b
commit bdfebac76d
2 changed files with 49 additions and 8 deletions

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.lib.core.utils.timer
interface Clock {
fun epochMillis(): Long
}
class DefaultClock : Clock {
/**
* Provides a UTC epoch in milliseconds
*
* This value is not guaranteed to be correct with reality
* as a User can override the system time and date to any values.
*/
override fun epochMillis(): Long {
return System.currentTimeMillis()
}
}

View File

@ -28,22 +28,23 @@ import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class) @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class CountUpTimer(private val initialTime: Long = 0L, private val intervalInMs: Long = 1_000) { class CountUpTimer(initialTime: Long = 0L, private val intervalInMs: Long = 1_000) {
private val coroutineScope = CoroutineScope(Dispatchers.Main) private val coroutineScope = CoroutineScope(Dispatchers.Main)
private val elapsedTime: AtomicLong = AtomicLong(initialTime)
private val resumed: AtomicBoolean = AtomicBoolean(false) private val resumed: AtomicBoolean = AtomicBoolean(false)
private val clock: Clock = DefaultClock()
private var lastTime: AtomicLong = AtomicLong()
private val elapsedTime: AtomicLong = AtomicLong(initialTime)
init { init {
startCounter() startCounter()
} }
private fun startCounter() { private fun startCounter() {
val internalDelay = if (intervalInMs > 100) intervalInMs / 10 else intervalInMs tickerFlow(coroutineScope, intervalInMs)
tickerFlow(coroutineScope, internalDelay)
.filter { resumed.get() } .filter { resumed.get() }
.map { elapsedTime.addAndGet(internalDelay) } .map { addAndGetElapsedTime() }
.filter { (it - initialTime) % intervalInMs == 0L }
.onEach { tickListener?.onTick(it) } .onEach { tickListener?.onTick(it) }
.launchIn(coroutineScope) .launchIn(coroutineScope)
} }
@ -55,19 +56,25 @@ class CountUpTimer(private val initialTime: Long = 0L, private val intervalInMs:
} }
fun pause() { fun pause() {
tickListener?.onTick(elapsedTime()) tickListener?.onTick(addAndGetElapsedTime())
resumed.set(false) resumed.set(false)
} }
fun resume() { fun resume() {
lastTime.set(clock.epochMillis())
resumed.set(true) resumed.set(true)
} }
fun stop() { fun stop() {
tickListener?.onTick(elapsedTime()) tickListener?.onTick(addAndGetElapsedTime())
coroutineScope.cancel() coroutineScope.cancel()
} }
private fun addAndGetElapsedTime(): Long {
val now = clock.epochMillis()
return elapsedTime.addAndGet(now - lastTime.getAndSet(now))
}
fun interface TickListener { fun interface TickListener {
fun onTick(milliseconds: Long) fun onTick(milliseconds: Long)
} }