From 86782e259e606430c2c14895c00eed587675254e Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 29 Sep 2023 20:26:45 +0700 Subject: [PATCH] KOptional type adapter, and movement controller defs structures --- .../kotlin/ru/dbotthepony/kstarbound/Ext.kt | 3 + .../ru/dbotthepony/kstarbound/Starbound.kt | 8 ++ .../kstarbound/api/IStarboundFileSystem.kt | 13 +++ .../kstarbound/defs/JumpProfile.kt | 29 ++++- .../kstarbound/defs/MovementModifiers.kt | 30 +++++ .../kstarbound/defs/MovementParameters.kt | 108 ++++++++++++++++-- .../io/json/KOptionalTypeAdapter.kt | 53 +++++++++ .../io/json/builder/FactoryAdapter.kt | 84 +++++++++----- .../kstarbound/io/json/builder/Properties.kt | 16 ++- .../ru/dbotthepony/kstarbound/util/Either.kt | 2 + .../dbotthepony/kstarbound/util/KOptional.kt | 63 ++++++++++ .../kstarbound/world/Raycasting.kt | 2 +- 12 files changed, 363 insertions(+), 48 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementModifiers.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KOptionalTypeAdapter.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt index 882a292b..6750977d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt @@ -6,6 +6,7 @@ import com.google.gson.GsonBuilder import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader import java.util.Arrays import java.util.stream.Stream import kotlin.reflect.KProperty @@ -43,3 +44,5 @@ operator fun ImmutableMap.Builder.set(key: K, value: V): ImmutableM fun String.sintern(): String = Starbound.strings.intern(this) +inline fun Gson.fromJson(reader: JsonReader): T? = fromJson(reader, T::class.java) + diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 3a305150..cc04b1f1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -55,6 +55,7 @@ import ru.dbotthepony.kstarbound.io.json.EitherTypeAdapter import ru.dbotthepony.kstarbound.io.json.FastutilTypeAdapterFactory import ru.dbotthepony.kstarbound.io.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.io.json.InternedStringAdapter +import ru.dbotthepony.kstarbound.io.json.KOptionalTypeAdapter import ru.dbotthepony.kstarbound.io.json.LongRangeAdapter import ru.dbotthepony.kstarbound.io.json.NothingAdapter import ru.dbotthepony.kstarbound.io.json.OneOfTypeAdapter @@ -217,6 +218,8 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(EitherTypeAdapter) // OneOf<> registerTypeAdapterFactory(OneOfTypeAdapter) + // KOptional<> + registerTypeAdapterFactory(KOptionalTypeAdapter) // Pair<> registerTypeAdapterFactory(PairAdapterFactory) @@ -347,6 +350,9 @@ object Starbound : ISBFileLocator { @Volatile var terminateLoading = false + var defaultMovementParameters = MovementParameters() + private set + fun loadJsonAsset(path: String): JsonElement? { val filename: String val jsonPath: String? @@ -1031,6 +1037,8 @@ object Starbound : ISBFileLocator { tasks.forEach { it.join() } + defaultMovementParameters = gson.fromJson(jsonReader("/default_actor_movement.config"))!! + initializing = false initialized = true log.line("Finished loading in ${System.currentTimeMillis() - time}ms") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt index f1f34f20..0d4fbadf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/api/IStarboundFileSystem.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.api import com.google.common.collect.ImmutableMap import com.google.gson.JsonElement import com.google.gson.JsonParser +import com.google.gson.stream.JsonReader import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.io.StarboundPak import ru.dbotthepony.kstarbound.stream @@ -37,6 +38,12 @@ fun interface ISBFileLocator { * @throws FileNotFoundException if file does not exist */ fun reader(path: String) = locate(path).reader() + + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun jsonReader(path: String) = locate(path).jsonReader() } interface IStarboundFile : ISBFileLocator { @@ -145,6 +152,12 @@ interface IStarboundFile : ISBFileLocator { */ fun reader(): Reader = InputStreamReader(BufferedInputStream(open())) + /** + * @throws IllegalStateException if file is a directory + * @throws FileNotFoundException if file does not exist + */ + fun jsonReader(): JsonReader = JsonReader(reader()).also { it.isLenient = true } + /** * @throws IllegalStateException if file is a directory * @throws FileNotFoundException if file does not exist diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt index e0897281..bf222697 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/JumpProfile.kt @@ -1,10 +1,31 @@ package ru.dbotthepony.kstarbound.defs import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.util.KOptional @JsonFactory data class JumpProfile( - val jumpSpeed: Double = 0.0, - val jumpInitialPercentage: Double = 0.0, - val jumpHoldTime: Double = 0.0, -) + val jumpSpeed: KOptional = KOptional.empty(), + val jumpControlForce: KOptional = KOptional.empty(), + val jumpInitialPercentage: KOptional = KOptional.empty(), + val jumpHoldTime: KOptional = KOptional.empty(), + val jumpTotalHoldTime: KOptional = KOptional.empty(), + val multiJump: KOptional = KOptional.empty(), + val reJumpDelay: KOptional = KOptional.empty(), + val autoJump: KOptional = KOptional.empty(), + val collisionCancelled: KOptional = KOptional.empty(), +) { + fun merge(other: JumpProfile): JumpProfile { + return JumpProfile( + jumpSpeed = jumpSpeed.or(other.jumpSpeed), + jumpControlForce = jumpControlForce.or(other.jumpControlForce), + jumpInitialPercentage = jumpInitialPercentage.or(other.jumpInitialPercentage), + jumpHoldTime = jumpHoldTime.or(other.jumpHoldTime), + jumpTotalHoldTime = jumpTotalHoldTime.or(other.jumpTotalHoldTime), + multiJump = multiJump.or(other.multiJump), + reJumpDelay = reJumpDelay.or(other.reJumpDelay), + autoJump = autoJump.or(other.autoJump), + collisionCancelled = collisionCancelled.or(other.collisionCancelled), + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementModifiers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementModifiers.kt new file mode 100644 index 00000000..8fec7c49 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementModifiers.kt @@ -0,0 +1,30 @@ +package ru.dbotthepony.kstarbound.defs + +import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory + +@JsonFactory +class MovementModifiers( + val groundMovementModifier: Double = 1.0, + val liquidMovementModifier: Double = 1.0, + val speedModifier: Double = 1.0, + val airJumpModifier: Double = 1.0, + val liquidJumpModifier: Double = 1.0, + val runningSuppressed: Boolean = false, + val jumpingSuppressed: Boolean = false, + val facingSuppressed: Boolean = false, + val movementSuppressed: Boolean = false, +) { + fun combine(other: MovementModifiers): MovementModifiers { + return MovementModifiers( + groundMovementModifier = groundMovementModifier * other.groundMovementModifier, + liquidMovementModifier = liquidMovementModifier * other.liquidMovementModifier, + speedModifier = speedModifier * other.speedModifier, + airJumpModifier = airJumpModifier * other.airJumpModifier, + liquidJumpModifier = liquidJumpModifier * other.liquidJumpModifier, + runningSuppressed = runningSuppressed || other.runningSuppressed, + jumpingSuppressed = jumpingSuppressed || other.jumpingSuppressed, + facingSuppressed = facingSuppressed || other.facingSuppressed, + movementSuppressed = movementSuppressed || other.movementSuppressed, + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementParameters.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementParameters.kt index af3ee69b..b84faed9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementParameters.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/MovementParameters.kt @@ -1,22 +1,106 @@ package ru.dbotthepony.kstarbound.defs import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSet +import ru.dbotthepony.kstarbound.io.json.builder.JsonAlias import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory +import ru.dbotthepony.kstarbound.util.KOptional import ru.dbotthepony.kvector.vector.Vector2d @JsonFactory data class MovementParameters( - val flySpeed: Double? = null, - val airFriction: Double? = null, - val airJumpProfile: JumpProfile? = null, - val airForce: Double? = null, + val mass: KOptional = KOptional.empty(), + val gravityMultiplier: KOptional = KOptional.empty(), + val liquidBuoyancy: KOptional = KOptional.empty(), + val airBuoyancy: KOptional = KOptional.empty(), + val bounceFactor: KOptional = KOptional.empty(), + val stopOnFirstBounce: KOptional = KOptional.empty(), + val enableSurfaceSlopeCorrection: KOptional = KOptional.empty(), + val slopeSlidingFactor: KOptional = KOptional.empty(), + val maxMovementPerStep: KOptional = KOptional.empty(), + val maximumCorrection: KOptional = KOptional.empty(), + val speedLimit: KOptional = KOptional.empty(), - val runSpeed: Double? = null, - val walkSpeed: Double? = null, - val mass: Double? = null, + @JsonAlias("collisionPoly") + val standingPoly: KOptional> = KOptional.empty(), + @JsonAlias("collisionPoly") + val crouchingPoly: KOptional> = KOptional.empty(), - // TODO: А оно вообще используется? Как по мне движок старбаунда генерирует коллизию из пикселей текстуры - val collisionPoly: ImmutableList? = null, - val crouchingPoly: ImmutableList? = null, - val standingPoly: ImmutableList? = null, -) + val stickyCollision: KOptional = KOptional.empty(), + val stickyForce: KOptional = KOptional.empty(), + val walkSpeed: KOptional = KOptional.empty(), + val runSpeed: KOptional = KOptional.empty(), + val flySpeed: KOptional = KOptional.empty(), + val airFriction: KOptional = KOptional.empty(), + val liquidFriction: KOptional = KOptional.empty(), + val minimumLiquidPercentage: KOptional = KOptional.empty(), + val liquidImpedance: KOptional = KOptional.empty(), + val normalGroundFriction: KOptional = KOptional.empty(), + val ambulatingGroundFriction: KOptional = KOptional.empty(), + val groundForce: KOptional = KOptional.empty(), + val airForce: KOptional = KOptional.empty(), + val liquidForce: KOptional = KOptional.empty(), + + val airJumpProfile: JumpProfile = JumpProfile(), + val liquidJumpProfile: JumpProfile = JumpProfile(), + + val fallStatusSpeedMin: KOptional = KOptional.empty(), + val fallThroughSustainFrames: KOptional = KOptional.empty(), + val maximumPlatformCorrection: KOptional = KOptional.empty(), + val maximumPlatformCorrectionVelocityFactor: KOptional = KOptional.empty(), + val physicsEffectCategories: KOptional> = KOptional.empty(), + val groundMovementMinimumSustain: KOptional = KOptional.empty(), + val groundMovementMaximumSustain: KOptional = KOptional.empty(), + val groundMovementCheckDistance: KOptional = KOptional.empty(), + + val collisionEnabled: KOptional = KOptional.empty(), + val frictionEnabled: KOptional = KOptional.empty(), + val gravityEnabled: KOptional = KOptional.empty(), + + val pathExploreRate: KOptional = KOptional.empty(), +) { + fun merge(other: MovementParameters): MovementParameters { + return MovementParameters( + mass = mass.or(other.mass), + gravityMultiplier = gravityMultiplier.or(other.gravityMultiplier), + liquidBuoyancy = liquidBuoyancy.or(other.liquidBuoyancy), + airBuoyancy = airBuoyancy.or(other.airBuoyancy), + bounceFactor = bounceFactor.or(other.bounceFactor), + stopOnFirstBounce = stopOnFirstBounce.or(other.stopOnFirstBounce), + enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection.or(other.enableSurfaceSlopeCorrection), + slopeSlidingFactor = slopeSlidingFactor.or(other.slopeSlidingFactor), + maxMovementPerStep = maxMovementPerStep.or(other.maxMovementPerStep), + maximumCorrection = maximumCorrection.or(other.maximumCorrection), + speedLimit = speedLimit.or(other.speedLimit), + standingPoly = standingPoly.or(other.standingPoly), + crouchingPoly = crouchingPoly.or(other.crouchingPoly), + stickyCollision = stickyCollision.or(other.stickyCollision), + stickyForce = stickyForce.or(other.stickyForce), + walkSpeed = walkSpeed.or(other.walkSpeed), + runSpeed = runSpeed.or(other.runSpeed), + flySpeed = flySpeed.or(other.flySpeed), + airFriction = airFriction.or(other.airFriction), + liquidFriction = liquidFriction.or(other.liquidFriction), + minimumLiquidPercentage = minimumLiquidPercentage.or(other.minimumLiquidPercentage), + liquidImpedance = liquidImpedance.or(other.liquidImpedance), + normalGroundFriction = normalGroundFriction.or(other.normalGroundFriction), + ambulatingGroundFriction = ambulatingGroundFriction.or(other.ambulatingGroundFriction), + groundForce = groundForce.or(other.groundForce), + airForce = airForce.or(other.airForce), + liquidForce = liquidForce.or(other.liquidForce), + airJumpProfile = airJumpProfile.merge(other.airJumpProfile), + liquidJumpProfile = liquidJumpProfile.merge(other.liquidJumpProfile), + fallStatusSpeedMin = fallStatusSpeedMin.or(other.fallStatusSpeedMin), + fallThroughSustainFrames = fallThroughSustainFrames.or(other.fallThroughSustainFrames), + maximumPlatformCorrection = maximumPlatformCorrection.or(other.maximumPlatformCorrection), + maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor.or(other.maximumPlatformCorrectionVelocityFactor), + physicsEffectCategories = physicsEffectCategories.or(other.physicsEffectCategories), + groundMovementMinimumSustain = groundMovementMinimumSustain.or(other.groundMovementMinimumSustain), + groundMovementMaximumSustain = groundMovementMaximumSustain.or(other.groundMovementMaximumSustain), + groundMovementCheckDistance = groundMovementCheckDistance.or(other.groundMovementCheckDistance), + collisionEnabled = collisionEnabled.or(other.collisionEnabled), + frictionEnabled = frictionEnabled.or(other.frictionEnabled), + gravityEnabled = gravityEnabled.or(other.gravityEnabled), + ) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KOptionalTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KOptionalTypeAdapter.kt new file mode 100644 index 00000000..20c103b5 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KOptionalTypeAdapter.kt @@ -0,0 +1,53 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException +import com.google.gson.TypeAdapter +import com.google.gson.TypeAdapterFactory +import com.google.gson.reflect.TypeToken +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.util.KOptional +import java.lang.reflect.ParameterizedType + +@Suppress("DEPRECATION") +object KOptionalTypeAdapter : TypeAdapterFactory { + override fun create(gson: Gson, type: TypeToken): TypeAdapter? { + if (type.rawType === KOptional.Nullable::class.java || type.rawType === KOptional::class.java || type.rawType === KOptional.NotNull::class.java) { + val notnull = type.rawType === KOptional.NotNull::class.java + val param = (type.type as? ParameterizedType ?: return null).actualTypeArguments[0] + val token = TypeToken.get(param) + val isBool = token.rawType === Boolean::class.java + + return object : TypeAdapter>() { + private val adapter0 = gson.getAdapter(token) as TypeAdapter + + override fun write(out: JsonWriter, value: KOptional?) { + if (value === null) { + out.nullValue() + } else if (value.isPresent) { + adapter0.write(out, value.value) + } + } + + override fun read(`in`: JsonReader): KOptional { + if (isBool) { + if (notnull) { + return KOptional((adapter0.read(`in`) ?: throw JsonSyntaxException("This KOptional does not accept nulls")) as Boolean) as KOptional + } else { + return KOptional(adapter0.read(`in`) as Boolean?) as KOptional + } + } else { + if (notnull) { + return KOptional(adapter0.read(`in`) ?: throw JsonSyntaxException("This KOptional does not accept nulls")) + } else { + return KOptional(adapter0.read(`in`)) + } + } + } + } as TypeAdapter + } else { + return null + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index c65dcf8d..6a497255 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableMap import com.github.benmanes.caffeine.cache.Interner import com.google.gson.Gson import com.google.gson.JsonArray +import com.google.gson.JsonElement import com.google.gson.JsonNull import com.google.gson.JsonObject import com.google.gson.JsonParseException @@ -18,6 +19,8 @@ import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.ints.IntArrayList +import it.unimi.dsi.fastutil.ints.IntList import it.unimi.dsi.fastutil.objects.Object2IntArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.ObjectArraySet @@ -28,7 +31,9 @@ import ru.dbotthepony.kstarbound.defs.util.enrollMap import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement import ru.dbotthepony.kstarbound.io.json.consumeNull import ru.dbotthepony.kstarbound.io.json.value +import ru.dbotthepony.kstarbound.util.Either import java.lang.reflect.Constructor +import java.util.function.Function import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.properties.Delegates import kotlin.reflect.* @@ -43,13 +48,14 @@ import kotlin.reflect.full.primaryConstructor class FactoryAdapter private constructor( val clazz: KClass, val types: ImmutableList>, - aliases: Map, + aliases: Map>, val asJsonArray: Boolean, val storesJson: Boolean, val stringInterner: Interner, val logMisses: Boolean, + private val elements: TypeAdapter ) : TypeAdapter() { - private val name2index = Object2IntArrayMap() + private val name2index = Object2ObjectArrayMap() private val loggedMisses = ObjectArraySet() init { @@ -57,14 +63,14 @@ class FactoryAdapter private constructor( throw IllegalArgumentException("Can't have both flat properties and input be as json array") } - name2index.defaultReturnValue(-1) - for ((i, pair) in types.withIndex()) { - name2index[pair.property.name] = i + name2index.computeIfAbsent(pair.property.name, Function { IntArrayList() }).add(i) aliases.entries.forEach { - if (it.value == pair.property.name) { - name2index[it.key] = i + it.value.forEach { target -> + if (target == pair.property.name) { + name2index.computeIfAbsent(it.key, Function { IntArrayList() }).add(i) + } } } } @@ -209,7 +215,7 @@ class FactoryAdapter private constructor( // Если нам необходимо читать объект как набор данных массива, то давай if (asJsonArray) { if (storesJson) { - val readArray = TypeAdapters.JSON_ELEMENT.read(reader) + val readArray = elements.read(reader) if (readArray !is JsonArray) throw JsonParseException("Expected JSON element to be an Array, ${readArray::class.qualifiedName} given") @@ -252,7 +258,7 @@ class FactoryAdapter private constructor( var json: JsonObject by Delegates.notNull() if (storesJson || types.any { it.isFlat }) { - val readMap = TypeAdapters.JSON_ELEMENT.read(reader) + val readMap = elements.read(reader) if (readMap !is JsonObject) throw JsonParseException("Expected JSON element to be a Map, ${readMap::class.qualifiedName} given") @@ -268,38 +274,55 @@ class FactoryAdapter private constructor( while (reader.peek() != JsonToken.END_OBJECT) { val name = reader.nextName() - val fieldId = name2index.getInt(name) + val fields = name2index[name] - if (fieldId == -1) { + if (fields == null || fields.size == 0) { if (!storesJson && logMisses && loggedMisses.add(name)) { LOGGER.warn("${clazz.qualifiedName} has no property for storing $name") } reader.skipValue() } else { - val tuple = types[fieldId] + val localReader: Either - if (tuple.isFlat) { - reader.skipValue() - continue + if (fields.size == 1) { + localReader = Either.right(reader) + } else { + val readValue = elements.read(reader) + localReader = Either.left(readValue) } - if (tuple.isIgnored) { - if (loggedMisses.add(name)) { - LOGGER.warn("${clazz.qualifiedName} can not load $name from JSON") + val itr = fields.intIterator() + + while (itr.hasNext()) { + val fieldId = itr.nextInt() + + @Suppress("NAME_SHADOWING") + val reader = localReader.map({ JsonTreeReader(it) }, { it }) + val tuple = types[fieldId] + + if (tuple.isFlat) { + reader.skipValue() + continue } - reader.skipValue() - continue - } + if (tuple.isIgnored) { + if (loggedMisses.add(name)) { + LOGGER.warn("${clazz.qualifiedName} can not load $name from JSON") + } - val (field, adapter) = tuple + reader.skipValue() + continue + } - try { - readValues[fieldId] = adapter.read(reader) - presentValues[fieldId] = true - } catch(err: Throwable) { - throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${clazz.qualifiedName}", err) + val (field, adapter) = tuple + + try { + readValues[fieldId] = adapter.read(reader) + presentValues[fieldId] = true + } catch(err: Throwable) { + throw JsonSyntaxException("Reading field \"${field.name}\" near ${reader.path} for ${clazz.qualifiedName}", err) + } } } } @@ -391,7 +414,7 @@ class FactoryAdapter private constructor( private var asList = false private var storesJson = false private val types = ArrayList>() - private val aliases = Object2ObjectArrayMap() + private val aliases = Object2ObjectArrayMap>() var stringInterner: Interner = Interner { it } var logMisses = true @@ -422,7 +445,8 @@ class FactoryAdapter private constructor( storesJson = storesJson, stringInterner = stringInterner, aliases = aliases, - logMisses = logMisses + logMisses = logMisses, + elements = gson.getAdapter(JsonElement::class.java) ) } @@ -445,7 +469,7 @@ class FactoryAdapter private constructor( } fun alias(alias: String, canonical: String): Builder { - aliases[alias] = canonical + aliases.computeIfAbsent(alias, Function { ArrayList() }).add(canonical) return this } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt index 347aeaa6..906e2bb7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/Properties.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.io.json.builder import com.google.gson.Gson import com.google.gson.TypeAdapter import com.google.gson.reflect.TypeToken +import ru.dbotthepony.kstarbound.util.KOptional import kotlin.properties.Delegates import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KProperty1 @@ -34,10 +35,23 @@ open class ReferencedProperty( private var isResolved = false @OptIn(ExperimentalStdlibApi::class) + @Suppress("DEPRECATION") fun resolve(gson: Gson) { if (!isResolved) { isResolved = true - adapter = gson.getAdapter(TypeToken.get(property.returnType.javaType)) as TypeAdapter + val token = TypeToken.get(type.javaType) + + if (token.rawType === KOptional::class.java && type.arguments.size == 1) { + val subtype = type.arguments[0] + + if (subtype.type!!.isMarkedNullable) { + adapter = gson.getAdapter(TypeToken.getParameterized(KOptional.Nullable::class.java, subtype.type!!.javaType)) as TypeAdapter + } else { + adapter = gson.getAdapter(TypeToken.getParameterized(KOptional.NotNull::class.java, subtype.type!!.javaType)) as TypeAdapter + } + } else { + adapter = gson.getAdapter(token) as TypeAdapter + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt index 93563b42..2c5b3435 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Either.kt @@ -52,10 +52,12 @@ class Either private constructor(val left: KOptional, val right: KOptio } companion object { + @JvmStatic fun left(value: L): Either { return Either(KOptional.of(value), KOptional.empty()) } + @JvmStatic fun right(value: R): Either { return Either(KOptional.empty(), KOptional.of(value)) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt index 17a10b43..3df464e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/KOptional.kt @@ -1,6 +1,8 @@ package ru.dbotthepony.kstarbound.util fun KOptional(value: T) = KOptional.of(value) +fun KOptional(value: Boolean) = KOptional.of(value) +fun KOptional(value: Boolean?) = KOptional.of(value) /** * [java.util.Optional] supporting nulls @@ -27,6 +29,39 @@ class KOptional private constructor(private val _value: T, val isPresent: Boo } } + inline fun map(block: (T) -> R): KOptional { + if (isPresent) { + return of(block.invoke(value)) + } else { + return empty() + } + } + + @Suppress("NOTHING_TO_INLINE") + inline fun orElse(value: T): T { + if (isPresent) { + return this.value + } else { + return value + } + } + + inline fun orElse(value: () -> T): T { + if (isPresent) { + return this.value + } else { + return value.invoke() + } + } + + infix fun or(value: KOptional): KOptional { + if (isPresent) { + return this + } else { + return value + } + } + override fun equals(other: Any?): Boolean { return this === other || other is KOptional<*> && isPresent == other.isPresent && _value == other._value } @@ -43,9 +78,17 @@ class KOptional private constructor(private val _value: T, val isPresent: Boo } } + // gson hack since type token can't diff between nullable and non-null types + @Deprecated("internal class") + internal class Nullable private constructor() + @Deprecated("internal class") + internal class NotNull private constructor() + companion object { private val EMPTY = KOptional(null, false) private val NULL = KOptional(null, true) + private val TRUE = KOptional(true, true) + private val FALSE = KOptional(false, true) @JvmStatic fun empty(): KOptional { @@ -60,5 +103,25 @@ class KOptional private constructor(private val _value: T, val isPresent: Boo return KOptional(value, true) } } + + @JvmStatic + fun of(value: Boolean): KOptional { + if (value) { + return TRUE + } else { + return FALSE + } + } + + @JvmStatic + fun of(value: Boolean?): KOptional { + if (value == null) { + return NULL as KOptional + } else if (value) { + return TRUE + } else { + return FALSE + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt index 69dce69f..089b55f6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt @@ -37,7 +37,7 @@ enum class RayFilterResult(val hit: Boolean, val write: Boolean) { companion object { fun of(boolean: Boolean): RayFilterResult { - return if (boolean) HIT else CONTINUE + return if (boolean) HIT else SKIP } } }