All checks were successful
/ Build and Release Library (push) Successful in 2m5s
Reviewed-on: #2
121 lines
3.5 KiB
Scala
121 lines
3.5 KiB
Scala
package gs.timing.v0
|
|
|
|
import cats.effect.Ref
|
|
import cats.effect.Sync
|
|
import cats.syntax.all.*
|
|
|
|
/** Provider of monotonic time - a monotonic clock is a clock that will never
|
|
* adjust or jump forwards or backwards, and represents the amount of time
|
|
* elapsed since some arbitrary point in time in the past.
|
|
*
|
|
* This is useful for calculating _elapsed time_ -- the exact duration elapsed
|
|
* between two relative events. This is _not useful_ for calculating anything
|
|
* related to real dates and times.
|
|
*
|
|
* ## System Provider
|
|
*
|
|
* For most real use cases, the _system_ provider is appropriate. This
|
|
* delegates to `System.nanoTime()` under the covers, which leverages the
|
|
* monotonic clock of the system where the application is running.
|
|
*
|
|
* {{{
|
|
* import cats.effect.IO
|
|
* import gs.timing.v0.MonotonicProvider
|
|
*
|
|
* val provider = MonotonicProvider.system[IO]
|
|
*
|
|
* val time: IO[Long] = provider.monotonic()
|
|
* }}}
|
|
*
|
|
* ## Manual Provider
|
|
*
|
|
* For testing purposes, a manual provider is supported. This provider allows
|
|
* the user to manually control a tick count and assume the role of the clock.
|
|
* This allows for deterministic clock values.
|
|
*
|
|
* {{{
|
|
* import cats.effect.IO
|
|
* import gs.timing.v0.MonotonicProvider
|
|
*
|
|
* for
|
|
* provider <- MonotonicProvider.manual[IO]
|
|
* t1 <- provider.monotonic() // 0
|
|
* _ <- provider.tick()
|
|
* t2 <- provider.monotonic() // 1
|
|
* _ <- provider.set(10)
|
|
* t3 <- provider.monotonic() // 10
|
|
* _ <- provider.reset()
|
|
* t4 <- provider.monotonic() // 0
|
|
* yield
|
|
* ()
|
|
* }}}
|
|
*/
|
|
trait MonotonicProvider[F[_]]:
|
|
/** @return
|
|
* The current value of the underlying monotonic clock.
|
|
*/
|
|
def monotonic(): F[Long]
|
|
|
|
object MonotonicProvider:
|
|
|
|
/** @return
|
|
* A new provider based on the system's underlying monotonic clock. This
|
|
* implementation delegates to `System.nanoTime()`.
|
|
*/
|
|
def system[F[_]: Sync]: SystemProvider[F] =
|
|
new SystemProvider[F]
|
|
|
|
/** @return
|
|
* A new provider, always initialized to 0, that is manually controlled by
|
|
* the user.
|
|
*/
|
|
def manual[F[_]: Sync]: F[ManualTickProvider[F]] =
|
|
ManualTickProvider.initialize[F]
|
|
|
|
final class SystemProvider[F[_]: Sync] extends MonotonicProvider[F]:
|
|
override def monotonic(): F[Long] = Sync[F].delay(System.nanoTime())
|
|
|
|
/** Manual implementation of the [[MonotonicProvider]] that allows the caller
|
|
* to directly increment/set a tick count, where each tick corresponds to one
|
|
* nanosecond of elapsed time.
|
|
*
|
|
* Use `ManualTickProvider.initialize[F]` to instantiate this class:
|
|
*
|
|
* {{{
|
|
* val program: IO[ManualTickProvider[IO]] =
|
|
* ManualTickProvider.initialize[IO]
|
|
* }}}
|
|
*/
|
|
final class ManualTickProvider[F[_]] private (
|
|
ticks: Ref[F, Long]
|
|
) extends MonotonicProvider[F]:
|
|
/** @return
|
|
* The current tick count.
|
|
*/
|
|
override def monotonic(): F[Long] = ticks.get
|
|
|
|
/** Increment the tick count by 1.
|
|
*/
|
|
def tick(): F[Unit] = ticks.update(_ + 1)
|
|
|
|
/** Set the tick count to a specific value.
|
|
*
|
|
* @param newTickCount
|
|
* The new tick count value.
|
|
* @return
|
|
* Side-effect.
|
|
*/
|
|
def set(newTickCount: Long): F[Unit] = ticks.set(newTickCount)
|
|
|
|
/** Reset the tick count to 0.
|
|
*/
|
|
def reset(): F[Unit] = ticks.set(0)
|
|
|
|
object ManualTickProvider:
|
|
|
|
def initialize[F[_]: Sync]: F[ManualTickProvider[F]] =
|
|
Ref.of[F, Long](0).map(ticks => new ManualTickProvider[F](ticks))
|
|
|
|
end ManualTickProvider
|
|
|
|
end MonotonicProvider
|