gs-timing/src/main/scala/gs/timing/v0/MonotonicProvider.scala
Pat Garrity dbc5a37672
All checks were successful
/ Build and Release Library (push) Successful in 2m5s
(patch) versioning updates and minor fix
Reviewed-on: #2
2025-07-28 02:09:31 +00:00

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