- 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 { plugins {
kotlin("multiplatform") version "2.0.20" kotlin("multiplatform") version "2.0.21"
kotlin("plugin.serialization") version "2.0.20" kotlin("plugin.serialization") version "2.0.21"
id("org.jetbrains.dokka") version "1.9.20" id("org.jetbrains.dokka") version "1.9.20"
`maven-publish` `maven-publish`
} }
val serialization_version = "1.6.5-SNAPSHOT"
group = "net.sergeych" group = "net.sergeych"
version = "0.1.8-SNAPSHOT" version = "0.1.9-SNAPSHOT"
repositories { repositories {
mavenCentral() mavenCentral()
@ -24,11 +22,11 @@ kotlin {
nodejs() nodejs()
} }
macosArm64() // macosArm64()
iosX64() // iosX64()
iosArm64() // iosArm64()
macosX64() // macosX64()
iosSimulatorArm64() // iosSimulatorArm64()
linuxX64() linuxX64()
linuxArm64() linuxArm64()
mingwX64() mingwX64()
@ -55,7 +53,7 @@ kotlin {
dependencies { dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") 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 // 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,)") api("net.sergeych:mp_stools:[1.4.7,)")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0") implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0")
} }

View File

@ -1,11 +1,10 @@
package net.sergeych.bintools package net.sergeych.bintools
import kotlinx.serialization.KSerializer
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import net.sergeych.bipack.BipackDecoder import net.sergeych.bipack.BipackDecoder
import net.sergeych.bipack.BipackEncoder import net.sergeych.bipack.BipackEncoder
import kotlin.reflect.KProperty 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 * Default implementation uses [keys]. You may override it for performance
*/ */
fun clear() { 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") @Suppress("unused")
inline fun <reified T:Any>KVStorage.load(key: String): T? = read(key) inline fun <reified T:Any>KVStorage.load(key: String): T? = read(key)
inline operator fun <reified T> KVStorage.invoke(defaultValue: T,overrideName: String? = null) = inline operator fun <reified T: Any> KVStorage.invoke(defaultValue: T,overrideName: String? = null) =
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName) KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
inline fun <reified T> KVStorage.stored(defaultValue: T, overrideName: String? = null) = inline fun <reified T: Any> KVStorage.stored(defaultValue: T, overrideName: String? = null) =
KVStorageDelegate<T>(this, typeOf<T>(), defaultValue, overrideName) KVStorageDelegate(this, serializer<T>(), defaultValue, overrideName)
inline fun <reified T> KVStorage.optStored(overrideName: String? = null) = inline fun <reified T: Any> KVStorage.optStored(overrideName: String? = null) =
KVStorageDelegate<T?>(this, typeOf<T?>(), null, overrideName) KVStorageOptDelegate<T>(this, serializer<T>(),overrideName)
class KVStorageDelegate<T>( class KVStorageDelegate<T: Any>(
private val storage: KVStorage, private val storage: KVStorage,
type: KType, private val serializer: KSerializer<T>,
private val defaultValue: T, private val defaultValue: T,
private val overrideName: String? = null, private val overrideName: String? = null,
) { ) {
private fun name(property: KProperty<*>): String = overrideName ?: property.name private fun name(property: KProperty<*>): String = overrideName ?: property.name
private var cachedValue: T = defaultValue operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
private var cacheReady = false storage.get(name(property))?.let { BipackDecoder.decode(serializer, it) }
private val serializer = serializer(type) ?: 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) { operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
// if (!cacheReady || value != cachedValue) {
cachedValue = value
cacheReady = true
storage[name(property)] = BipackEncoder.encode(serializer, value) 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") s2.write("test_$i", "payload_$i")
} }
} }
} }