added more tools, start shifting to kotlin 2.2.0
This commit is contained in:
1 parent
a121f2c4a6
commit
01d9e0239e
7 files changed
+132
-8
No files matched your search
@@ -1,7 +1,7 @@
|
||||
package net.sergeych.bintools
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.mp_tools.encodeToBase64Compact
|
||||
import net.sergeych.mp_tools.encodeToBase64Url
|
||||
import kotlin.math.min
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -53,7 +53,7 @@ class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
|
||||
/**
|
||||
* hex representation of data
|
||||
*/
|
||||
override fun toString(): String = hex
|
||||
override fun toString(): String = base64
|
||||
|
||||
/**
|
||||
* Hex encoded data
|
||||
@@ -68,7 +68,7 @@ class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
|
||||
/**
|
||||
* Lazy encode to base64 with url alphabet, without trailing fill '=' characters.
|
||||
*/
|
||||
val base64 by lazy { data.asByteArray().encodeToBase64Compact() }
|
||||
val base64 by lazy { data.asByteArray().encodeToBase64Url() }
|
||||
|
||||
/**
|
||||
* Lazy (cached) view of [data] as ByteArray
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package net.sergecyh.diwan.tools
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Value with expiration.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class Expiring<T>(
|
||||
val value: T,
|
||||
val expiresAt: Instant,
|
||||
) {
|
||||
constructor(value: T, expiresIn: Duration) : this(value, Clock.System.now() + expiresIn)
|
||||
|
||||
/**
|
||||
* @return value if not expired, null otherwise
|
||||
*/
|
||||
fun valueOrNull(): T? = if( isExpired ) value else null
|
||||
|
||||
val isExpired: Boolean get() = expiresAt < Clock.System.now()
|
||||
val isOk: Boolean get() = !isExpired
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.sergecyh.diwan.tools
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* Experimental
|
||||
*
|
||||
* set of mutexes associated with keys `K`, so each key can have its own mutex
|
||||
* used with [lock] or [withLock]
|
||||
*/
|
||||
class IndividualLock<K> {
|
||||
|
||||
private val access = Mutex()
|
||||
private val locks = mutableMapOf<K, Mutex>()
|
||||
|
||||
suspend inline fun <T>withLock(key: K, block: ()->T): T = lock(key).use { block() }
|
||||
|
||||
suspend fun lock(key: K): AutoCloseable {
|
||||
val m = access.withLock {
|
||||
locks.getOrPut(key) { Mutex() }
|
||||
}
|
||||
m.lock()
|
||||
return AutoCloseable { m.unlock() }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package net.sergecyh.diwan.tools
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
|
||||
/**
|
||||
* Coroutine-based unique "once-per-key" executor.
|
||||
*
|
||||
* When [invoke] is called, it checks that there is already invocation in progress
|
||||
* and either wait for it to complete or start a new one.
|
||||
* @param K key value, should have valid `hashCode` and `equals`, e.g., suitable to be a map key
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class OncePer<K> {
|
||||
private val access = Mutex()
|
||||
|
||||
private val queue = mutableMapOf<K,CompletableDeferred<Any>>()
|
||||
|
||||
/**
|
||||
* Execute [f] as unique-per-key [key]. If such invocation is in progress, it suspends than returns
|
||||
* its result. If there is no invocation for such a key, start new invocation.
|
||||
*
|
||||
* __Important note__ all simultaneous invocation should have the same [R] type, or, at least, a type
|
||||
* _castable to [R]_.
|
||||
*/
|
||||
suspend operator fun <R: Any>invoke(key: K, f: suspend ()->R ): R {
|
||||
var mustStart = false
|
||||
val d = access.withLock {
|
||||
queue.getOrPut(key) {
|
||||
CompletableDeferred<Any>().also { mustStart = true }
|
||||
}
|
||||
}
|
||||
if( mustStart ) {
|
||||
d.complete(f())
|
||||
access.withLock {
|
||||
queue.remove(key)
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return d.await() as R
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.sergecyh.diwan.tools
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlin.time.Duration
|
||||
|
||||
/**
|
||||
* Experimental.
|
||||
*
|
||||
* limit invocations rate of [invoke] to once per [minimalInterval] or less frequent.
|
||||
* Note that it is not a debouncing, it just ignores too frequent calls!
|
||||
*/
|
||||
@Suppress("unused")
|
||||
class RateLimiter(val minimalInterval: Duration) {
|
||||
var lastExecutedAt = Instant.DISTANT_PAST
|
||||
private set
|
||||
|
||||
/**
|
||||
* invoke [f] if the last invocation was earlier than now minus [minimalInterval], otherwise
|
||||
* do nothing.
|
||||
* @return the value returned by [f] if it was actually invoked this time, null otherwise
|
||||
*/
|
||||
suspend operator fun <T> invoke(f: suspend () -> T): T? =
|
||||
if (Clock.System.now() - lastExecutedAt > minimalInterval) {
|
||||
f().also { lastExecutedAt = Clock.System.now() }
|
||||
} else null
|
||||
}
|
||||
Reference in new issue
Block a user