/* * Copyright 2026 Flix Authors * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */pubmod Time.Sleep {use Logger.Severityuse Math.Randomuse Time.Durationuse Time.Sleepuse RichString.{text, gray}import java.lang.{Thread => JThread}////// An effect used to sleep the current thread.///pubeffSleep {////// Sleeps the current thread for the given duration `d`.///defsleep(d: Duration): Unit }////// Handles the `Sleep` effect of the given function `f`.////// In other words, re-interprets the `Sleep` effect using the `IO` effect.///pubdefhandle(f: a -> b \ ef): a -> b \ (ef - Sleep) + IO = x ->run {f(x) } withhandlerSleep {defsleep(d, k) =letmillis = Duration.toMillis(d); JThread.sleep(millis);k() }////// Runs the `Sleep` effect of the given function `f`.////// In other words, re-interprets the `Sleep` effect using the `IO` effect.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - Sleep) + IO = handle(f)()////// Runs the given function `f` ignoring all sleeps (no-op).///pubdefwithNoOp(f: Unit -> a \ ef): a \ (ef - Sleep) =run {f() } withhandlerSleep {defsleep(_d, k) = k() }////// Middleware that intercepts the `Sleep` effect, replacing each sleep duration/// with the given constant `duration`, ignoring the original duration.///pubdefwithConstant(duration: Duration, f: Unit -> a \ ef): a \ (ef - Sleep) + Sleep =run {f() } withhandlerSleep {defsleep(_d, k) = {Sleep.sleep(duration);k() } }////// Runs the given function `f` collecting all sleep durations into a list.////// Returns a pair of the result and the list of durations in call order.///pubdefwithCollect(f: Unit -> a \ ef): (a, List[Duration]) \ (ef - Sleep) =run { (f(), List.empty()) } withhandlerSleep {defsleep(d, k) =let (r, l) = k(); (r, d :: l) }////// Middleware that intercepts the `Sleep` effect, logging each sleep duration/// via the `Logger` effect and re-raising the `Sleep` effect.///pubdefwithLogging(f: Unit -> a \ ef): a \ (ef - Sleep) + { Sleep, Logger } =run {f() } withhandlerSleep {defsleep(d, k) = {Logger.log(Severity.Debug, gray("Sleep: ") +text("${d}"));Sleep.sleep(d);k() } }////// Middleware that intercepts the `Sleep` effect, scaling each sleep duration/// by the given `factor`.///pubdefwithScale(factor: Float64, f: Unit -> a \ ef): a \ (ef - Sleep) + Sleep =run {f() } withhandlerSleep {defsleep(d, k) = {letnanos = Int64.toFloat64(Duration.toNanos(d)) *factor;letscaled = Float64.clampToInt64(min = 0i64, max = Int64.maxValue(), nanValue = 0i64, nanos);Sleep.sleep(Duration.nanoseconds(scaled));k() } }////// Middleware that intercepts the `Sleep` effect, capping each sleep duration/// to the given `cap`.///pubdefwithMaxSleep(cap: Duration, f: Unit -> a \ ef): a \ (ef - Sleep) + Sleep =run {f() } withhandlerSleep {defsleep(d, k) = {Sleep.sleep(Order.min(d, cap));k() } }////// Middleware that intercepts the `Sleep` effect, capping the total cumulative/// sleep duration to the given `budget`. Once the budget is exhausted, all/// further sleeps become no-ops.///pubdefwithMaxTotalSleep(budget: Duration, f: Unit -> a \ ef): a \ (ef - Sleep) + Sleep =regionrc {letremaining: Ref[Duration, _] = Ref.fresh(rc, budget);run {f() } withhandlerSleep {defsleep(d, k) = {letr = Ref.get(remaining);letactual = Order.min(d, r);Ref.put(r-actual, remaining);Sleep.sleep(actual);k() } } }////// Middleware that intercepts the `Sleep` effect, ensuring each sleep duration/// is at least the given `floor`.///pubdefwithMinSleep(floor: Duration, f: Unit -> a \ ef): a \ (ef - Sleep) + Sleep =run {f() } withhandlerSleep {defsleep(d, k) = {Sleep.sleep(Order.max(d, floor));k() } }////// Middleware that intercepts the `Sleep` effect, adding random jitter to each/// sleep duration. The `factor` controls the maximum jitter fraction, e.g. `0.2`/// means each sleep duration is randomly adjusted by up to ±20%.///pubdefwithJitter(factor: Float64, f: Unit -> a \ ef): a \ (ef - Sleep) + { Sleep, Random } =run {f() } withhandlerSleep {defsleep(d, k) = {letnanos = Int64.toFloat64(Duration.toNanos(d));letr = Random.randomFloat64()*2.0f64*factor-factor;letjittered = nanos* (1.0f64+r);letclamped = Float64.clampToInt64(min = 0i64, max = Int64.maxValue(), nanValue = 0i64, jittered);Sleep.sleep(Duration.nanoseconds(clamped));k() } }}