From 497782b4282ac606fcc0abf9be52fec673bb0a12 Mon Sep 17 00:00:00 2001 From: minjaesong Date: Sun, 9 Feb 2025 14:58:06 +0900 Subject: [PATCH] integration of blob and actorvalue --- src/net/torvald/terrarum/KVHashMap.kt | 34 +---- .../torvald/terrarum/gameactors/ActorValue.kt | 118 +++++++++++++++++- 2 files changed, 118 insertions(+), 34 deletions(-) diff --git a/src/net/torvald/terrarum/KVHashMap.kt b/src/net/torvald/terrarum/KVHashMap.kt index 5d0c146fb..cb6e46bac 100644 --- a/src/net/torvald/terrarum/KVHashMap.kt +++ b/src/net/torvald/terrarum/KVHashMap.kt @@ -27,6 +27,7 @@ open class KVHashMap { var hashMap: HashMap + /** * Add key-value pair to the configuration table. * If key does not exist on the table, new key will be generated. @@ -37,18 +38,7 @@ open class KVHashMap { * @param value */ open operator fun set(key: String, value: Any) { - hashMap.put(key.toLowerCase(), value) - } - - fun setBlob(key: String, value: ByteArray) { - val filename0 = hashMap.getOrPut(key.toLowerCase()) { "blob://${UUID.randomUUID()}" } as String - if (filename0.startsWith("blob://")) { - Terrarum.getSharedSaveFiledesc(filename0.removePrefix("blob://")).let { - if (!it.exists()) it.createNewFile() - it.writeBytes(value) // will overwrite whatever's there - } - } - else throw TypeCastException("ActorValue is not blob") + hashMap[key.toLowerCase()] = value } /** @@ -89,19 +79,6 @@ open class KVHashMap { return value as Boolean } - fun getAsBlob(key: String): ByteArray? { - val uri = getAsString(key) ?: return null - if (uri.startsWith("blob://")) { - val filename = uri.removePrefix("blob://") - val file = Terrarum.getSharedSaveFiledesc(filename) - if (file.exists()) - return file.readBytes() - else - throw IOException("Blob not found") - } - else throw TypeCastException("ActorValue is not blob") - } - fun hasKey(key: String) = hashMap.containsKey(key) val keySet: Set @@ -111,13 +88,6 @@ open class KVHashMap { if (hashMap[key] != null) { val value = hashMap[key]!! hashMap.remove(key, value) - - (value as? String)?.let { - if (it.startsWith("blob://")) { - val filename = it.removePrefix("blob://") - Terrarum.getSharedSaveFiledesc(filename).delete() - } - } } } diff --git a/src/net/torvald/terrarum/gameactors/ActorValue.kt b/src/net/torvald/terrarum/gameactors/ActorValue.kt index f140c8b75..3e970fdc7 100644 --- a/src/net/torvald/terrarum/gameactors/ActorValue.kt +++ b/src/net/torvald/terrarum/gameactors/ActorValue.kt @@ -1,6 +1,14 @@ package net.torvald.terrarum.gameactors +import net.torvald.terrarum.App +import net.torvald.terrarum.INGAME import net.torvald.terrarum.KVHashMap +import net.torvald.terrarum.Terrarum +import net.torvald.terrarum.modulebasegame.gameactors.IngamePlayer +import net.torvald.terrarum.savegame.* +import java.io.IOException +import java.util.* +import kotlin.collections.HashMap /** * For the dictionary of recognised ActorValues, see the source code of [net.torvald.terrarum.gameactors.AVKey] @@ -12,6 +20,8 @@ class ActorValue : KVHashMap { @Transient lateinit var actor: Actor internal set + @Transient private val BLOB = "blob://" // typical filename will be like "blob://-2147483648" + constructor() constructor(actor: Actor) : this() { @@ -24,18 +34,122 @@ class ActorValue : KVHashMap { } override fun set(key: String, value: Any) { - super.set(key, value) + // check if the key exists and is a blob + if (getAsString(key.toLowerCase())?.startsWith(BLOB) == true) { + throw IllegalStateException("Cannot write plain values to the blob object") + } + else + super.set(key, value) + actor.onActorValueChange(key, value) // fire the event handler } + fun setBlob(key: String, ref: Long, value: ByteArray64) { + checkActorBlobRef(ref) + + // if setBlob is invoked on non-existing key, adds random empty blob to the key + // filename0 will be whatever value stored on the actorvalue, cast to String + val preexisted = hashMap.containsKey(key.toLowerCase()) + + val filename0 = hashMap.getOrPut(key.toLowerCase()) { "$BLOB$ref" } as String + if (filename0.startsWith(BLOB)) { + writeBlobBin(ref, value) + } + else { + // rollback key addition, if applicable + if (preexisted) hashMap.remove(key.toLowerCase()) + throw TypeCastException("ActorValue is not blob") + } + } + + fun getAsBlob(key: String): ByteArray64? { + val uri = getAsString(key) ?: return null + if (uri.startsWith(BLOB)) { + return readBlobBin(uri.removePrefix(BLOB).toLong()) + } + else throw TypeCastException("ActorValue is not blob") + } + override fun remove(key: String) { if (hashMap[key] != null) { - hashMap.remove(key, hashMap[key]!!) + val value = hashMap[key]!! + hashMap.remove(key, value) + + // remove blob + (value as? String)?.let { + if (it.startsWith(BLOB)) { + deleteBlobBin(it.removePrefix(BLOB).toLong()) + } + } + actor.onActorValueChange(key, null) } } fun clone(newActor: Actor): ActorValue { return ActorValue(newActor, hashMap) + // TODO clone blobs } + + private fun readBlobBin(ref: Long): ByteArray64? { + checkActorBlobRef(ref) + + if (INGAME.actorNowPlaying == actor) { + val disk = INGAME.playerDisk + return disk.getFile(ref)?.bytes + } + else if (actor is IngamePlayer) { + val diskSkimmer = DiskSkimmer(Terrarum.getPlayerSaveFiledesc((actor as IngamePlayer).uuid.toString())) + return diskSkimmer.getFile(ref)?.bytes + } + else throw IllegalStateException("Actor is not a player") + } + + private fun writeBlobBin(ref: Long, contents: ByteArray64) { + checkActorBlobRef(ref) + + val time = App.getTIME_T() + val file = EntryFile(contents) + + if (INGAME.actorNowPlaying == actor) { + val disk = INGAME.playerDisk + + disk.getFile(ref).let { + // create new file if one not exists, then pass it + if (it == null) { + VDUtil.addFile(disk, DiskEntry(ref, 0, time, time, file)) + file + } + else it + }.bytes = contents + + disk.getEntry(ref)!!.modificationDate = time + } + else if (actor is IngamePlayer) { + val diskSkimmer = DiskSkimmer(Terrarum.getPlayerSaveFiledesc((actor as IngamePlayer).uuid.toString())) + val oldCreationDate = diskSkimmer.getEntry(ref)?.creationDate + + diskSkimmer.appendEntry(DiskEntry(ref, 0, oldCreationDate ?: time, time, file)) + } + else throw IllegalStateException("Actor is not a player") + } + + private fun deleteBlobBin(ref: Long) { + checkActorBlobRef(ref) + + if (INGAME.actorNowPlaying == actor) { + val disk = INGAME.playerDisk + VDUtil.deleteFile(disk, ref) + } + else if (actor is IngamePlayer) { + val diskSkimmer = DiskSkimmer(Terrarum.getPlayerSaveFiledesc((actor as IngamePlayer).uuid.toString())) + diskSkimmer.deleteEntry(ref) + } + else throw IllegalStateException("Actor is not a player") + } + + private fun checkActorBlobRef(ref: Long) { + if (ref > -2147483648L) throw IllegalArgumentException("File Ref $ref is not a valid ActorValue blob") + } + }