Minor cleanup, VersionedAdapter

This commit is contained in:
DBotThePony 2024-04-02 21:53:18 +07:00
parent 24fdf7ab70
commit 66a3f0304a
Signed by: DBot
GPG Key ID: DCC23B5715498507
24 changed files with 345 additions and 187 deletions

View File

@ -162,27 +162,67 @@ object Starbound : ISBFileLocator {
// immeasurably lazy and fragile solution, too bad!
// While having four separate Gson instances look like a (much) better solution (and it indeed could have been!),
// we must not forget the fact that 'Starbound' and 'Consistent data format' are opposites,
// and there are cases of where discStore() calls toJson() on children data, despite it having discStore() too.
var IS_WRITING_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false }
// and there are cases of where discStore() calls toJson() on children data, despite it having its own discStore() too.
var IS_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false }
private set
var IS_WRITING_STORE_JSON: Boolean by ThreadLocal.withInitial { false }
var IS_STORE_JSON: Boolean by ThreadLocal.withInitial { false }
private set
fun writeLegacyJson(data: Any): JsonElement {
fun legacyJson(data: Any): JsonElement {
try {
IS_WRITING_LEGACY_JSON = true
IS_LEGACY_JSON = true
return gson.toJsonTree(data)
} finally {
IS_WRITING_LEGACY_JSON = false
IS_LEGACY_JSON = false
}
}
fun <T> writeLegacyJson(block: () -> T): T {
fun storeJson(data: Any): JsonElement {
try {
IS_WRITING_LEGACY_JSON = true
IS_STORE_JSON = true
return gson.toJsonTree(data)
} finally {
IS_STORE_JSON = false
}
}
fun legacyStoreJson(data: Any): JsonElement {
try {
IS_STORE_JSON = true
IS_LEGACY_JSON = true
return gson.toJsonTree(data)
} finally {
IS_STORE_JSON = false
IS_LEGACY_JSON = false
}
}
fun <T> legacyJson(block: () -> T): T {
try {
IS_LEGACY_JSON = true
return block.invoke()
} finally {
IS_WRITING_LEGACY_JSON = false
IS_LEGACY_JSON = false
}
}
fun <T> storeJson(block: () -> T): T {
try {
IS_STORE_JSON = true
return block.invoke()
} finally {
IS_STORE_JSON = false
}
}
fun <T> legacyStoreJson(block: () -> T): T {
try {
IS_STORE_JSON = true
IS_LEGACY_JSON = true
return block.invoke()
} finally {
IS_STORE_JSON = false
IS_LEGACY_JSON = false
}
}
@ -242,11 +282,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(RGBAColorTypeAdapter)
registerTypeAdapter(Drawable::Adapter)
registerTypeAdapter(ObjectOrientation::Adapter)
registerTypeAdapter(ObjectDefinition::Adapter)
registerTypeAdapter(StatModifier::Adapter)
registerTypeAdapterFactory(NativeLegacy.Companion)
// математические классы
@ -261,8 +296,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(Vector4iTypeAdapter.nullSafe())
registerTypeAdapter(Vector4dTypeAdapter.nullSafe())
registerTypeAdapter(Vector4fTypeAdapter.nullSafe())
registerTypeAdapterFactory(Line2d.Companion)
registerTypeAdapterFactory(UniversePos.Companion)
registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
registerTypeAdapterFactory(WeightedList.Companion)
@ -291,7 +324,6 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
registerTypeAdapter(ItemDescriptor::Adapter)
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
registerTypeAdapterFactory(UniverseChunk.Companion)

View File

@ -0,0 +1,31 @@
package ru.dbotthepony.kstarbound
import com.google.gson.JsonElement
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import ru.dbotthepony.kstarbound.json.VersionedJson
object VersionRegistry {
private val versions = Object2IntOpenHashMap<String>()
fun currentVersion(name: String): Int {
return versions.getInt(name)
}
fun make(name: String, contents: JsonElement): VersionedJson {
return VersionedJson(name, currentVersion(name), contents)
}
fun <T> load(name: String, reader: JsonReader, adapter: TypeAdapter<T>): T {
val read = this.adapter.read(reader) ?: throw NullPointerException("Expected versioned json $name, but found null")
if (read.version != currentVersion(name)) {
throw IllegalStateException("NYI: Migrating $name from ${read.version} to ${currentVersion(name)}")
}
return adapter.fromJsonTree(read.content)
}
private val adapter by lazy { Starbound.gson.getAdapter(VersionedJson::class.java) }
}

View File

@ -31,6 +31,7 @@ import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.getAdapter
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.world.physics.Poly
@ -111,7 +112,7 @@ data class TouchDamage(
@JsonAdapter(DamageSource.Adapter::class)
data class DamageSource(
val damageType: DamageType,
val damageArea: Either<Poly, Pair<Vector2d, Vector2d>>,
val damageArea: Either<Poly, Line2d>,
val damage: Double,
val trackSourceEntity: Boolean,
val sourceEntityId: Int = 0,
@ -125,7 +126,7 @@ data class DamageSource(
) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(
DamageType.entries[stream.readUnsignedByte()],
stream.readMVariant2({ Poly.read(this, isLegacy) }, { stream.readVector2d(isLegacy) to stream.readVector2d(isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant damageArea"),
stream.readMVariant2({ Poly.read(this, isLegacy) }, { Line2d(stream, isLegacy) }) ?: throw IllegalArgumentException("Empty MVariant damageArea"),
stream.readDouble(isLegacy),
stream.readBoolean(),
stream.readInt(),
@ -140,7 +141,7 @@ data class DamageSource(
data class JsonData(
val poly: Poly? = null,
val line: Pair<Vector2d, Vector2d>? = null,
val line: Line2d? = null,
val damage: Double,
val damageType: DamageType = DamageType.DAMAGE,
val trackSourceEntity: Boolean = true,
@ -158,7 +159,7 @@ data class DamageSource(
fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeByte(damageType.ordinal)
stream.writeMVariant2(damageArea, { it.write(stream, isLegacy) }, { stream.writeStruct2d(it.first, isLegacy); stream.writeStruct2d(it.second, isLegacy) })
stream.writeMVariant2(damageArea, { it.write(stream, isLegacy) }, { it.write(stream, isLegacy) })
stream.writeDouble(damage, isLegacy)
stream.writeBoolean(trackSourceEntity)
stream.writeInt(sourceEntityId)

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
@ -23,6 +24,7 @@ import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kstarbound.math.Line2d
@JsonAdapter(Drawable.Adapter::class)
sealed class Drawable(val position: Vector2f, val color: RGBAColor, val fullbright: Boolean) {
@JsonFactory
data class Transformations(val centered: Boolean = false, val rotation: Float = 0f, val mirrored: Boolean = false, val scale: Either<Float, Vector2f> = Either.left(1f))

View File

@ -4,6 +4,7 @@ import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
@ -17,6 +18,7 @@ import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream
import java.io.DataOutputStream
@JsonAdapter(StatModifier.Adapter::class)
data class StatModifier(val stat: String, val value: Double, val type: StatModifierType) {
class Adapter(gson: Gson) : TypeAdapter<StatModifier>() {
private val objects = gson.getAdapter(JsonObject::class.java)

View File

@ -53,7 +53,7 @@ class Image private constructor(
val width: Int,
val height: Int,
val amountOfChannels: Int,
sprites: List<DataSprite>?
spritesData: Pair<List<DataSprite>, IStarboundFile>?
) {
init {
check(width >= 0) { "Invalid width $width" }
@ -61,22 +61,45 @@ class Image private constructor(
check(amountOfChannels in 1 .. 4) { "Unknown number of channels $amountOfChannels" }
}
private val spritesInternal = Object2ObjectLinkedOpenHashMap<String, Sprite>()
private val spritesInternal = LinkedHashMap<String, Sprite>()
private var dataRef: WeakReference<ByteBuffer>? = null
private val lock = ReentrantLock()
//private val _texture = ThreadLocal<WeakReference<GLTexture2D>>()
init {
if (sprites == null) {
if (spritesData == null) {
this.spritesInternal["default"] = Sprite("default", 0, 0, width, height)
} else {
val (sprites, origin) = spritesData
for (data in sprites) {
this.spritesInternal[data.name] = Sprite(
data.name,
data.coordinates.x,
data.coordinates.y,
data.coordinates.z - data.coordinates.x,
data.coordinates.w - data.coordinates.y)
var sX = data.coordinates.x % width
var sY = data.coordinates.y % height
if (sX !in 0 .. width) {
//LOGGER.warn("Sprite X offset ${data.name} is out of bounds: $sX, clamping to 0 .. $width. (image: $source; frames: $origin)")
sX = sX.coerceIn(0, width)
}
if (sY !in 0 .. height) {
//LOGGER.warn("Sprite Y offset ${data.name} is out of bounds: $sY, clamping to 0 .. $height. (image: $source; frames: $origin)")
sY = sY.coerceIn(0, height)
}
var sWidth = data.coordinates.z - sX
var sHeight = data.coordinates.w - sY
if (sWidth !in 0 .. width) {
//LOGGER.warn("Sprite width ${data.name} is out of bounds: $sWidth, clamping to 0 .. $width. (image: $source; frames: $origin)")
sWidth = sWidth.coerceIn(0, width)
}
if (sHeight !in 0 .. height) {
//LOGGER.warn("Sprite height ${data.name} is out of bounds: $sHeight, clamping to 0 .. $height. (image: $source; frames: $origin)")
sHeight = sHeight.coerceIn(0, height)
}
this.spritesInternal[data.name] = Sprite(data.name, sX, sY, sWidth, sHeight)
}
}
}
@ -273,7 +296,7 @@ class Image private constructor(
for (y in 0 until PIXELS_IN_STARBOUND_UNITi) {
val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y)
if (ypixel !in 0 until width)
if (ypixel !in 0 until height)
continue
for (x in 0 until PIXELS_IN_STARBOUND_UNITi) {
@ -300,12 +323,12 @@ class Image private constructor(
}
companion object : TypeAdapter<Image>() {
const val FILL_RATIO = 1 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT)
private val LOGGER = LogManager.getLogger()
private val objects by lazy { Starbound.gson.getAdapter(JsonObject::class.java) }
private val vectors by lazy { Starbound.gson.getAdapter(Vector4i::class.java) }
private val vectors2 by lazy { Starbound.gson.getAdapter(Vector2i::class.java) }
private val configCache = ConcurrentHashMap<String, Optional<List<DataSprite>>>()
private val configCache = ConcurrentHashMap<String, Optional<Pair<List<DataSprite>, IStarboundFile>>>()
private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
private val logger = LogManager.getLogger()
@ -494,17 +517,17 @@ class Image private constructor(
return ImmutableList.copyOf(sprites.values)
}
private fun compute(it: String): Optional<List<DataSprite>> {
private fun compute(it: String): Optional<Pair<List<DataSprite>, IStarboundFile>> {
val find = Starbound.locate("$it.frames")
if (!find.exists) {
return Optional.empty()
} else {
return Optional.of(parseFrames(objects.read(JsonReader(find.reader()).also { it.isLenient = true })))
return Optional.of(parseFrames(objects.read(JsonReader(find.reader()).also { it.isLenient = true })) to find)
}
}
private fun getConfig(path: String): List<DataSprite>? {
private fun getConfig(path: String): Pair<List<DataSprite>, IStarboundFile>? {
var folder = path.substringBefore(':').substringBeforeLast('/')
val name = path.substringBefore(':').substringAfterLast('/').substringBefore('.')

View File

@ -17,7 +17,7 @@ data class InventoryIcon(
override val image: SpriteReference
) : IInventoryIcon {
companion object : TypeAdapter<InventoryIcon>() {
private val adapter by lazy { FactoryAdapter.createFor(InventoryIcon::class, JsonFactory(), Starbound.gson) }
private val adapter by lazy { FactoryAdapter.createFor(InventoryIcon::class, Starbound.gson) }
private val images by lazy { Starbound.gson.getAdapter(SpriteReference::class.java) }
override fun write(out: JsonWriter, value: InventoryIcon?) {

View File

@ -8,6 +8,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import org.classdump.luna.ByteString
@ -28,6 +29,8 @@ import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeVarLong
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.VersionRegistry
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.item.ItemStack
@ -105,6 +108,7 @@ fun ItemDescriptor(stream: DataInputStream): ItemDescriptor {
* [parameters] is considered to be immutable and should not be modified
* directly (must be copied for mutable context)
*/
@JsonAdapter(ItemDescriptor.Adapter::class)
data class ItemDescriptor(
val name: String,
val count: Long,
@ -123,15 +127,21 @@ data class ItemDescriptor(
return ItemStack.create(this)
}
fun toJson(): JsonObject? {
if (isEmpty) {
return null
}
private fun toJsonStruct() = JsonObject().also {
it.add("name", JsonPrimitive(name))
it.add("count", JsonPrimitive(count))
it.add("parameters", parameters.deepCopy())
}
return JsonObject().also {
it.add("name", JsonPrimitive(name))
it.add("count", JsonPrimitive(count))
it.add("parameters", parameters.deepCopy())
fun toJson(): JsonObject? {
if (Starbound.IS_STORE_JSON) {
return VersionRegistry.make("Item", toJsonStruct()).toJson()
} else {
if (isEmpty) {
return null
}
return toJsonStruct()
}
}
@ -156,19 +166,14 @@ data class ItemDescriptor(
class Adapter(gson: Gson) : TypeAdapter<ItemDescriptor>() {
private val elements = gson.getAdapter(JsonElement::class.java)
override fun write(out: JsonWriter, value: ItemDescriptor?) {
if (value == null)
out.nullValue()
else if (value.isEmpty)
override fun write(out: JsonWriter, value: ItemDescriptor) {
if (value.isEmpty)
out.nullValue()
else
out.value(value.toJson())
}
override fun read(`in`: JsonReader): ItemDescriptor {
if (`in`.consumeNull())
return EMPTY
return ItemDescriptor(elements.read(`in`))
}
}

View File

@ -45,7 +45,7 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Frames::class.java) {
return object : TypeAdapter<Frames>() {
private val adapter = FactoryAdapter.createFor(Frames::class, JsonFactory(), gson)
private val adapter = FactoryAdapter.createFor(Frames::class, gson)
private val frames = gson.getAdapter(SpriteReference::class.java)

View File

@ -9,6 +9,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.util.Either
@ -32,6 +33,7 @@ import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
@JsonAdapter(ObjectDefinition.Adapter::class)
data class ObjectDefinition(
val objectName: String,
val objectType: ObjectType = ObjectType.OBJECT,
@ -79,10 +81,6 @@ data class ObjectDefinition(
val flickerPeriod: PeriodicFunction? = null,
val orientations: ImmutableList<ObjectOrientation>,
) {
companion object {
}
class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() {
@JsonFactory(logMisses = false)
data class PlainData(

View File

@ -7,6 +7,7 @@ import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
@ -33,6 +34,7 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.world.Side
import kotlin.math.PI
@JsonAdapter(ObjectOrientation.Adapter::class)
data class ObjectOrientation(
val json: JsonObject,
val flipImages: Boolean = false,
@ -173,10 +175,23 @@ data class ObjectOrientation(
}
}
val minX = occupySpaces.minOf { it.x }
val minY = occupySpaces.minOf { it.y }
val maxX = occupySpaces.maxOf { it.x }
val maxY = occupySpaces.maxOf { it.y }
val minX: Int
val minY: Int
val maxX: Int
val maxY: Int
if (occupySpaces.isNotEmpty()) {
minX = occupySpaces.minOf { it.x }
minY = occupySpaces.minOf { it.y }
maxX = occupySpaces.maxOf { it.x }
maxY = occupySpaces.maxOf { it.y }
} else {
minX = 0
minY = 0
maxX = 0
maxY = 0
}
val boundingBox = AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY))
val metaBoundBox = obj["metaBoundBox"]?.let { aabbs.fromJsonTree(it) }

View File

@ -1,17 +1,28 @@
package ru.dbotthepony.kstarbound.defs.quest
import com.google.common.collect.ImmutableList
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.io.readCollection
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeCollection
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.VersionRegistry
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.VersionedAdapter
import ru.dbotthepony.kstarbound.json.VersionedJson
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.getAdapter
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream
import java.io.DataOutputStream
@JsonFactory
@JsonAdapter(QuestArcDescriptor.Adapter::class)
data class QuestArcDescriptor(
val quests: ImmutableList<QuestDescriptor> = ImmutableList.of(),
val stagehandUniqueId: String? = null,
@ -27,6 +38,8 @@ data class QuestArcDescriptor(
if (stagehandUniqueId != null) stream.writeBinaryString(stagehandUniqueId)
}
class Adapter(gson: Gson) : VersionedAdapter<QuestArcDescriptor>("QuestArcDescriptor", FactoryAdapter.createFor(QuestArcDescriptor::class, gson))
companion object {
val CODEC = nativeCodec(::QuestArcDescriptor, QuestArcDescriptor::write)
val LEGACY_CODEC = legacyCodec(::QuestArcDescriptor, QuestArcDescriptor::write)

View File

@ -1,18 +1,23 @@
package ru.dbotthepony.kstarbound.defs.quest
import com.google.common.collect.ImmutableMap
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.annotations.JsonAdapter
import ru.dbotthepony.kommons.io.readMap
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.io.writeMap
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.json.VersionedAdapter
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import java.io.DataInputStream
import java.io.DataOutputStream
@JsonFactory
@JsonAdapter(QuestDescriptor.Adapter::class)
data class QuestDescriptor(
val questId: String,
val templateId: String = questId,
@ -33,6 +38,8 @@ data class QuestDescriptor(
stream.writeLong(seed)
}
class Adapter(gson: Gson) : VersionedAdapter<QuestDescriptor>("QuestDescriptor", FactoryAdapter.createFor(QuestDescriptor::class, gson))
companion object {
val CODEC = nativeCodec(::QuestDescriptor, QuestDescriptor::write)
val LEGACY_CODEC = legacyCodec(::QuestDescriptor, QuestDescriptor::write)

View File

@ -140,7 +140,7 @@ abstract class VisitableWorldParameters {
this.weatherPool = read.weatherPool
}
open fun toJson(data: JsonObject, isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON) {
open fun toJson(data: JsonObject, isLegacy: Boolean = Starbound.IS_LEGACY_JSON) {
val store = StoreData(
threatLevel,
typeName,
@ -164,7 +164,7 @@ abstract class VisitableWorldParameters {
data["type"] = type.jsonName
}
fun toJson(isLegacy: Boolean = Starbound.IS_WRITING_LEGACY_JSON): JsonObject {
fun toJson(isLegacy: Boolean = Starbound.IS_LEGACY_JSON): JsonObject {
val data = JsonObject()
toJson(data, isLegacy)
return data

View File

@ -211,7 +211,7 @@ class WorldLayout {
return Starbound.gson.toJsonTree(SerializedForm(
worldSize, regionBlending, blockNoise, blendNoise,
playerStartSearchRegions, biomes.list, terrainSelectors.list,
layers = layers.stream().map { it.toJson(Starbound.IS_WRITING_LEGACY_JSON) }.collect(JsonArrayCollector)
layers = layers.stream().map { it.toJson(Starbound.IS_LEGACY_JSON) }.collect(JsonArrayCollector)
)) as JsonObject
}

View File

@ -1,18 +1,13 @@
package ru.dbotthepony.kstarbound.defs.world
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.fromJson
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.world.Universe
import ru.dbotthepony.kstarbound.world.UniversePos
import ru.dbotthepony.kstarbound.world.WorldGeometry
import kotlin.properties.Delegates
class WorldTemplate(val geometry: WorldGeometry) {
var seed: Long = 0L
@ -58,7 +53,7 @@ class WorldTemplate(val geometry: WorldGeometry) {
fun toJson(): JsonObject {
val data = Starbound.gson.toJsonTree(SerializedForm(
celestialParameters, worldParameters, skyParameters, seed,
if (Starbound.IS_WRITING_LEGACY_JSON) Either.right(geometry.size) else Either.left(geometry),
if (Starbound.IS_LEGACY_JSON) Either.right(geometry.size) else Either.left(geometry),
worldLayout
)) as JsonObject

View File

@ -106,7 +106,7 @@ abstract class NativeLegacy<NATIVE, LEGACY> {
override fun write(out: JsonWriter, value: NativeLegacy<L, R>?) {
if (value == null)
out.nullValue()
else if (Starbound.IS_WRITING_LEGACY_JSON || value.nativeValue.isEmpty)
else if (Starbound.IS_LEGACY_JSON || value.nativeValue.isEmpty)
right.write(out, value.legacy)
else
left.write(out, value.native)

View File

@ -0,0 +1,25 @@
package ru.dbotthepony.kstarbound.json
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.VersionRegistry
abstract class VersionedAdapter<T>(val name: String, val parent: TypeAdapter<T>) : TypeAdapter<T>() {
override fun write(out: JsonWriter, value: T) {
if (Starbound.IS_STORE_JSON) {
VersionRegistry.make(name, parent.toJsonTree(value)).toJson(out)
} else {
parent.write(out, value)
}
}
override fun read(`in`: JsonReader): T {
if (Starbound.IS_STORE_JSON) {
return VersionRegistry.load(name, `in`, parent)
} else {
return parent.read(`in`)
}
}
}

View File

@ -1,12 +1,29 @@
package ru.dbotthepony.kstarbound.json
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.io.readBinaryString
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import java.io.DataInputStream
@JsonFactory
data class VersionedJson(val identifier: String, val version: Int?, val content: JsonElement) {
constructor(data: DataInputStream) : this(
data.readBinaryString(),
data.read().let { if (it > 0) data.readInt() else null },
data.readJsonElement())
fun toJson(): JsonObject {
return adapter.toJsonTree(this) as JsonObject
}
fun toJson(writer: JsonWriter) {
adapter.write(writer, this)
}
companion object {
private val adapter by lazy { Starbound.gson.getAdapter<VersionedJson>() }
}
}

View File

@ -484,7 +484,7 @@ class FactoryAdapter<T : Any> private constructor(
companion object {
private val LOGGER = LogManager.getLogger()
fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory = JsonFactory(), gson: Gson, stringInterner: Interner<String> = Starbound.STRINGS): TypeAdapter<T> {
fun <T : Any> createFor(kclass: KClass<T>, gson: Gson, config: JsonFactory = JsonFactory(), stringInterner: Interner<String> = Starbound.STRINGS): TypeAdapter<T> {
val builder = Builder(kclass)
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()
@ -537,7 +537,7 @@ class FactoryAdapter<T : Any> private constructor(
val bconfig = first[0] as JsonFactory
val kclass = raw.kotlin as KClass<T>
return createFor(kclass, bconfig, gson, stringInterner)
return createFor(kclass, gson, bconfig, stringInterner)
}
return null

View File

@ -3,12 +3,19 @@ package ru.dbotthepony.kstarbound.math
import com.google.gson.Gson
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.io.readVector2d
import ru.dbotthepony.kstarbound.io.writeDouble
import ru.dbotthepony.kstarbound.io.writeStruct2d
import ru.dbotthepony.kstarbound.json.getAdapter
import java.io.DataInputStream
import java.io.DataOutputStream
import kotlin.math.absoluteValue
private operator fun Vector2d.compareTo(other: Vector2d): Int {
@ -17,7 +24,10 @@ private operator fun Vector2d.compareTo(other: Vector2d): Int {
return cmp
}
@JsonAdapter(Line2d.Adapter::class)
data class Line2d(val a: Vector2d, val b: Vector2d) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector2d(isLegacy), stream.readVector2d(isLegacy))
data class Intersection(val intersects: Boolean, val point: KOptional<Vector2d>, val t: KOptional<Double>, val coincides: Boolean, val glances: Boolean) {
companion object {
val EMPTY = Intersection(false, KOptional(), KOptional(), false, false)
@ -32,6 +42,11 @@ data class Line2d(val a: Vector2d, val b: Vector2d) {
return Line2d(b, a)
}
fun write(stream: DataOutputStream, isLegacy: Boolean) {
stream.writeStruct2d(a, isLegacy)
stream.writeStruct2d(b, isLegacy)
}
// original source of this intersection algorithm:
// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
// article "Intersection of two lines in three-space" by Ronald Goldman, published in Graphics Gems, page 304
@ -110,33 +125,21 @@ data class Line2d(val a: Vector2d, val b: Vector2d) {
return (other - a + difference() * proj).length
}
companion object : TypeAdapterFactory {
const val NEAR_ZERO = Double.MIN_VALUE * 2.0
const val NEAR_ONE = 1.0 - NEAR_ZERO
class Adapter(gson: Gson) : TypeAdapter<Line2d>() {
private val pair = gson.getAdapter<Pair<Vector2d, Vector2d>>()
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == Line2d::class.java) {
val pair = gson.getAdapter(TypeToken.getParameterized(Vector2d::class.java, Vector2d::class.java)) as TypeAdapter<Pair<Vector2d, Vector2d>>
override fun write(out: JsonWriter, value: Line2d) {
pair.write(out, value.a to value.b)
}
return object : TypeAdapter<Line2d>() {
override fun write(out: JsonWriter, value: Line2d?) {
if (value == null)
out.nullValue()
else
pair.write(out, value.a to value.b)
}
override fun read(`in`: JsonReader): Line2d? {
if (`in`.consumeNull())
return null
val (a, b) = pair.read(`in`)
return Line2d(a, b)
}
} as TypeAdapter<T>
}
return null
override fun read(`in`: JsonReader): Line2d {
val (a, b) = pair.read(`in`)
return Line2d(a, b)
}
}
companion object {
const val NEAR_ZERO = Double.MIN_VALUE * 2.0
const val NEAR_ONE = 1.0 - NEAR_ZERO
}
}

View File

@ -145,17 +145,18 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
if (world == null) {
send(PlayerWarpResultPacket(false, request, false))
} else {
try {
world.acceptClient(this, request).await()
} catch (err: Throwable) {
send(PlayerWarpResultPacket(false, request, false))
continue
}
if (world == shipWorld) {
disconnect("ShipWorld refused to accept its owner: $err")
} else {
enqueueWarp(returnWarp ?: WarpAlias.OwnShip)
}
try {
world.acceptClient(this, request).await()
} catch (err: Throwable) {
send(PlayerWarpResultPacket(false, request, false))
if (world == shipWorld) {
disconnect("ShipWorld refused to accept its owner: $err")
} else {
enqueueWarp(returnWarp ?: WarpAlias.OwnShip)
}
}
}

View File

@ -33,7 +33,6 @@ import ru.dbotthepony.kstarbound.world.TileHealth
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
import java.io.DataOutputStream
import java.util.HashMap
import java.util.concurrent.ConcurrentLinkedQueue
@ -91,7 +90,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
if (client.isLegacy) {
client.send(WorldStartPacket(
templateData = Starbound.writeLegacyJson { world.template.toJson() },
templateData = Starbound.legacyJson { world.template.toJson() },
skyData = skyData.toByteArray(),
weatherData = ByteArray(0),
playerStart = playerStart,
@ -105,7 +104,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
localInterpolationMode = false,
))
Starbound.writeLegacyJson {
Starbound.legacyJson {
client.sendAndFlush(CentralStructureUpdatePacket(Starbound.gson.toJsonTree(world.centralStructure)))
}
}

View File

@ -5,6 +5,7 @@ import com.google.gson.JsonObject
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory
import com.google.gson.annotations.JsonAdapter
import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
@ -32,6 +33,7 @@ import java.io.DataOutputStream
* No validity checking is done here, any coordinate to any body whether it
* exists in a specific universe or not can be expressed.
*/
@JsonAdapter(UniversePos.Adapter::class)
data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit: Int = 0, val satelliteOrbit: Int = 0) {
constructor(stream: DataInputStream, isLegacy: Boolean) : this(stream.readVector3i(), if (isLegacy) stream.readInt() else stream.readVarInt(), if (isLegacy) stream.readInt() else stream.readVarInt())
@ -95,84 +97,71 @@ data class UniversePos(val location: Vector3i = Vector3i.ZERO, val planetOrbit:
}
}
companion object : TypeAdapterFactory {
class Adapter(gson: Gson) : TypeAdapter<UniversePos>() {
private val vectors = gson.getAdapter(Vector3i::class.java)
private val objects = gson.getAdapter(JsonObject::class.java)
override fun write(out: JsonWriter, value: UniversePos) {
out.beginObject()
out.name("location")
vectors.write(out, value.location)
out.name("planet")
out.value(value.planetOrbit)
out.name("satellite")
out.value(value.satelliteOrbit)
out.endObject()
}
override fun read(`in`: JsonReader): UniversePos {
if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
val values = objects.read(`in`)!!
val location = values.get("location", vectors)
val planet = values.get("planet", 0)
val orbit = values.get("orbit", 0)
return UniversePos(location, planet, orbit)
}
if (`in`.peek() == JsonToken.STRING) {
val read = `in`.nextString().trim()
if (read == "" || read.lowercase() == "")
return ZERO
else {
try {
val split = read.split(splitter)
val x = split[0].toInt()
val y = split[1].toInt()
val z = split[2].toInt()
val planet = if (split.size > 3) split[3].toInt() else 0
val orbit = if (split.size > 4) split[4].toInt() else 0
if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code
throw IndexOutOfBoundsException("Planetary orbit: $planet")
if (orbit < 0)
throw IndexOutOfBoundsException("Satellite orbit: $orbit")
return UniversePos(Vector3i(x, y, z), planet, orbit)
} catch (err: Throwable) {
throw JsonSyntaxException("Error parsing UniversePos from string", err)
}
}
}
throw JsonSyntaxException("Invalid data type for UniversePos: ${`in`.peek()}")
}
}
companion object {
val CODEC = nativeCodec(::UniversePos, UniversePos::write)
val LEGACY_CODEC = legacyCodec(::UniversePos, UniversePos::write)
private val splitter = Regex("[ _:]")
val ZERO = UniversePos()
override fun <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == UniversePos::class.java) {
val vectors = gson.getAdapter(Vector3i::class.java)
val objects = gson.getAdapter(JsonObject::class.java)
return object : TypeAdapter<UniversePos>() {
override fun write(out: JsonWriter, value: UniversePos?) {
if (value == null)
out.nullValue()
else {
out.beginObject()
out.name("location")
vectors.write(out, value.location)
out.name("planet")
out.value(value.planetOrbit)
out.name("satellite")
out.value(value.satelliteOrbit)
out.endObject()
}
}
override fun read(`in`: JsonReader): UniversePos? {
if (`in`.consumeNull())
return null
if (`in`.peek() == JsonToken.BEGIN_OBJECT) {
val values = objects.read(`in`)!!
val location = values.get("location", vectors)
val planet = values.get("planet", 0)
val orbit = values.get("orbit", 0)
return UniversePos(location, planet, orbit)
}
if (`in`.peek() == JsonToken.STRING) {
val read = `in`.nextString().trim()
if (read == "" || read.lowercase() == "")
return ZERO
else {
try {
val split = read.split(splitter)
val x = split[0].toInt()
val y = split[1].toInt()
val z = split[2].toInt()
val planet = if (split.size > 3) split[3].toInt() else 0
val orbit = if (split.size > 4) split[4].toInt() else 0
if (planet <= 0) // TODO: ??? Determine, if this is a bug in original code
throw IndexOutOfBoundsException("Planetary orbit: $planet")
if (orbit < 0)
throw IndexOutOfBoundsException("Satellite orbit: $orbit")
return UniversePos(Vector3i(x, y, z), planet, orbit)
} catch (err: Throwable) {
throw JsonSyntaxException("Error parsing UniversePos from string", err)
}
}
}
throw JsonSyntaxException("Invalid data type for UniversePos: ${`in`.peek()}")
}
} as TypeAdapter<T>
}
return null
}
}
}