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 /** * 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 KVStorage.invoke(defaultValue: T, overrideName: String? = null) = KVStorageDelegate(this, typeOf(), defaultValue, overrideName) inline fun KVStorage.stored(defaultValue: T, overrideName: String? = null) = KVStorageDelegate(this, typeOf(), defaultValue, overrideName) inline fun KVStorage.optStored(defaultValue: T? = null, overrideName: String? = null) = KVStorageDelegate(this, typeOf(), defaultValue, overrideName) class KVStorageDelegate( 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(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() @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 get() = underlying?.keys ?: data.keys override fun clear() { underlying?.clear() ?: data.clear() } init { copyFrom?.let { addAll(it) } } } expect fun defaultNamedStorage(name: String): KVStorage