- no caching on stored delegates, fix problens with memory storage connections

+ optStored now properly load/store only non-null values
* possible concurrent modification in KVStorage.clear fixed
This commit is contained in:
Sergey Chernov 2024-11-30 13:37:20 +07:00
parent 2eb38e27ed
commit d29bf960aa
4 changed files with 86 additions and 42 deletions

View File

@ -1,14 +1,12 @@
plugins {
kotlin("multiplatform") version "2.0.20"
kotlin("plugin.serialization") version "2.0.20"
kotlin("multiplatform") version "2.0.21"
kotlin("plugin.serialization") version "2.0.21"
id("org.jetbrains.dokka") version "1.9.20"
`maven-publish`
}
val serialization_version = "1.6.5-SNAPSHOT"
group = "net.sergeych"
version = "0.1.8-SNAPSHOT"
version = "0.1.9-SNAPSHOT"
repositories {
mavenCentral()
@ -24,11 +22,11 @@ kotlin {
nodejs()
}
macosArm64()
iosX64()
iosArm64()
macosX64()
iosSimulatorArm64()
// macosArm64()
// iosX64()
// iosArm64()
// macosX64()
// iosSimulatorArm64()
linuxX64()
linuxArm64()
mingwX64()
@ -55,7 +53,7 @@ kotlin {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
// this is actually a bug: we need only the core, but bare core causes strange errors
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
api("net.sergeych:mp_stools:[1.4.7,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
}

View File

@ -1,11 +1,10 @@
package net.sergeych.bintools
import kotlinx.serialization.KSerializer
import kotlinx.serialization.serializer
import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.typeOf
/**
@ -43,7 +42,7 @@ interface KVStorage {
* Default implementation uses [keys]. You may override it for performance
*/
fun clear() {
for (k in keys) this[k] = null
for (k in keys.toList()) this[k] = null
}
/**
@ -93,46 +92,50 @@ inline fun <reified T:Any>KVStorage.read(key: String): T? =
@Suppress("unused")
inline fun <reified T:Any>KVStorage.load(key: String): T? = read(key)
inline operator fun <reified T> KVStorage.invoke(defaultValue: T,overrideName: String? = null) =
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName)
inline operator fun <reified T: Any> KVStorage.invoke(defaultValue: T,overrideName: String? = null) =
KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
inline fun <reified T> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName)
inline fun <reified T: Any> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
inline fun <reified T> KVStorage.optStored(overrideName: String? = null) =
KVStorageDelegate<T?>(this, typeOf<T?>(), null, overrideName)
inline fun <reified T: Any> KVStorage.optStored(overrideName: String? = null) =
KVStorageOptDelegate<T>(this, serializer<T>(),overrideName)
class KVStorageDelegate<T>(
class KVStorageDelegate<T: Any>(
private val storage: KVStorage,
type: KType,
private val serializer: KSerializer<T>,
private val defaultValue: T,
private val overrideName: String? = null,
) {
private fun name(property: KProperty<*>): String = overrideName ?: property.name
private var cachedValue: T = defaultValue
private var cacheReady = false
private val serializer = serializer(type)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
storage.get(name(property))?.let { BipackDecoder.decode(serializer, it) }
?: defaultValue
@Suppress("UNCHECKED_CAST")
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (cacheReady) return cachedValue
val data = storage.get(name(property))
if (data == null)
cachedValue = defaultValue
else
cachedValue = BipackDecoder.decode(data.toDataSource(), serializer) as T
cacheReady = true
return cachedValue
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
// if (!cacheReady || value != cachedValue) {
cachedValue = value
cacheReady = true
storage[name(property)] = BipackEncoder.encode(serializer, value)
}
}
class KVStorageOptDelegate<T: Any>(
private val storage: KVStorage,
private val serializer: KSerializer<T>,
private val overrideName: String? = null,
) {
private fun name(property: KProperty<*>): String = overrideName ?: property.name
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
storage.get(name(property))?.let{
BipackDecoder.decode(serializer, it)
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
if (value == null)
storage.delete(name(property))
else
storage[name(property)] = BipackEncoder.encode(serializer, value)
// }
}
}

View File

@ -0,0 +1,44 @@
package bipack
import net.sergeych.bintools.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class StorageTest {
@Test
fun storageTest3() {
val s1 = MemoryKVStorage()
val s2 = defaultNamedStorage("test_mp_bintools2")
s2.clear()
s1.write("foo", "bar")
s1.write("foo2", "bar2")
s2.write("foo", "foobar")
s2.write("bar", "buzz")
s2.write("reason", 42)
assertEquals("bar", s1.read("foo"))
assertEquals("bar2", s1.read("foo2"))
assertNull(s1.get("bar"))
val reason: Int? by s1.optStored()
assertNull(s1.get("reason"))
assertNull(reason)
s1.connectToStorage(s2)
// s1 overwrites s2!
assertEquals("bar", s1.read("foo"))
// don't change
assertEquals("bar2", s1.read("foo2"))
// pull from s1
assertEquals("buzz", s1.read("bar"))
assertEquals(42, s1.read("reason"))
assertEquals(42, reason)
}
}

View File

@ -34,5 +34,4 @@ class StorageTest {
s2.write("test_$i", "payload_$i")
}
}
}