Skip to content

Instantly share code, notes, and snippets.

@kaushikgopal
Created June 20, 2025 23:19
Show Gist options
  • Save kaushikgopal/4adcd72aa253179340126391339ce2e2 to your computer and use it in GitHub Desktop.
Save kaushikgopal/4adcd72aa253179340126391339ce2e2 to your computer and use it in GitHub Desktop.
Samplers in Kotlin
/**
* runs a block at most [rate] times per [period]
*
* Examples:
* - [rate=30 period=1s]: run max 30 times within period of 1 second (30fps)
* - [rate=10 period=1mt]: run max 10 times within period of 1 minuts (10 updates per minute)
*
* @param rate Maximum number of executions per period
* @param period Time duration for the rate limit
* @param timeSource Source for time measurements (default: monotonic system time)
*/
class TimeSampler(
val rate: Int,
val period: Duration = 1.seconds,
val timeSource: TimeSource = TimeSource.Monotonic,
) {
private val minInterval: Duration = period / rate
private var lastExecutionMark: TimeMark? = null
/**
* execute [block] if enough time has passed since last execution.
*
* @param block Code to execute at the controlled rate
* @return true if block was executed, false if skipped due to rate limiting
*/
fun sample(block: TimeSampler.() -> Unit): Boolean {
val currentMark = timeSource.markNow()
val lastMark = lastExecutionMark
// First execution always runs
if (lastMark == null) {
lastExecutionMark = currentMark
block()
return true
}
// Check if enough time has passed
val elapsed = lastMark.elapsedNow()
if (elapsed >= minInterval) {
lastExecutionMark = currentMark
block()
return true
}
return false
}
/** resets the sampler, allowing immediate execution on next [sample] call. */
fun reset() {
lastExecutionMark = null
}
}
/**
* run a block every [runEvery] calls.
* With [runEvery]=60:
* - Default [count]=0: Executes on calls #1, #61, #121...
* - Custom [count]=5: Executes on calls #56, #116, #176...
*
* @param count provide a starting count to the sampler (if you want to shift the first execution)
*/
class FrequencySampler(val runEvery: Int, private var count: Int = 0) {
fun sample(block: FrequencySampler.() -> Unit) {
if ((count % runEvery) == 0) block()
count++
}
}
class FrequencySamplerWithTime(
runEvery: Int = 1500,
val timeSource: TimeSource = TimeSource.Monotonic,
) {
private val sampler = FrequencySampler(runEvery)
private var startMark: TimeMark? = null
private var lastMark: TimeMark? = null
// [timePerCall] - approximate time each unsampled call takes
fun sample(block: FrequencySamplerWithTime.(timePerCall: Duration) -> Unit = {}) {
// Init on first call.
if (lastMark == null) {
val now = timeSource.markNow()
startMark = now
lastMark = now
}
sampler.sample {
val currentMark = timeSource.markNow()
val diff = lastMark?.elapsedNow() ?: Duration.ZERO
block(diff / runEvery)
lastMark = currentMark
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment