mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-11 11:04:05 +09:00
tile damage and wire layers are now save/loaded
This commit is contained in:
267
src/net/torvald/terrarum/serialise/Common.kt
Normal file
267
src/net/torvald/terrarum/serialise/Common.kt
Normal file
@@ -0,0 +1,267 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.badlogic.gdx.utils.JsonWriter
|
||||
import net.torvald.terrarum.AppLoader
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.gameworld.BlockLayer
|
||||
import net.torvald.terrarum.gameworld.GameWorld
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
|
||||
import net.torvald.terrarum.utils.*
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import java.math.BigInteger
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2021-08-26.
|
||||
*/
|
||||
object Common {
|
||||
/** dispose of the `offendingObject` after rejection! */
|
||||
class BlockLayerHashMismatchError(val oldHash: String, val newHash: String, val offendingObject: BlockLayer) : Error("Old Hash $oldHash != New Hash $newHash")
|
||||
|
||||
private fun Byte.tostr() = this.toInt().and(255).toString(16).padStart(2,'0')
|
||||
private val digester = DigestUtils.getSha256Digest()
|
||||
|
||||
val jsoner = Json(JsonWriter.OutputType.json)
|
||||
|
||||
// install custom (de)serialiser
|
||||
init {
|
||||
// BigInteger
|
||||
jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> {
|
||||
|
||||
override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) {
|
||||
json.writeValue(obj?.toString())
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): BigInteger {
|
||||
return BigInteger(jsonData.asString())
|
||||
}
|
||||
})
|
||||
// BlockLayer
|
||||
jsoner.setSerializer(BlockLayer::class.java, object : Json.Serializer<BlockLayer> {
|
||||
|
||||
override fun write(json: Json, obj: BlockLayer, knownType: Class<*>?) {
|
||||
digester.reset()
|
||||
obj.bytesIterator().forEachRemaining { digester.update(it) }
|
||||
val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
|
||||
|
||||
val layer = LayerInfo(hash, blockLayerToStr(obj), obj.width, obj.height)
|
||||
|
||||
AppLoader.printdbg(this, "pre: ${(0L..1023L).map { obj.ptr[it].tostr() }.joinToString(" ")}")
|
||||
|
||||
|
||||
json.writeValue(layer)
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>): BlockLayer {
|
||||
// full auto
|
||||
//return strToBlockLayer(json.fromJson(type, jsonData.toJson(JsonWriter.OutputType.minimal)) as LayerInfo)
|
||||
|
||||
// full manual
|
||||
try {
|
||||
return strToBlockLayer(LayerInfo(
|
||||
jsonData.getString("h"),
|
||||
jsonData.getString("b"),
|
||||
jsonData.getInt("x"),
|
||||
jsonData.getInt("y")
|
||||
))
|
||||
}
|
||||
catch (e: BlockLayerHashMismatchError) {
|
||||
EchoError(e.message ?: "")
|
||||
return e.offendingObject
|
||||
}
|
||||
}
|
||||
})
|
||||
// WorldTime
|
||||
jsoner.setSerializer(WorldTime::class.java, object : Json.Serializer<WorldTime> {
|
||||
override fun write(json: Json, obj: WorldTime, knownType: Class<*>?) {
|
||||
json.writeValue(obj.TIME_T)
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WorldTime {
|
||||
return WorldTime(jsonData.asLong())
|
||||
}
|
||||
})
|
||||
// HashArray
|
||||
jsoner.setSerializer(HashArray::class.java, object : Json.Serializer<HashArray<*>> {
|
||||
override fun write(json: Json, obj: HashArray<*>, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
obj.forEach { (k, v) ->
|
||||
json.writeValue(k.toString(), v)
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashArray<*> {
|
||||
val hashMap = HashArray<Any>()
|
||||
printdbg(type?.canonicalName, "deserialising '$jsonData'")
|
||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
||||
hashMap[key.toLong()] = json.readValue(null, obj)
|
||||
printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}")
|
||||
}
|
||||
return hashMap
|
||||
}
|
||||
})
|
||||
// HashedWirings
|
||||
jsoner.setSerializer(HashedWirings::class.java, object : Json.Serializer<HashedWirings> {
|
||||
override fun write(json: Json, obj: HashedWirings, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
obj.forEach { (k, v) ->
|
||||
json.writeValue(k.toString(), v)
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWirings {
|
||||
val hashMap = HashedWirings()
|
||||
printdbg(type?.canonicalName, "deserialising '$jsonData'")
|
||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
||||
hashMap[key.toLong()] = json.readValue(GameWorld.WiringNode::class.java, obj)
|
||||
printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}")
|
||||
}
|
||||
return hashMap
|
||||
}
|
||||
})
|
||||
// HashedWiringGraph
|
||||
jsoner.setSerializer(HashedWiringGraph::class.java, object : Json.Serializer<HashedWiringGraph> {
|
||||
override fun write(json: Json, obj: HashedWiringGraph, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
obj.forEach { (k, v) ->
|
||||
json.writeValue(k.toString(), v, WiringGraphMap::class.java)
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): HashedWiringGraph {
|
||||
val hashMap = HashedWiringGraph()
|
||||
printdbg(type?.canonicalName, "deserialising '$jsonData'")
|
||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
||||
hashMap[key.toLong()] = json.readValue(WiringGraphMap::class.java, obj)
|
||||
printdbg(this, "key: $key, value: ${hashMap[key.toLong()]}")
|
||||
}
|
||||
return hashMap
|
||||
}
|
||||
})
|
||||
// WiringGraphMap; this serialiser is here just to reduce the JSON filesize
|
||||
jsoner.setSerializer(WiringGraphMap::class.java, object : Json.Serializer<WiringGraphMap> {
|
||||
override fun write(json: Json, obj: WiringGraphMap, knownType: Class<*>?) {
|
||||
json.writeObjectStart()
|
||||
obj.forEach { (k, v) ->
|
||||
json.writeValue(k, v, GameWorld.WiringSimCell::class.java)
|
||||
}
|
||||
json.writeObjectEnd()
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WiringGraphMap {
|
||||
val hashMap = WiringGraphMap()
|
||||
printdbg(type?.canonicalName, "deserialising '$jsonData'")
|
||||
JsonFetcher.forEach(jsonData) { key, obj ->
|
||||
hashMap[key] = json.readValue(GameWorld.WiringSimCell::class.java, obj)
|
||||
printdbg(this, "key: $key, value: ${hashMap[key]}")
|
||||
}
|
||||
return hashMap
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private data class LayerInfo(val h: String, val b: String, val x: Int, val y: Int)
|
||||
|
||||
/**
|
||||
* @param b a BlockLayer
|
||||
* @return Bytes in [b] which are GZip'd then Ascii85-encoded
|
||||
*/
|
||||
private fun blockLayerToStr(b: BlockLayer): String {
|
||||
val sb = StringBuilder()
|
||||
val bo = ByteArray64GrowableOutputStream()
|
||||
val zo = GZIPOutputStream(bo)
|
||||
|
||||
// zip
|
||||
/*b.bytesIterator().forEachRemaining {
|
||||
zo.write(it.toInt())
|
||||
}
|
||||
zo.flush(); zo.close()*/
|
||||
|
||||
// enascii
|
||||
val ba = bo.toByteArray64()
|
||||
var bai = 0
|
||||
val buf = IntArray(4) { Ascii85.PAD_BYTE }
|
||||
// ba.forEach {
|
||||
b.bytesIterator().forEachRemaining {
|
||||
if (bai > 0 && bai % 4 == 0) {
|
||||
sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
|
||||
buf.fill(Ascii85.PAD_BYTE)
|
||||
}
|
||||
|
||||
buf[bai % 4] = it.toInt() and 255
|
||||
|
||||
bai += 1
|
||||
}; sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun strToBlockLayer(layerInfo: LayerInfo): BlockLayer {
|
||||
val layer = BlockLayer(layerInfo.x, layerInfo.y)
|
||||
val unasciidBytes = ByteArray64()
|
||||
val unzipdBytes = ByteArray64()
|
||||
|
||||
// unascii
|
||||
var bai = 0
|
||||
val buf = CharArray(5) { Ascii85.PAD_CHAR }
|
||||
layerInfo.b.forEach {
|
||||
if (bai > 0 && bai % 5 == 0) {
|
||||
Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) }
|
||||
buf.fill(Ascii85.PAD_CHAR)
|
||||
}
|
||||
|
||||
buf[bai % 5] = it
|
||||
|
||||
bai += 1
|
||||
}; Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) }
|
||||
|
||||
// unzip
|
||||
/*val zi = GZIPInputStream(ByteArray64InputStream(unasciidBytes))
|
||||
while (true) {
|
||||
val byte = zi.read()
|
||||
if (byte == -1) break
|
||||
unzipdBytes.add(byte.toByte())
|
||||
}
|
||||
zi.close()*/
|
||||
|
||||
// write to blocklayer and the digester
|
||||
digester.reset()
|
||||
var writeCursor = 0L
|
||||
val sb = StringBuilder()
|
||||
// unzipdBytes.forEach {
|
||||
unasciidBytes.forEach {
|
||||
if (writeCursor < layer.ptr.size) {
|
||||
|
||||
if (writeCursor < 1024) {
|
||||
sb.append("${it.tostr()} ")
|
||||
}
|
||||
|
||||
|
||||
layer.ptr[writeCursor] = it
|
||||
digester.update(it)
|
||||
writeCursor += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
AppLoader.printdbg(this, "post: $sb")
|
||||
|
||||
|
||||
// check hash
|
||||
val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
|
||||
|
||||
if (hash != layerInfo.h) {
|
||||
throw BlockLayerHashMismatchError(layerInfo.h, hash, layer)
|
||||
}
|
||||
|
||||
return layer
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import java.io.Reader
|
||||
open class ReadWorld(val ingame: TerrarumIngame) {
|
||||
|
||||
open fun invoke(worldDataStream: InputStream) {
|
||||
postRead(WriteWorld.jsoner.fromJson(GameWorld::class.java, worldDataStream))
|
||||
postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream))
|
||||
}
|
||||
|
||||
open fun invoke(worldDataStream: Reader) {
|
||||
postRead(WriteWorld.jsoner.fromJson(GameWorld::class.java, worldDataStream))
|
||||
postRead(Common.jsoner.fromJson(GameWorld::class.java, worldDataStream))
|
||||
}
|
||||
|
||||
private fun postRead(world: GameWorld) {
|
||||
|
||||
@@ -12,23 +12,8 @@ import java.math.BigInteger
|
||||
*/
|
||||
object WriteActor {
|
||||
|
||||
private val jsoner = Json(JsonWriter.OutputType.json)
|
||||
|
||||
// install custom (de)serialiser
|
||||
init {
|
||||
jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> {
|
||||
override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) {
|
||||
json.writeValue(obj?.toString())
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): BigInteger {
|
||||
return BigInteger(jsonData.asString())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
operator fun invoke(actor: Actor): String {
|
||||
return jsoner.toJson(actor)
|
||||
return Common.jsoner.toJson(actor)
|
||||
}
|
||||
|
||||
fun encodeToByteArray64(actor: Actor): ByteArray64 {
|
||||
|
||||
@@ -1,22 +1,7 @@
|
||||
package net.torvald.terrarum.serialise
|
||||
|
||||
import com.badlogic.gdx.utils.Json
|
||||
import com.badlogic.gdx.utils.JsonReader
|
||||
import com.badlogic.gdx.utils.JsonValue
|
||||
import com.badlogic.gdx.utils.JsonWriter
|
||||
import net.torvald.terrarum.AppLoader.printdbg
|
||||
import net.torvald.terrarum.console.EchoError
|
||||
import net.torvald.terrarum.gameworld.BlockLayer
|
||||
import net.torvald.terrarum.gameworld.WorldTime
|
||||
import net.torvald.terrarum.modulebasegame.TerrarumIngame
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64GrowableOutputStream
|
||||
import net.torvald.terrarum.modulecomputers.virtualcomputer.tvd.ByteArray64InputStream
|
||||
import net.torvald.terrarum.serialise.WriteWorld.Companion.tostr
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import java.math.BigInteger
|
||||
import java.util.zip.GZIPInputStream
|
||||
import java.util.zip.GZIPOutputStream
|
||||
|
||||
/**
|
||||
* Created by minjaesong on 2021-08-23.
|
||||
@@ -26,7 +11,7 @@ open class WriteWorld(val ingame: TerrarumIngame) {
|
||||
open fun invoke(): String {
|
||||
val world = ingame.world
|
||||
//return "{${world.getJsonFields().joinToString(",\n")}}"
|
||||
return jsoner.toJson(world)
|
||||
return Common.jsoner.toJson(world)
|
||||
}
|
||||
|
||||
fun encodeToByteArray64(): ByteArray64 {
|
||||
@@ -44,170 +29,4 @@ open class WriteWorld(val ingame: TerrarumIngame) {
|
||||
return ba
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** dispose of the `offendingObject` after rejection! */
|
||||
class BlockLayerHashMismatchError(val oldHash: String, val newHash: String, val offendingObject: BlockLayer) : Error("Old Hash $oldHash != New Hash $newHash")
|
||||
|
||||
private fun Byte.tostr() = this.toInt().and(255).toString(16).padStart(2,'0')
|
||||
private val digester = DigestUtils.getSha256Digest()
|
||||
|
||||
val jsoner = Json(JsonWriter.OutputType.json)
|
||||
|
||||
// install custom (de)serialiser
|
||||
init {
|
||||
// BigInteger
|
||||
jsoner.setSerializer(BigInteger::class.java, object : Json.Serializer<BigInteger> {
|
||||
|
||||
override fun write(json: Json, obj: BigInteger?, knownType: Class<*>?) {
|
||||
json.writeValue(obj?.toString())
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): BigInteger {
|
||||
return BigInteger(jsonData.asString())
|
||||
}
|
||||
})
|
||||
// BlockLayer
|
||||
jsoner.setSerializer(BlockLayer::class.java, object : Json.Serializer<BlockLayer> {
|
||||
|
||||
override fun write(json: Json, obj: BlockLayer, knownType: Class<*>?) {
|
||||
digester.reset()
|
||||
obj.bytesIterator().forEachRemaining { digester.update(it) }
|
||||
val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
|
||||
|
||||
val layer = LayerInfo(hash, blockLayerToStr(obj), obj.width, obj.height)
|
||||
|
||||
printdbg(this, "pre: ${(0L..1023L).map { obj.ptr[it].tostr() }.joinToString(" ")}")
|
||||
|
||||
|
||||
json.writeValue(layer)
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>): BlockLayer {
|
||||
// full auto
|
||||
//return strToBlockLayer(json.fromJson(type, jsonData.toJson(JsonWriter.OutputType.minimal)) as LayerInfo)
|
||||
|
||||
// full manual
|
||||
try {
|
||||
return strToBlockLayer(LayerInfo(
|
||||
jsonData.getString("h"),
|
||||
jsonData.getString("b"),
|
||||
jsonData.getInt("x"),
|
||||
jsonData.getInt("y")
|
||||
))
|
||||
}
|
||||
catch (e: BlockLayerHashMismatchError) {
|
||||
EchoError(e.message ?: "")
|
||||
return e.offendingObject
|
||||
}
|
||||
}
|
||||
})
|
||||
// WorldTime
|
||||
jsoner.setSerializer(WorldTime::class.java, object : Json.Serializer<WorldTime> {
|
||||
override fun write(json: Json, obj: WorldTime, knownType: Class<*>?) {
|
||||
json.writeValue(obj.TIME_T)
|
||||
}
|
||||
|
||||
override fun read(json: Json, jsonData: JsonValue, type: Class<*>?): WorldTime {
|
||||
return WorldTime(jsonData.asLong())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private data class LayerInfo(val h: String, val b: String, val x: Int, val y: Int)
|
||||
|
||||
/**
|
||||
* @param b a BlockLayer
|
||||
* @return Bytes in [b] which are GZip'd then Ascii85-encoded
|
||||
*/
|
||||
private fun blockLayerToStr(b: BlockLayer): String {
|
||||
val sb = StringBuilder()
|
||||
val bo = ByteArray64GrowableOutputStream()
|
||||
val zo = GZIPOutputStream(bo)
|
||||
|
||||
// zip
|
||||
/*b.bytesIterator().forEachRemaining {
|
||||
zo.write(it.toInt())
|
||||
}
|
||||
zo.flush(); zo.close()*/
|
||||
|
||||
// enascii
|
||||
val ba = bo.toByteArray64()
|
||||
var bai = 0
|
||||
val buf = IntArray(4) { Ascii85.PAD_BYTE }
|
||||
// ba.forEach {
|
||||
b.bytesIterator().forEachRemaining {
|
||||
if (bai > 0 && bai % 4 == 0) {
|
||||
sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
|
||||
buf.fill(Ascii85.PAD_BYTE)
|
||||
}
|
||||
|
||||
buf[bai % 4] = it.toInt() and 255
|
||||
|
||||
bai += 1
|
||||
}; sb.append(Ascii85.encode(buf[0], buf[1], buf[2], buf[3]))
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun strToBlockLayer(layerInfo: LayerInfo): BlockLayer {
|
||||
val layer = BlockLayer(layerInfo.x, layerInfo.y)
|
||||
val unasciidBytes = ByteArray64()
|
||||
val unzipdBytes = ByteArray64()
|
||||
|
||||
// unascii
|
||||
var bai = 0
|
||||
val buf = CharArray(5) { Ascii85.PAD_CHAR }
|
||||
layerInfo.b.forEach {
|
||||
if (bai > 0 && bai % 5 == 0) {
|
||||
Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) }
|
||||
buf.fill(Ascii85.PAD_CHAR)
|
||||
}
|
||||
|
||||
buf[bai % 5] = it
|
||||
|
||||
bai += 1
|
||||
}; Ascii85.decode(buf[0], buf[1], buf[2], buf[3], buf[4]).forEach { unasciidBytes.add(it) }
|
||||
|
||||
// unzip
|
||||
/*val zi = GZIPInputStream(ByteArray64InputStream(unasciidBytes))
|
||||
while (true) {
|
||||
val byte = zi.read()
|
||||
if (byte == -1) break
|
||||
unzipdBytes.add(byte.toByte())
|
||||
}
|
||||
zi.close()*/
|
||||
|
||||
// write to blocklayer and the digester
|
||||
digester.reset()
|
||||
var writeCursor = 0L
|
||||
val sb = StringBuilder()
|
||||
// unzipdBytes.forEach {
|
||||
unasciidBytes.forEach {
|
||||
if (writeCursor < layer.ptr.size) {
|
||||
|
||||
if (writeCursor < 1024) {
|
||||
sb.append("${it.tostr()} ")
|
||||
}
|
||||
|
||||
|
||||
layer.ptr[writeCursor] = it
|
||||
digester.update(it)
|
||||
writeCursor += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
printdbg(this, "post: $sb")
|
||||
|
||||
|
||||
// check hash
|
||||
val hash = StringBuilder().let { sb -> digester.digest().forEach { sb.append(it.tostr()) }; sb.toString() }
|
||||
|
||||
if (hash != layerInfo.h) {
|
||||
throw BlockLayerHashMismatchError(layerInfo.h, hash, layer)
|
||||
}
|
||||
|
||||
return layer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user