added more tools, start shifting to kotlin 2.2.0

This commit is contained in:
Sergey Chernov 2025-09-25 10:26:12 +04:00
parent a121f2c4a6
commit 01d9e0239e
7 changed files with 132 additions and 8 deletions

View File

@ -1,14 +1,14 @@
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins { plugins {
kotlin("multiplatform") version "2.0.21" kotlin("multiplatform") version "2.2.20"
kotlin("plugin.serialization") version "2.0.21" kotlin("plugin.serialization") version "2.2.20"
id("org.jetbrains.dokka") version "1.9.20" id("org.jetbrains.dokka") version "1.9.20"
`maven-publish` `maven-publish`
} }
group = "net.sergeych" group = "net.sergeych"
version = "0.1.12-SNAPSHOT" version = "0.2.1-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
@ -31,7 +31,6 @@ kotlin {
// iosSimulatorArm64() // iosSimulatorArm64()
linuxX64() linuxX64()
linuxArm64() linuxArm64()
mingwX64()
@OptIn(ExperimentalWasmDsl::class) @OptIn(ExperimentalWasmDsl::class)
wasmJs { wasmJs {
@ -90,6 +89,7 @@ kotlin {
// val nativeTest by getting // val nativeTest by getting
val wasmJsMain by getting { val wasmJsMain by getting {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser-wasm-js:0.5.0")
} }
} }
val wasmJsTest by getting { val wasmJsTest by getting {

View File

@ -1,3 +1,3 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.js.compiler=ir
kotlin.mpp.applyDefaultHierarchyTemplate=false kotlin.mpp.applyDefaultHierarchyTemplate=false
kotlin.daemon.jvmargs=-Xmx3072M

View File

@ -1,7 +1,7 @@
package net.sergeych.bintools package net.sergeych.bintools
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.sergeych.mp_tools.encodeToBase64Compact import net.sergeych.mp_tools.encodeToBase64Url
import kotlin.math.min import kotlin.math.min
import kotlin.random.Random import kotlin.random.Random
@ -53,7 +53,7 @@ class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
/** /**
* hex representation of data * hex representation of data
*/ */
override fun toString(): String = hex override fun toString(): String = base64
/** /**
* Hex encoded data * 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. * 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 * Lazy (cached) view of [data] as ByteArray

View File

@ -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
}

View File

@ -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() }
}
}

View File

@ -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
}
}

View File

@ -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
}