Compare commits

...

6 Commits

Author SHA1 Message Date
2eb38e27ed +ByteChunk 2024-10-11 08:24:00 +07:00
a4cf3fe8ee readme fix 2024-09-01 19:38:06 +02:00
988974230d readme fix 2024-08-24 07:14:10 +02:00
10b21ab205 docs/tests addons 2024-08-24 07:13:38 +02:00
fc9d4c5070 v0.1.7 release for all platforms on kotlin 2.0.20 - important fix in wasmJS 2024-08-24 07:00:01 +02:00
e07ebf564a v0.1.6 release for all platforms 2024-07-27 19:00:36 +02:00
12 changed files with 1167 additions and 174 deletions

View File

@ -6,6 +6,10 @@ in native targets.
# Recent changes
- 0.1.7 built with kotlin 2.0.20 which contains important fix in wasmJS
- 0.1.6 add many useful features, added support to wasmJS and all other platforms. Note to wasmJS: it appears to be a bug in wasm compiler so BipackDecoder could cause wasm loading problem.
- 0.1.1: added serialized KVStorage with handy implementation on JVM and JS platforms and some required synchronization
tools.
-
@ -13,6 +17,10 @@ in native targets.
The last 1.8-based version is 0.0.8. Some fixes are not yet backported to it pls leave an issue of needed.
# Documentation
Aside of the samples in this readme please see [library documentation](https://code.sergeych.net/docs/mp_bintools/).
# Usage
Add our maven:
@ -29,7 +37,7 @@ And add dependency to the proper place in your project like this:
```kotlin
dependencies {
// ...
implementation("net.sergeych:mp_bintools:0.1.0")
implementation("net.sergeych:mp_bintools:0.1.7")
}
```

View File

@ -1,6 +1,6 @@
plugins {
kotlin("multiplatform") version "2.0.0"
kotlin("plugin.serialization") version "2.0.0"
kotlin("multiplatform") version "2.0.20"
kotlin("plugin.serialization") version "2.0.20"
id("org.jetbrains.dokka") version "1.9.20"
`maven-publish`
}
@ -8,7 +8,7 @@ plugins {
val serialization_version = "1.6.5-SNAPSHOT"
group = "net.sergeych"
version = "0.1.6-SNAPSHOT"
version = "0.1.8-SNAPSHOT"
repositories {
mavenCentral()
@ -18,40 +18,24 @@ repositories {
kotlin {
jvmToolchain(8)
jvm {
// compilations.all {
// kotlinOptions.jvmTarget = "1.8"
// }
withJava()
testRuns["test"].executionTask.configure {
useJUnitPlatform()
}
}
jvm()
js {
browser()
nodejs()
}
// macosArm64()
// iosX64()
// iosArm64()
// macosX64()
// iosSimulatorArm64()
macosArm64()
iosX64()
iosArm64()
macosX64()
iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
// val hostOs = System.getProperty("os.name")
// val isMingwX64 = hostOs.startsWith("Windows")
// val nativeTarget = when {
// hostOs == "Mac OS X" -> macosX64("native")
// hostOs == "Linux" -> linuxX64("native")
// isMingwX64 -> mingwX64("native")
// else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
// }
wasmJs {
browser()
// binaries.executable()
binaries.executable()
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
package net.sergeych.bintools
import kotlinx.serialization.Serializable
import kotlin.math.min
import kotlin.random.Random
/**
* Bytes sequence with comparison, concatenation, and string representation,
* could be used as hash keys for pure binary values, etc.
*/
@Suppress("unused")
@Serializable
class ByteChunk(val data: UByteArray): Comparable<ByteChunk> {
val size: Int get() = data.size
/**
* Per-byte comparison also of different length. From two chunks
* of different size but equal beginning, the shorter is considered
* the smaller.
*/
override fun compareTo(other: ByteChunk): Int {
val limit = min(size, other.size)
for( i in 0 ..< limit) {
val own = data[i]
val their = other.data[i]
if( own < their) return -1
else if( own > their) return 1
}
if( size < other.size ) return -1
if( size > other.size ) return 1
return 0
}
/**
* Equal chunks means content equality.
*/
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is ByteChunk) return false
return data contentEquals other.data
}
/**
* Content-based hash code
*/
override fun hashCode(): Int {
return data.contentHashCode()
}
/**
* hex representation of data
*/
override fun toString(): String = hex
/**
* Hex encoded data
*/
val hex by lazy { data.encodeToHex() }
/**
* human-readable dump
*/
val dump by lazy { data.toDump() }
/**
* Concatenate two chunks and return new one
*/
operator fun plus(other: ByteChunk): ByteChunk = ByteChunk(data + other.data)
companion object {
fun fromHex(hex: String): ByteChunk = ByteChunk(hex.decodeHex().asUByteArray())
fun random(size: Int=16): ByteChunk = Random.nextBytes(size).asChunk()
}
}
fun ByteArray.asChunk() = ByteChunk(this.asUByteArray())
@Suppress("unused")
fun UByteArray.asChunk() = ByteChunk(this)

View File

@ -2,11 +2,18 @@ package net.sergeych.bintools
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
import net.sergeych.mp_logger.LogTag
import net.sergeych.mp_logger.Loggable
import net.sergeych.mp_logger.debug
import net.sergeych.synctools.AtomicCounter
import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.WaitHandle
import net.sergeych.synctools.withLock
class DataKVStorage(private val provider: DataProvider) : KVStorage {
private val ac = AtomicCounter(0)
class DataKVStorage(private val provider: DataProvider) : KVStorage,
Loggable by LogTag("DKVS${ac.incrementAndGet()}") {
data class Lock(val name: String) {
private val exclusive = ProtectedOp()
@ -20,8 +27,6 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage {
exclusive.withLock {
if (readerCount == 0) {
return f()
} else {
println("can't lock $this: count is $readerCount")
}
}
pulses.await()
@ -49,19 +54,16 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage {
access.withLock {
// TODO: read keys
for (fn in provider.list()) {
println("Scanning: $fn")
debug { "Scanning: $fn" }
if (fn.endsWith(".d")) {
val id = fn.dropLast(2).toInt(16)
println("found data record: $fn -> $id")
debug { "found data record: $fn -> $id" }
val name = provider.read(fn) { BipackDecoder.decode<String>(it) }
println("Key=$name")
keyIds[name] = id
if (id > lastId) lastId = id
} else println("ignoring record $fn")
} else debug { "ignoring record $fn" }
}
}
println("initialized, ${keyIds.size} records found, lastId=$lastId")
}
@ -97,7 +99,7 @@ class DataKVStorage(private val provider: DataProvider) : KVStorage {
lock.lockExclusive { provider.write(recordName(id), f) }
}
fun deleteEntry(name: String) {
private fun deleteEntry(name: String) {
// fast pre-check:
if (name !in keyIds) return
// global lock: we can't now detect concurrent delete + write ops, so exclusive:

View File

@ -119,7 +119,6 @@ class KVStorageDelegate<T>(
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (cacheReady) return cachedValue
val data = storage.get(name(property))
println("Got data: ${data?.toDump()}")
if (data == null)
cachedValue = defaultValue
else
@ -132,7 +131,6 @@ class KVStorageDelegate<T>(
// if (!cacheReady || value != cachedValue) {
cachedValue = value
cacheReady = true
println("set ${name(property)} to ${BipackEncoder.encode(serializer, value).toDump()}")
storage[name(property)] = BipackEncoder.encode(serializer, value)
// }
}

View File

@ -113,6 +113,8 @@ fun Collection<Byte>.encodeToHex(separator: String = " "): String = joinToString
fun ByteArray.toDump(wide: Boolean = false): String = toDumpLines(wide).joinToString("\n")
fun UByteArray.toDump(wide: Boolean = false): String = asByteArray().toDumpLines(wide).joinToString("\n")
fun ByteArray.toDumpLines(wide: Boolean = false): List<String> {
val lineSize = if (wide) 32 else 16

View File

@ -30,6 +30,7 @@ data class Foobar2(val bar: Int, val foo: Int, val other: Int = -1)
@Framed
data class FoobarF1(val bar: Int, val foo: Int = 117)
@Suppress("unused")
@Serializable
@Framed
@SerialName("bipack.FoobarF1")
@ -440,4 +441,21 @@ class BipackEncoderTest {
println(y)
}
@Serializable
data class T1(@Fixed val i: Byte)
@Test
fun testFixedByte() {
fun t1(i: Int) {
val packed = BipackEncoder.encode(T1(i.toByte()))
println(packed.toDump())
assertEquals(1, packed.size)
assertEquals(i, BipackDecoder.decode<T1>(packed).i.toInt())
}
t1(127)
t1(-127)
t1(1)
t1(-1)
}
}

View File

@ -0,0 +1,34 @@
@file:Suppress("unused")
package net.sergeych.synctools
import java.nio.channels.CompletionHandler
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
/**
* Helper class to handle Java continuation with Kotlin coroutines.
* Usage sample:
* ```kotlin
* val socket = withContext(Dispatchers.IO) {
* AsynchronousSocketChannel.open()
* }
* suspendCoroutine { cont ->
* socket.connect(address.socketAddress, cont, VoidCompletionHandler)
* }
* ```
*/
open class ContinuationHandler<T> : CompletionHandler<T, Continuation<T>> {
override fun completed(result: T, attachment: Continuation<T>) {
attachment.resume(result)
}
override fun failed(exc: Throwable, attachment: Continuation<T>) {
attachment.resumeWithException(exc)
}
}
object VoidCompletionHandler : ContinuationHandler<Void>()
object IntCompletionHandler : ContinuationHandler<Int>()

View File

@ -1,9 +0,0 @@
import kotlin.test.Test
class EmptyTest {
@Test
fun emptyTest() {
println("hello!")
}
}

View File

@ -5,10 +5,6 @@ import kotlin.test.assertNull
import kotlin.test.assertTrue
class StorageTest {
@Test
fun emptyTest() {
println("hello")
}
@Test
fun storageTest() {
@ -27,7 +23,6 @@ class StorageTest {
assertEquals(answer, 42)
answer = 43
println("----------------------------------------------------------------")
val s2 = defaultNamedStorage("test_mp_bintools")
val foo1 by s2.stored("?", "foo")
val answer1: Int? by s2.optStored("answer")