KOptional type adapter, and movement controller defs structures

This commit is contained in:
DBotThePony 2023-09-29 20:26:45 +07:00
parent c0ecbe9a8b
commit 86782e259e
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 363 additions and 48 deletions

View File

@ -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 <K, V> ImmutableMap.Builder<K, V>.set(key: K, value: V): ImmutableM
fun String.sintern(): String = Starbound.strings.intern(this)
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)

View File

@ -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")

View File

@ -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

View File

@ -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<Double> = KOptional.empty(),
val jumpControlForce: KOptional<Double> = KOptional.empty(),
val jumpInitialPercentage: KOptional<Double> = KOptional.empty(),
val jumpHoldTime: KOptional<Double> = KOptional.empty(),
val jumpTotalHoldTime: KOptional<Double> = KOptional.empty(),
val multiJump: KOptional<Boolean> = KOptional.empty(),
val reJumpDelay: KOptional<Double> = KOptional.empty(),
val autoJump: KOptional<Boolean> = KOptional.empty(),
val collisionCancelled: KOptional<Boolean> = 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),
)
}
}

View File

@ -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,
)
}
}

View File

@ -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<Double> = KOptional.empty(),
val gravityMultiplier: KOptional<Double> = KOptional.empty(),
val liquidBuoyancy: KOptional<Double> = KOptional.empty(),
val airBuoyancy: KOptional<Double> = KOptional.empty(),
val bounceFactor: KOptional<Double> = KOptional.empty(),
val stopOnFirstBounce: KOptional<Boolean> = KOptional.empty(),
val enableSurfaceSlopeCorrection: KOptional<Boolean> = KOptional.empty(),
val slopeSlidingFactor: KOptional<Double> = KOptional.empty(),
val maxMovementPerStep: KOptional<Double> = KOptional.empty(),
val maximumCorrection: KOptional<Double> = KOptional.empty(),
val speedLimit: KOptional<Double> = KOptional.empty(),
val runSpeed: Double? = null,
val walkSpeed: Double? = null,
val mass: Double? = null,
@JsonAlias("collisionPoly")
val standingPoly: KOptional<ImmutableList<Vector2d>> = KOptional.empty(),
@JsonAlias("collisionPoly")
val crouchingPoly: KOptional<ImmutableList<Vector2d>> = KOptional.empty(),
// TODO: А оно вообще используется? Как по мне движок старбаунда генерирует коллизию из пикселей текстуры
val collisionPoly: ImmutableList<Vector2d>? = null,
val crouchingPoly: ImmutableList<Vector2d>? = null,
val standingPoly: ImmutableList<Vector2d>? = null,
)
val stickyCollision: KOptional<Boolean> = KOptional.empty(),
val stickyForce: KOptional<Double> = KOptional.empty(),
val walkSpeed: KOptional<Double> = KOptional.empty(),
val runSpeed: KOptional<Double> = KOptional.empty(),
val flySpeed: KOptional<Double> = KOptional.empty(),
val airFriction: KOptional<Double> = KOptional.empty(),
val liquidFriction: KOptional<Double> = KOptional.empty(),
val minimumLiquidPercentage: KOptional<Double> = KOptional.empty(),
val liquidImpedance: KOptional<Double> = KOptional.empty(),
val normalGroundFriction: KOptional<Double> = KOptional.empty(),
val ambulatingGroundFriction: KOptional<Double> = KOptional.empty(),
val groundForce: KOptional<Double> = KOptional.empty(),
val airForce: KOptional<Double> = KOptional.empty(),
val liquidForce: KOptional<Double> = KOptional.empty(),
val airJumpProfile: JumpProfile = JumpProfile(),
val liquidJumpProfile: JumpProfile = JumpProfile(),
val fallStatusSpeedMin: KOptional<Double> = KOptional.empty(),
val fallThroughSustainFrames: KOptional<Int> = KOptional.empty(),
val maximumPlatformCorrection: KOptional<Double> = KOptional.empty(),
val maximumPlatformCorrectionVelocityFactor: KOptional<Double> = KOptional.empty(),
val physicsEffectCategories: KOptional<ImmutableSet<String>> = KOptional.empty(),
val groundMovementMinimumSustain: KOptional<Double> = KOptional.empty(),
val groundMovementMaximumSustain: KOptional<Double> = KOptional.empty(),
val groundMovementCheckDistance: KOptional<Double> = KOptional.empty(),
val collisionEnabled: KOptional<Boolean> = KOptional.empty(),
val frictionEnabled: KOptional<Boolean> = KOptional.empty(),
val gravityEnabled: KOptional<Boolean> = KOptional.empty(),
val pathExploreRate: KOptional<Double> = 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),
)
}
}

View File

@ -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 <T : Any> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
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<KOptional<Any?>>() {
private val adapter0 = gson.getAdapter(token) as TypeAdapter<Any?>
override fun write(out: JsonWriter, value: KOptional<Any?>?) {
if (value === null) {
out.nullValue()
} else if (value.isPresent) {
adapter0.write(out, value.value)
}
}
override fun read(`in`: JsonReader): KOptional<Any?> {
if (isBool) {
if (notnull) {
return KOptional((adapter0.read(`in`) ?: throw JsonSyntaxException("This KOptional does not accept nulls")) as Boolean) as KOptional<Any?>
} else {
return KOptional(adapter0.read(`in`) as Boolean?) as KOptional<Any?>
}
} else {
if (notnull) {
return KOptional(adapter0.read(`in`) ?: throw JsonSyntaxException("This KOptional does not accept nulls"))
} else {
return KOptional(adapter0.read(`in`))
}
}
}
} as TypeAdapter<T>
} else {
return null
}
}
}

View File

@ -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<T : Any> private constructor(
val clazz: KClass<T>,
val types: ImmutableList<ReferencedProperty<T, *>>,
aliases: Map<String, String>,
aliases: Map<String, List<String>>,
val asJsonArray: Boolean,
val storesJson: Boolean,
val stringInterner: Interner<String>,
val logMisses: Boolean,
private val elements: TypeAdapter<JsonElement>
) : TypeAdapter<T>() {
private val name2index = Object2IntArrayMap<String>()
private val name2index = Object2ObjectArrayMap<String, IntArrayList>()
private val loggedMisses = ObjectArraySet<String>()
init {
@ -57,14 +63,14 @@ class FactoryAdapter<T : Any> 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<T : Any> 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<T : Any> 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<T : Any> 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<JsonElement, JsonReader>
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<T : Any> private constructor(
private var asList = false
private var storesJson = false
private val types = ArrayList<ReferencedProperty<T, *>>()
private val aliases = Object2ObjectArrayMap<String, String>()
private val aliases = Object2ObjectArrayMap<String, ArrayList<String>>()
var stringInterner: Interner<String> = Interner { it }
var logMisses = true
@ -422,7 +445,8 @@ class FactoryAdapter<T : Any> private constructor(
storesJson = storesJson,
stringInterner = stringInterner,
aliases = aliases,
logMisses = logMisses
logMisses = logMisses,
elements = gson.getAdapter(JsonElement::class.java)
)
}
@ -445,7 +469,7 @@ class FactoryAdapter<T : Any> private constructor(
}
fun alias(alias: String, canonical: String): Builder<T> {
aliases[alias] = canonical
aliases.computeIfAbsent(alias, Function { ArrayList() }).add(canonical)
return this
}

View File

@ -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<T : Any, V>(
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<V>
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<V>
} else {
adapter = gson.getAdapter(TypeToken.getParameterized(KOptional.NotNull::class.java, subtype.type!!.javaType)) as TypeAdapter<V>
}
} else {
adapter = gson.getAdapter(token) as TypeAdapter<V>
}
}
}
}

View File

@ -52,10 +52,12 @@ class Either<L, R> private constructor(val left: KOptional<L>, val right: KOptio
}
companion object {
@JvmStatic
fun <L, R> left(value: L): Either<L, R> {
return Either(KOptional.of(value), KOptional.empty())
}
@JvmStatic
fun <L, R> right(value: R): Either<L, R> {
return Either(KOptional.empty(), KOptional.of(value))
}

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.kstarbound.util
fun <T> 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<T> private constructor(private val _value: T, val isPresent: Boo
}
}
inline fun <R> map(block: (T) -> R): KOptional<R> {
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<T>): KOptional<T> {
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<T> 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<T : Any?> private constructor()
@Deprecated("internal class")
internal class NotNull<T : Any> 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 <T> empty(): KOptional<T> {
@ -60,5 +103,25 @@ class KOptional<T> private constructor(private val _value: T, val isPresent: Boo
return KOptional(value, true)
}
}
@JvmStatic
fun of(value: Boolean): KOptional<Boolean> {
if (value) {
return TRUE
} else {
return FALSE
}
}
@JvmStatic
fun of(value: Boolean?): KOptional<Boolean> {
if (value == null) {
return NULL as KOptional<Boolean>
} else if (value) {
return TRUE
} else {
return FALSE
}
}
}
}

View File

@ -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
}
}
}