Some improvement to asset loading and feedback

This commit is contained in:
DBotThePony 2024-04-28 19:44:15 +07:00
parent 0854baa986
commit 4c959b82a9
Signed by: DBot
GPG Key ID: DCC23B5715498507
10 changed files with 123 additions and 57 deletions

View File

@ -4,6 +4,8 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.future.await
import kotlinx.coroutines.launch
@ -147,7 +149,7 @@ object Globals {
LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!")
} else {
try {
AssetPathStack("/") {
AssetPathStack(path) {
accept.set(adapter.value.fromJsonTree(file))
}
} catch (err: Throwable) {
@ -161,6 +163,38 @@ object Globals {
return Starbound.GLOBAL_SCOPE.launch { load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) }) }.asCompletableFuture()
}
private inline fun <reified T : Any> mapAdapter(filename: String): Lazy<TypeAdapter<ImmutableMap<String, T>>> {
val parent by lazy { Starbound.gson.getAdapter(T::class.java) }
return lazy(LazyThreadSafetyMode.NONE) {
object : TypeAdapter<ImmutableMap<String, T>>() {
override fun write(out: JsonWriter, value: ImmutableMap<String, T>) {
TODO("Not yet implemented")
}
override fun read(`in`: JsonReader): ImmutableMap<String, T> {
`in`.beginObject()
val builder = ImmutableMap.Builder<String, T>()
while (`in`.hasNext()) {
val name = `in`.nextName()
val element = Starbound.ELEMENTS_ADAPTER.read(`in`)
try {
builder.put(name, parent.fromJsonTree(element))
} catch (err: Throwable) {
LOGGER.error("Exception reading element at $name from $filename; it will not be loaded", err)
}
}
`in`.endObject()
return builder.build()
}
}
}
}
fun load(): List<Future<*>> {
val tasks = ArrayList<CompletableFuture<*>>()
@ -186,10 +220,10 @@ object Globals {
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, mapAdapter("/dungeon_worlds.config")) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, mapAdapter("/currencies.config")) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, mapAdapter("/system_objects.config")) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, mapAdapter("/instance_worlds.config")) }.asCompletableFuture())
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())

View File

@ -123,7 +123,7 @@ object Registries {
try {
val elem = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()])
AssetPathStack(listedFile.computeDirectory()) {
AssetPathStack(listedFile.computeFullPath()) {
val read = adapter.fromJsonTree(elem)
val keys = keyProvider(read)

View File

@ -98,9 +98,13 @@ class AssetReference<V> {
}
val value = json.thenApplyAsync({ j ->
AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
adapter.fromJsonTree(j)
}
if (j == null) {
null
} else {
AssetPathStack(fullPath.substringBefore(':').substringBefore('?')) {
adapter.fromJsonTree(j)
}
}
}, Starbound.EXECUTOR)
value.exceptionally {

View File

@ -10,6 +10,7 @@ import ru.dbotthepony.kstarbound.io.writeStruct2d
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
@ -38,6 +39,7 @@ sealed class PhysicsForceRegion {
val xTargetVelocity: Double?,
val yTargetVelocity: Double?,
val controlForce: Double,
@JsonFlat
override val filter: PhysicsCategoryFilter,
override val enabled: Boolean = true
) : PhysicsForceRegion() {
@ -69,6 +71,7 @@ sealed class PhysicsForceRegion {
val innerRadius: Double,
val targetRadialVelocity: Double,
val controlForce: Double,
@JsonFlat
override val filter: PhysicsCategoryFilter,
override val enabled: Boolean = true
) : PhysicsForceRegion() {
@ -101,6 +104,7 @@ sealed class PhysicsForceRegion {
val gradient: Line2d,
val baseTargetVelocity: Double,
val baseControlForce: Double,
@JsonFlat
override val filter: PhysicsCategoryFilter,
override val enabled: Boolean = true
) : PhysicsForceRegion() {

View File

@ -72,10 +72,10 @@ data class DungeonDefinition(
tiles.spewWarnings(name)
}
private val directory = AssetPathStack.last()
private val file = AssetPathStack.lastFile()
val actualParts: ImmutableList<DungeonPart> by lazy {
AssetPathStack(directory) {
AssetPathStack(file) {
val build = parts.stream().map { Starbound.gson.fromJson(it, DungeonPart::class.java) }.collect(ImmutableList.toImmutableList())
build.forEach { it.bind(this) }

View File

@ -39,6 +39,7 @@ import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.gson.getObject
import ru.dbotthepony.kstarbound.json.JsonPatch
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.io.BufferedInputStream
import java.io.FileNotFoundException
import java.lang.ref.Reference
@ -378,11 +379,27 @@ class Image private constructor(
val file = Starbound.locate(it)
if (!file.exists) {
throw FileNotFoundException("No such file $it")
val asset = AssetPathStack.lastFile()
if (asset == "/") {
logger.error("No such file $it")
} else {
logger.error("No such file $it (referenced in $asset)")
}
return@computeIfAbsent Optional.empty()
}
if (!file.isFile) {
throw FileNotFoundException("File $it is a directory")
val asset = AssetPathStack.lastFile()
if (asset == "/") {
logger.error("$it is a directory")
} else {
logger.error("$it is a directory (referenced in $asset)")
}
return@computeIfAbsent Optional.empty()
}
val getWidth = intArrayOf(0)
@ -400,7 +417,7 @@ class Image private constructor(
MemoryUtil.memPutByte(buf + i, readBuf[i])
}
return@STBIReadCallbackI read
return@STBIReadCallbackI read.coerceAtLeast(0)
})
val skipCallback = STBISkipCallback.create { _, n -> stream.skip(n.toLong()) }
@ -425,10 +442,18 @@ class Image private constructor(
eofCallback.free()
callback.free()
if (!status)
throw IllegalArgumentException("File $file is not an image or it is corrupted")
if (!status) {
val asset = AssetPathStack.lastFile()
Optional.of(Image(file, it, getWidth[0], getHeight[0], getConfig(it)))
if (asset == "/") {
logger.error("$file is not an image or it is corrupted")
} else {
logger.error("$file is not an image or it is corrupted (referenced in $asset)")
}
Optional.empty()
} else
Optional.of(Image(file, it, getWidth[0], getHeight[0], getConfig(it)))
} catch (err: Exception) {
logger.error("Failed to load image at path $it", err)
Optional.empty()

View File

@ -9,27 +9,23 @@ import com.google.gson.JsonElement
import com.google.gson.JsonNull
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 kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.defs.JsonReference
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
import ru.dbotthepony.kstarbound.defs.tile.TileDamageParameters
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.stream
import ru.dbotthepony.kstarbound.util.PeriodicFunction
import ru.dbotthepony.kommons.gson.contains
import ru.dbotthepony.kommons.gson.get
@ -219,7 +215,7 @@ data class ObjectDefinition(
val orientations = ImmutableList.Builder<Supplier<ObjectOrientation>>()
val path = AssetPathStack.last()
val path = AssetPathStack.lastFile()
for (v in ObjectOrientation.preprocess(read.getArray("orientations"))) {
val future = Starbound.GLOBAL_SCOPE.async { this@Adapter.orientations.read(v as JsonObject, path) }.asCompletableFuture()
@ -275,7 +271,7 @@ data class ObjectDefinition(
soundEffectRangeMultiplier = basic.soundEffectRangeMultiplier,
price = basic.price,
statusEffects = basic.statusEffects,
touchDamage = Starbound.loadJsonAsset(basic.touchDamage, AssetPathStack.last()),
touchDamage = Starbound.loadJsonAsset(basic.touchDamage, AssetPathStack.lastFolder()),
minimumLiquidLevel = basic.minimumLiquidLevel,
maximumLiquidLevel = basic.maximumLiquidLevel,
liquidCheckInterval = basic.liquidCheckInterval,

View File

@ -9,10 +9,7 @@ import com.google.gson.JsonNull
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
import kotlinx.coroutines.future.await
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.gson.clear
@ -24,9 +21,7 @@ import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.defs.Drawable
import ru.dbotthepony.kstarbound.defs.JsonReference
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.json.setAdapter
import ru.dbotthepony.kommons.gson.contains
@ -37,11 +32,9 @@ import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.tile.isEmptyTile
import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.World
import java.util.concurrent.CompletableFuture
import kotlin.math.PI
data class ObjectOrientation(
@ -154,12 +147,12 @@ data class ObjectOrientation(
private val spaces = gson.setAdapter<Vector2i>()
private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter<ImmutableList<Pair<Vector2i, String>>>
suspend fun read(obj: JsonObject, folder: String): ObjectOrientation {
suspend fun read(obj: JsonObject, file: String): ObjectOrientation {
val drawables = ArrayList<Drawable>()
val flipImages = obj.get("flipImages", false)
val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object"))
AssetPathStack(folder) {
AssetPathStack(file) {
if ("imageLayers" in obj) {
for (value in obj["imageLayers"].asJsonArray) {
var result = this.drawables.fromJsonTree(value)
@ -273,7 +266,7 @@ data class ObjectOrientation(
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last()).await()
val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.lastFolder()).await()
val emitters = ArrayList<ParticleEmissionEntry>()

View File

@ -1,7 +1,6 @@
package ru.dbotthepony.kstarbound.util
import ru.dbotthepony.kstarbound.Starbound
import java.io.File
object AssetPathStack {
private val _stack = object : ThreadLocal<ArrayDeque<String>>() {
@ -10,20 +9,35 @@ object AssetPathStack {
}
}
private val stack: ArrayDeque<String> get() = _stack.get()
private val _fileStack = object : ThreadLocal<ArrayDeque<String>>() {
override fun initialValue(): ArrayDeque<String> {
return ArrayDeque()
}
}
private val folderStack: ArrayDeque<String> get() = _stack.get()
private val fileStack: ArrayDeque<String> get() = _fileStack.get()
fun push(value: String) {
stack.addLast(value)
if (value.last() == '/') {
// already folder, we don't have filename
folderStack.addLast(value)
fileStack.addLast(value)
} else {
folderStack.addLast(value.substringBeforeLast('/') + "/")
fileStack.addLast(value)
}
}
fun pushFilename(value: String) {
push(value.substringBefore(':').substringBeforeLast('/'))
fun lastFolder() = folderStack.lastOrNull() ?: "/"
fun lastFile() = fileStack.lastOrNull() ?: "/"
fun pop() {
folderStack.removeLast()
fileStack.removeLast()
}
fun last() = stack.lastOrNull() ?: "/"
fun pop() = stack.removeLast()
inline fun <T> block(path: String, block: (String) -> T): T {
inline operator fun <T> invoke(path: String, block: (String) -> T): T {
try {
push(path)
return block.invoke(path)
@ -32,23 +46,12 @@ object AssetPathStack {
}
}
inline operator fun <T> invoke(path: String, block: (String) -> T) = block(path, block)
private fun remap(a: String, b: String): String {
if (b.isEmpty())
return a
if (b[0] == '/')
return b
return Starbound.STRINGS.intern("$a/$b")
}
fun remap(path: String): String {
return remap(last(), path)
return relativeTo(lastFolder(), path)
}
fun remapSafe(path: String): String {
return remap(last(), path)
return relativeTo(lastFolder(), path)
}
fun relativeTo(base: String, path: String): String {

View File

@ -8,7 +8,7 @@ import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import org.lwjgl.opengl.GL11.GL_LINES
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.math.RGBAColor
@ -30,6 +30,7 @@ import ru.dbotthepony.kstarbound.json.listAdapter
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.LinkedList
@ -417,6 +418,7 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
}
companion object : TypeAdapterFactory {
private val LOGGER = LogManager.getLogger()
val CODEC = nativeCodec(::read, Poly::write)
val LEGACY_CODEC = legacyCodec(::read, Poly::write)
@ -447,8 +449,13 @@ class Poly private constructor(val edges: ImmutableList<Line2d>, val vertices: I
else {
val list = list.read(`in`)
if (list.isEmpty())
if (list.isEmpty()) {
// LOGGER.warn("Accepting empty poly from asset ${AssetPathStack.lastFile()} to match original engine behavior")
return EMPTY
} else if (list.size == 1) {
LOGGER.warn("Accepting degenerate poly with 1 points from asset ${AssetPathStack.lastFile()} to match original engine behavior")
return Poly(listOf(list[0], list[0], list[0], list[0]))
}
return Poly(list)
}