164 lines
4.8 KiB
Kotlin
164 lines
4.8 KiB
Kotlin
package net.sergeych.parsec3
|
|
|
|
import net.sergeych.boss_serialization.BossDecoder
|
|
import net.sergeych.boss_serialization_mp.BossEncoder
|
|
import net.sergeych.mp_logger.LogTag
|
|
import net.sergeych.mp_logger.exception
|
|
import net.sergeych.mp_logger.info
|
|
import net.sergeych.mptools.encodeToHex
|
|
import net.sergeych.mptools.toDump
|
|
import kotlin.reflect.KProperty
|
|
import kotlin.reflect.KType
|
|
import kotlin.reflect.typeOf
|
|
|
|
|
|
/**
|
|
* Generic storage of binary content. PArsec uses boss encoding to store everything in it
|
|
* in te convenient way. See [KVStorage.stored] delegate. The [MemoryKVStorage] allows to
|
|
* store values in-memory and then connect some permanent stprage to it in a transparent way.
|
|
*/
|
|
interface KVStorage {
|
|
operator fun get(key: String): ByteArray?
|
|
operator fun set(key: String, value: ByteArray?)
|
|
|
|
/**
|
|
* Check whether key is in storage.
|
|
* Default implementation uses [keys]. You may override it for performance
|
|
*/
|
|
operator fun contains(key: String): Boolean = key in keys
|
|
|
|
val keys: Set<String>
|
|
|
|
|
|
/**
|
|
* Get number of object in the storage
|
|
* Default implementation uses [keys]. You may override it for performance
|
|
*/
|
|
val size: Int get() = keys.size
|
|
|
|
/**
|
|
* Clears all objects in the storage
|
|
* Default implementation uses [keys]. You may override it for performance
|
|
*/
|
|
fun clear() {
|
|
for (k in keys) this[k] = null
|
|
}
|
|
|
|
/**
|
|
* Default implementation uses [keys]. You may override it for performance
|
|
*/
|
|
fun isEmpty() = size == 0
|
|
|
|
/**
|
|
* Default implementation uses [keys]. You may override it for performance
|
|
*/
|
|
fun isNotEmpty() = size != 0
|
|
|
|
fun addAll(other: KVStorage) {
|
|
for (k in other.keys) {
|
|
this[k] = other[k]
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
inline operator fun <reified T> KVStorage.invoke(defaultValue: T, overrideName: String? = null) =
|
|
KVStorageDelegate<T>(this, typeOf<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> KVStorage.optStored(defaultValue: T? = null, overrideName: String? = null) =
|
|
KVStorageDelegate<T?>(this, typeOf<T>(), defaultValue, overrideName)
|
|
|
|
class KVStorageDelegate<T>(
|
|
private val storage: KVStorage,
|
|
private val type: KType,
|
|
private val defaultValue: T,
|
|
private val overrideName: String? = null,
|
|
): LogTag("KVSD") {
|
|
|
|
private fun name(property: KProperty<*>): String = overrideName ?: property.name
|
|
|
|
private var cachedValue: T = defaultValue
|
|
private var cacheReady = false
|
|
|
|
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 = try {
|
|
info { " get ${name(property)}: ${type} [{${data.encodeToHex()}" }
|
|
BossDecoder.decodeFrom<T>(type, data)
|
|
.also { println("decoded as $it") }
|
|
} catch (e: Exception) {
|
|
exception { "failed to decode ${name(property)}: ${type}" to e }
|
|
defaultValue
|
|
}
|
|
cacheReady = true
|
|
return cachedValue
|
|
}
|
|
|
|
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
|
// if (!cacheReady || value != cachedValue) {
|
|
cachedValue = value
|
|
cacheReady = true
|
|
println("set ${name(property)} to ${BossEncoder.encode(type, value).toDump()}")
|
|
storage[name(property)] = BossEncoder.encode(type, value)
|
|
// }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Memory storage allows to connect existing in-memory content to some permanent storage on the fly
|
|
*/
|
|
class MemoryKVStorage(copyFrom: KVStorage? = null) : KVStorage {
|
|
|
|
// is used when connected:
|
|
private var underlying: KVStorage? = null
|
|
|
|
// is used while underlying is null:
|
|
private val data = mutableMapOf<String, ByteArray>()
|
|
|
|
@Suppress("unused")
|
|
fun connectToStorage(other: KVStorage) {
|
|
other.addAll(this)
|
|
underlying = other
|
|
data.clear()
|
|
}
|
|
|
|
|
|
override fun get(key: String): ByteArray? {
|
|
underlying?.let {
|
|
return it[key]
|
|
}
|
|
return data[key]
|
|
}
|
|
|
|
override fun set(key: String, value: ByteArray?) {
|
|
underlying?.let { it[key] = value } ?: run {
|
|
if (value != null) data[key] = value
|
|
else data.remove(key)
|
|
}
|
|
}
|
|
|
|
override fun contains(key: String): Boolean {
|
|
underlying?.let { return key in it }
|
|
return key in data
|
|
}
|
|
|
|
override val keys: Set<String>
|
|
get() = underlying?.keys ?: data.keys
|
|
|
|
override fun clear() {
|
|
underlying?.clear() ?: data.clear()
|
|
}
|
|
|
|
init {
|
|
copyFrom?.let { addAll(it) }
|
|
}
|
|
}
|
|
|
|
expect fun defaultNamedStorage(name: String): KVStorage |