AssetReference, AssetPathStack и корректировка существующих адаптеров

This commit is contained in:
DBotThePony 2023-02-05 00:39:18 +07:00
parent c57558af20
commit 6584087842
Signed by: DBot
GPG Key ID: DCC23B5715498507
22 changed files with 303 additions and 280 deletions

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.kstarbound package ru.dbotthepony.kstarbound
import com.google.common.collect.ImmutableList
import com.google.common.collect.Interner import com.google.common.collect.Interner
import com.google.common.collect.Interners import com.google.common.collect.Interners
import com.google.gson.* import com.google.gson.*
@ -15,10 +14,8 @@ import ru.dbotthepony.kstarbound.api.NonExistingFile
import ru.dbotthepony.kstarbound.api.PhysicalFile import ru.dbotthepony.kstarbound.api.PhysicalFile
import ru.dbotthepony.kstarbound.api.explore import ru.dbotthepony.kstarbound.api.explore
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration import ru.dbotthepony.kstarbound.defs.image.ImageReference
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.ArmorItemPrototype
import ru.dbotthepony.kstarbound.defs.item.ArmorPieceType
import ru.dbotthepony.kstarbound.defs.item.BackArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.BackArmorItemPrototype
import ru.dbotthepony.kstarbound.defs.item.ChestArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.ChestArmorItemPrototype
import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype import ru.dbotthepony.kstarbound.defs.item.CurrencyItemPrototype
@ -26,14 +23,9 @@ import ru.dbotthepony.kstarbound.defs.item.FlashlightPrototype
import ru.dbotthepony.kstarbound.defs.item.HarvestingToolPrototype import ru.dbotthepony.kstarbound.defs.item.HarvestingToolPrototype
import ru.dbotthepony.kstarbound.defs.item.HeadArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.HeadArmorItemPrototype
import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.IArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.IFossilItemDefinition
import ru.dbotthepony.kstarbound.defs.item.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.IItemDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemPrototype import ru.dbotthepony.kstarbound.defs.item.ItemPrototype
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
import ru.dbotthepony.kstarbound.defs.item.ItemTooltipKind
import ru.dbotthepony.kstarbound.defs.item.LegsArmorItemPrototype import ru.dbotthepony.kstarbound.defs.item.LegsArmorItemPrototype
import ru.dbotthepony.kstarbound.defs.item.LeveledStatusEffect
import ru.dbotthepony.kstarbound.defs.item.LiquidItemPrototype import ru.dbotthepony.kstarbound.defs.item.LiquidItemPrototype
import ru.dbotthepony.kstarbound.defs.item.MaterialItemPrototype import ru.dbotthepony.kstarbound.defs.item.MaterialItemPrototype
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
@ -41,25 +33,16 @@ import ru.dbotthepony.kstarbound.defs.parallax.ParallaxPrototype
import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition
import ru.dbotthepony.kstarbound.defs.projectile.* import ru.dbotthepony.kstarbound.defs.projectile.*
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
import ru.dbotthepony.kstarbound.defs.tile.RenderTemplate
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.defs.world.SkyParameters
import ru.dbotthepony.kstarbound.defs.world.dungeon.DungeonWorldDef
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter
import ru.dbotthepony.kstarbound.io.json.AABBiTypeAdapter
import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter
import ru.dbotthepony.kstarbound.io.json.Vector2dTypeAdapter
import ru.dbotthepony.kstarbound.io.json.Vector2fTypeAdapter
import ru.dbotthepony.kstarbound.io.json.Vector2iTypeAdapter
import ru.dbotthepony.kstarbound.io.json.Vector4iTypeAdapter
import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementationTypeFactory
import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory import ru.dbotthepony.kstarbound.io.json.factory.ArrayListAdapterFactory
import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.io.json.factory.ImmutableCollectionAdapterFactory
import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.math.*
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.WriteOnce
import java.io.* import java.io.*
import java.text.DateFormat import java.text.DateFormat
@ -80,36 +63,11 @@ fun String.sbIntern(): String = Starbound.STRING_INTERNER.intern(this)
object Starbound { object Starbound {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
/** val STRING_INTERNER: Interner<String> = Interners.newWeakInterner()
* Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке val pathStack = AssetPathStack(STRING_INTERNER)
*/
var assetFolder by ThreadLocal<String>()
private set
fun assetFolder(input: String): String { fun assetFolder(input: String): String {
val assetFolder = assetFolder return pathStack.remap(input)
require(assetFolder != null) { "Not reading an asset on current thread" }
if (input[0] == '/')
return input
return STRING_INTERNER.intern("$assetFolder/$input")
}
fun assetFolderNullable(input: String?): String? {
require(assetFolder != null) { "Not reading an asset on current thread" }
if (input != null)
return assetFolder(input)
return null
}
fun readingFolderListTransformer(input: List<String>?): List<String>? {
if (input == null)
return null
return input.stream().map { assetFolder(it) }.collect(ImmutableList.toImmutableList())
} }
private val tiles = Object2ObjectOpenHashMap<String, TileDefinition>() private val tiles = Object2ObjectOpenHashMap<String, TileDefinition>()
@ -125,6 +83,7 @@ object Starbound {
private val parallax = Object2ObjectOpenHashMap<String, ParallaxPrototype>() private val parallax = Object2ObjectOpenHashMap<String, ParallaxPrototype>()
private val functions = Object2ObjectOpenHashMap<String, JsonFunction>() private val functions = Object2ObjectOpenHashMap<String, JsonFunction>()
private val species = Object2ObjectOpenHashMap<String, Species>() private val species = Object2ObjectOpenHashMap<String, Species>()
private val statusEffects = Object2ObjectOpenHashMap<String, StatusEffectDefinition>()
private val items = Object2ObjectOpenHashMap<String, IItemDefinition>() private val items = Object2ObjectOpenHashMap<String, IItemDefinition>()
@ -139,8 +98,6 @@ object Starbound {
val FUNCTION: Map<String, JsonFunction> = Collections.unmodifiableMap(functions) val FUNCTION: Map<String, JsonFunction> = Collections.unmodifiableMap(functions)
val ITEM: Map<String, IItemDefinition> = Collections.unmodifiableMap(items) val ITEM: Map<String, IItemDefinition> = Collections.unmodifiableMap(items)
val STRING_INTERNER: Interner<String> = Interners.newWeakInterner()
val STRING_ADAPTER: TypeAdapter<String> = object : TypeAdapter<String>() { val STRING_ADAPTER: TypeAdapter<String> = object : TypeAdapter<String>() {
override fun write(out: JsonWriter, value: String?) { override fun write(out: JsonWriter, value: String?) {
if (value == null) if (value == null)
@ -184,6 +141,20 @@ object Starbound {
.also(::addStarboundJsonAdapters) .also(::addStarboundJsonAdapters)
.registerTypeAdapterFactory(IItemDefinition.InventoryIcon.Factory(pathStack))
.registerTypeAdapter(SpriteReference.Adapter(pathStack))
.registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.Factory(pathStack))
.registerTypeAdapter(ImageReference.Adapter(pathStack))
.registerTypeAdapterFactory(AssetReferenceFactory(pathStack) {
val file = locate(it)
if (file.exists && file.isFile)
file.reader()
else
null
})
.registerTypeAdapterFactory(RegistryReferenceFactory() .registerTypeAdapterFactory(RegistryReferenceFactory()
.add(tiles::get) .add(tiles::get)
.add(tileModifiers::get) .add(tileModifiers::get)
@ -193,6 +164,7 @@ object Starbound {
.add(functions::get) .add(functions::get)
.add(items::get) .add(items::get)
.add(species::get) .add(species::get)
.add(statusEffects::get)
) )
.create() .create()
@ -352,9 +324,9 @@ object Starbound {
loadStage(callback, this::loadItemDefinitions, "item definitions") loadStage(callback, this::loadItemDefinitions, "item definitions")
loadStage(callback, this::loadSpecies, "species") loadStage(callback, this::loadSpecies, "species")
assetFolder = "/" pathStack.block("/") {
playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java)
assetFolder = null }
initializing = false initializing = false
initialized = true initialized = true
@ -381,16 +353,12 @@ object Starbound {
} }
private fun loadTileMaterials(callback: (String) -> Unit) { private fun loadTileMaterials(callback: (String) -> Unit) {
assetFolder = "/tiles/materials"
for (fs in fileSystems) { for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
assetFolder = listedFile.computeDirectory() val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), TileDefinition::class.java) }
val tileDef = GSON.fromJson(listedFile.reader(), TileDefinition::class.java)
check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" }
check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" } check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" }
tilesByMaterialID[tileDef.materialId] = tileDef tilesByMaterialID[tileDef.materialId] = tileDef
@ -405,8 +373,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadProjectiles(callback: (String) -> Unit) { private fun loadProjectiles(callback: (String) -> Unit) {
@ -415,8 +381,7 @@ object Starbound {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
assetFolder = listedFile.computeDirectory() val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(it) }
val def = GSON.fromJson(listedFile.reader(), ConfigurableProjectile::class.java).assemble(listedFile.computeDirectory())
check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" } check(projectiles[def.projectileName] == null) { "Already has projectile with ID ${def.projectileName} loaded!" }
projectiles[def.projectileName] = def projectiles[def.projectileName] = def
} catch(err: Throwable) { } catch(err: Throwable) {
@ -429,8 +394,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadFunctions(callback: (String) -> Unit) { private fun loadFunctions(callback: (String) -> Unit) {
@ -439,11 +402,10 @@ object Starbound {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
assetFolder = listedFile.computeDirectory()
val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject val readObject = JsonParser.parseReader(listedFile.reader()) as JsonObject
for (key in readObject.keySet()) { for (key in readObject.keySet()) {
val def = GSON.fromJson(readObject[key], JsonFunction::class.java) val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(readObject[key], JsonFunction::class.java) }
functions[key] = def functions[key] = def
} }
} catch(err: Throwable) { } catch(err: Throwable) {
@ -455,8 +417,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadParallax(callback: (String) -> Unit) { private fun loadParallax(callback: (String) -> Unit) {
@ -464,9 +424,7 @@ object Starbound {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".parallax") }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), ParallaxPrototype::class.java) }
assetFolder = listedFile.computeDirectory()
val def = GSON.fromJson(listedFile.reader(), ParallaxPrototype::class.java)
parallax[listedFile.name.substringBefore('.')] = def parallax[listedFile.name.substringBefore('.')] = def
} catch(err: Throwable) { } catch(err: Throwable) {
LOGGER.error("Loading parallax file $listedFile", err) LOGGER.error("Loading parallax file $listedFile", err)
@ -477,21 +435,14 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadMaterialModifiers(callback: (String) -> Unit) { private fun loadMaterialModifiers(callback: (String) -> Unit) {
assetFolder = "/tiles/materials"
for (fs in fileSystems) { for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), MaterialModifier::class.java) }
assetFolder = listedFile.computeDirectory()
val tileDef = GSON.fromJson(listedFile.reader(), MaterialModifier::class.java)
check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" } check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" }
check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" } check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" }
tileModifiersByID[tileDef.modId] = tileDef tileModifiersByID[tileDef.modId] = tileDef
@ -506,8 +457,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadLiquidDefinitions(callback: (String) -> Unit) { private fun loadLiquidDefinitions(callback: (String) -> Unit) {
@ -515,10 +464,7 @@ object Starbound {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val liquidDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), LiquidDefinition::class.java) }
assetFolder = listedFile.computeDirectory()
val liquidDef = GSON.fromJson(listedFile.reader(), LiquidDefinition::class.java)
check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" } check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" }
check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" } check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" }
} catch (err: Throwable) { } catch (err: Throwable) {
@ -531,8 +477,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadSpecies(callback: (String) -> Unit) { private fun loadSpecies(callback: (String) -> Unit) {
@ -540,9 +484,7 @@ object Starbound {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".species") }) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".species") }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), Species::class.java) }
assetFolder = listedFile.computeDirectory()
val def = GSON.fromJson(listedFile.reader(), Species::class.java)
check(species.put(def.kind, def) == null) { "Already has liquid with name ${def.kind} loaded!" } check(species.put(def.kind, def) == null) { "Already has liquid with name ${def.kind} loaded!" }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Loading species definition file $listedFile", err) LOGGER.error("Loading species definition file $listedFile", err)
@ -553,8 +495,6 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
private fun loadItemDefinitions(callback: (String) -> Unit) { private fun loadItemDefinitions(callback: (String) -> Unit) {
@ -575,10 +515,7 @@ object Starbound {
for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.keys.any { f.name.endsWith(it) } }) { for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.keys.any { f.name.endsWith(it) } }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
val def: ItemPrototype = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value) }
assetFolder = listedFile.computeDirectory()
val def: ItemPrototype = GSON.fromJson(listedFile.reader(), files.entries.first { listedFile.name.endsWith(it.key) }.value)
check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" } check(items.put(def.itemName, def.assemble()) == null) { "Already has item with name ${def.itemName} loaded!" }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Loading item definition file $listedFile", err) LOGGER.error("Loading item definition file $listedFile", err)
@ -589,7 +526,5 @@ object Starbound {
} }
} }
} }
assetFolder = null
} }
} }

View File

@ -75,17 +75,12 @@ fun addStarboundJsonAdapters(builder: GsonBuilder) {
registerTypeAdapterFactory(ParallaxPrototypeLayer.ADAPTER) registerTypeAdapterFactory(ParallaxPrototypeLayer.ADAPTER)
registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER) registerTypeAdapter(ParallaxPrototypeLayer.LAYER_PARALLAX_ADAPTER)
// Предметы
registerTypeAdapterFactory(IItemDefinition.InventoryIcon.ADAPTER)
registerTypeAdapterFactory(IArmorItemDefinition.ArmorFrames.ADAPTER)
// Функции // Функции
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER) registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER) registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
registerTypeAdapter(JsonFunction.Companion) registerTypeAdapter(JsonFunction.Companion)
// Общее // Общее
registerTypeAdapter(SpriteReference.Companion)
registerTypeAdapterFactory(AtlasConfiguration.Companion) registerTypeAdapterFactory(AtlasConfiguration.Companion)
registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER) registerTypeAdapterFactory(LeveledStatusEffect.ADAPTER)

View File

@ -183,7 +183,7 @@ private class ModifierEqualityTester(val definition: MaterialModifier) : Equalit
} }
class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) { class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
val texture = state.loadNamedTexture(def.renderParameters.absoluteTexturePath).also { val texture = state.loadNamedTexture(def.renderParameters.texture.image).also {
it.textureMagFilter = GL_NEAREST it.textureMagFilter = GL_NEAREST
} }
@ -293,7 +293,7 @@ class TileRenderer(val state: GLStateTracker, val def: IRenderableTile) {
fun tesselate(self: ITileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) { fun tesselate(self: ITileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
// если у нас нет renderTemplate // если у нас нет renderTemplate
// то мы просто не можем его отрисовать // то мы просто не можем его отрисовать
val template = def.renderTemplate val template = def.renderTemplate.value ?: return
val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder) val vertexBuilder = layers.computeIfAbsent(if (background) bakedBackgroundProgramState else bakedProgramState, def.renderParameters.zLevel, ::vertexTextureBuilder)

View File

@ -0,0 +1,81 @@
package ru.dbotthepony.kstarbound.defs
import com.google.gson.Gson
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.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import ru.dbotthepony.kstarbound.util.AssetPathStack
import java.io.Reader
import java.lang.reflect.ParameterizedType
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* Данный [AssetReferenceFactory] реализует возможности чтения данных по "ссылке" на файл.
*
* Созданный [TypeAdapter] имеет встроенный кеш.
*/
class AssetReferenceFactory(val remapper: AssetPathStack, val reader: (String) -> Reader?) : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == AssetReference::class.java) {
val param = type.type as? ParameterizedType ?: return null
return object : TypeAdapter<AssetReference<Any>>() {
private val cache = ConcurrentHashMap<String, Any>()
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<Any>
private val strings = gson.getAdapter(String::class.java)
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
override fun write(out: JsonWriter, value: AssetReference<Any>?) {
if (value == null)
out.nullValue()
else
out.value(value.fullPath)
}
override fun read(`in`: JsonReader): AssetReference<Any>? {
if (`in`.peek() == JsonToken.NULL) {
return null
} else if (`in`.peek() == JsonToken.STRING) {
val path = strings.read(`in`)!!
val fullPath = remapper.remap(path)
val get = cache[fullPath]
if (get != null)
return AssetReference(path, fullPath, get)
if (fullPath in missing)
return null
val reader = reader.invoke(fullPath)
if (reader == null) {
missing.add(fullPath)
return AssetReference(path, fullPath, null)
}
val value = remapper(fullPath) {
adapter.read(JsonReader(reader).also {
it.isLenient = true
}) ?: return AssetReference(path, fullPath, null)
}
cache[fullPath] = value
return AssetReference(path, fullPath, value)
} else {
val value = adapter.read(`in`) ?: return null
return AssetReference(null, null, value)
}
}
} as TypeAdapter<T>
}
return null
}
}
data class AssetReference<V>(val path: String?, val fullPath: String?, val value: V?)

View File

@ -25,6 +25,7 @@ data class Species(
val undyColor: ImmutableList<ColorReplacements>, val undyColor: ImmutableList<ColorReplacements>,
val hairColor: ImmutableList<ColorReplacements>, val hairColor: ImmutableList<ColorReplacements>,
val genders: ImmutableList<Gender>, val genders: ImmutableList<Gender>,
val statusEffects: ImmutableSet<RegistryReference<StatusEffectDefinition>> = ImmutableSet.of(),
) { ) {
@JsonFactory @JsonFactory
data class Tooltip(val title: String, val subTitle: String, val description: String) data class Tooltip(val title: String, val subTitle: String, val description: String)

View File

@ -0,0 +1,13 @@
package ru.dbotthepony.kstarbound.defs
import com.google.common.collect.ImmutableList
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
@JsonFactory
data class StatusEffectDefinition(
val name: String,
val defaultDuration: Double,
val scripts: ImmutableList<String>,
val animationConfig: AssetReference<AnimationDefinition>,
)

View File

@ -6,7 +6,7 @@ import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.ndouble.Vector2d
@JsonFactory(asReference = true) @JsonFactory
data class AnimationDefinition( data class AnimationDefinition(
val animatedParts: AnimatedParts, val animatedParts: AnimatedParts,
val sounds: ImmutableMap<String, Sound> = ImmutableMap.of(), val sounds: ImmutableMap<String, Sound> = ImmutableMap.of(),

View File

@ -6,6 +6,7 @@ import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.util.AssetPathStack
/** /**
* Хранит данные (пару) вида "/example/animated.png" у которого, вероятнее всего, есть "/example/animated.frames" * Хранит данные (пару) вида "/example/animated.png" у которого, вероятнее всего, есть "/example/animated.frames"
@ -23,23 +24,28 @@ data class ImageReference(
*/ */
constructor(image: String) : this(image, AtlasConfiguration.get(image)) constructor(image: String) : this(image, AtlasConfiguration.get(image))
companion object : TypeAdapter<ImageReference>() { class Adapter(val remapper: AssetPathStack) : TypeAdapter<ImageReference>() {
override fun write(out: JsonWriter, value: ImageReference) { override fun write(out: JsonWriter, value: ImageReference?) {
out.value(value.image) if (value == null)
out.nullValue()
else
out.value(value.image)
} }
override fun read(`in`: JsonReader): ImageReference { override fun read(`in`: JsonReader): ImageReference? {
if (`in`.peek() == JsonToken.STRING) { if (`in`.peek() == JsonToken.NULL)
val image = Starbound.assetFolder(`in`.nextString()) return null
if (image.contains(':')) { if (`in`.peek() == JsonToken.STRING) {
val image = remapper.remap(`in`.nextString())
if (image.contains(':'))
throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image") throw JsonSyntaxException("Expected atlas/image reference, but got sprite reference: $image")
}
return ImageReference(image, AtlasConfiguration.get(image)) return ImageReference(image, AtlasConfiguration.get(image))
} }
throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()}") throw JsonSyntaxException("Expected atlas/image reference, but got: ${`in`.peek()} near ${`in`.path}")
} }
} }
} }

View File

@ -4,6 +4,7 @@ import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.util.AssetPathStack
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
/** /**
@ -21,7 +22,17 @@ data class SpriteReference(
return atlas[resolved] ?: atlas.any() return atlas[resolved] ?: atlas.any()
} }
companion object : TypeAdapter<SpriteReference>() { class Adapter(val remapper: AssetPathStack) : TypeAdapter<SpriteReference>() {
override fun write(out: JsonWriter, value: SpriteReference) {
out.value(value.image + ":" + value.sprite.raw)
}
override fun read(`in`: JsonReader): SpriteReference {
return parse(remapper.remap(`in`.nextString()))
}
}
companion object {
fun parse(input: String): SpriteReference { fun parse(input: String): SpriteReference {
val grid = AtlasConfiguration.get(input.substringBefore(':')) val grid = AtlasConfiguration.get(input.substringBefore(':'))
@ -31,13 +42,5 @@ data class SpriteReference(
else -> throw IllegalArgumentException("Invalid sprite reference: $input") else -> throw IllegalArgumentException("Invalid sprite reference: $input")
} }
} }
override fun write(out: JsonWriter, value: SpriteReference) {
out.value(value.image + ":" + value.sprite.raw)
}
override fun read(`in`: JsonReader): SpriteReference {
return parse(Starbound.assetFolder(`in`.nextString()))
}
} }
} }

View File

@ -1,9 +1,17 @@
package ru.dbotthepony.kstarbound.defs.item package ru.dbotthepony.kstarbound.defs.item
import ru.dbotthepony.kstarbound.Starbound import com.google.gson.Gson
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.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.defs.image.AtlasConfiguration
import ru.dbotthepony.kstarbound.defs.image.ImageReference
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
import ru.dbotthepony.kstarbound.io.json.ifString import ru.dbotthepony.kstarbound.util.AssetPathStack
interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefinition { interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefinition {
/** /**
@ -28,23 +36,50 @@ interface IArmorItemDefinition : ILeveledItemDefinition, IScriptableItemDefiniti
@JsonImplementation(ArmorFrames::class) @JsonImplementation(ArmorFrames::class)
interface IArmorFrames { interface IArmorFrames {
val body: String val body: ImageReference
val backSleeve: String? val backSleeve: ImageReference?
val frontSleeve: String? val frontSleeve: ImageReference?
} }
data class ArmorFrames( data class ArmorFrames(
override val body: String, override val body: ImageReference,
override val backSleeve: String?, override val backSleeve: ImageReference? = null,
override val frontSleeve: String?, override val frontSleeve: ImageReference? = null,
) : IArmorFrames { ) : IArmorFrames {
companion object { class Factory(private val remapper: AssetPathStack) : TypeAdapterFactory {
val ADAPTER = FactoryAdapter.Builder( override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
ArmorFrames::class, if (type.rawType == ArmorFrames::class.java) {
ArmorFrames::body, return object : TypeAdapter<ArmorFrames>() {
ArmorFrames::backSleeve, private val adapter = FactoryAdapter.Builder(
ArmorFrames::frontSleeve, ArmorFrames::class,
).ifString { ArmorFrames(Starbound.assetFolder(it), null, null) } ArmorFrames::body,
ArmorFrames::backSleeve,
ArmorFrames::frontSleeve,
).build(gson)
override fun write(out: JsonWriter, value: ArmorFrames?) {
if (value == null)
out.nullValue()
else
adapter.write(out, value)
}
override fun read(`in`: JsonReader): ArmorFrames? {
if (`in`.peek() == JsonToken.NULL)
return null
if (`in`.peek() == JsonToken.STRING) {
val path = remapper.remap(`in`.nextString())
return ArmorFrames(ImageReference(path, AtlasConfiguration.get(path)), null, null)
}
return adapter.read(`in`)
}
} as TypeAdapter<T>
}
return null
}
} }
} }
} }

View File

@ -1,11 +1,19 @@
package ru.dbotthepony.kstarbound.defs.item package ru.dbotthepony.kstarbound.defs.item
import com.google.gson.Gson
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.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.IThingWithDescription import ru.dbotthepony.kstarbound.defs.IThingWithDescription
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation import ru.dbotthepony.kstarbound.io.json.builder.JsonImplementation
import ru.dbotthepony.kstarbound.io.json.ifString import ru.dbotthepony.kstarbound.io.json.ifString
import ru.dbotthepony.kstarbound.util.AssetPathStack
interface IItemDefinition : IThingWithDescription { interface IItemDefinition : IThingWithDescription {
/** /**
@ -45,8 +53,34 @@ interface IItemDefinition : IThingWithDescription {
data class InventoryIcon( data class InventoryIcon(
override val image: SpriteReference override val image: SpriteReference
) : IInventoryIcon { ) : IInventoryIcon {
companion object { class Factory(val remapper: AssetPathStack) : TypeAdapterFactory {
val ADAPTER = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).ifString { InventoryIcon(SpriteReference.parse(Starbound.assetFolder(it))) } override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
if (type.rawType == InventoryIcon::class.java) {
return object : TypeAdapter<InventoryIcon>() {
private val adapter = FactoryAdapter.Builder(InventoryIcon::class, InventoryIcon::image).build(gson)
override fun write(out: JsonWriter, value: InventoryIcon?) {
if (value == null)
out.nullValue()
else
adapter.write(out, value)
}
override fun read(`in`: JsonReader): InventoryIcon? {
if (`in`.peek() == JsonToken.NULL)
return null
if (`in`.peek() == JsonToken.STRING) {
return InventoryIcon(SpriteReference.parse(remapper.remap(`in`.nextString())))
}
return adapter.read(`in`)
}
} as TypeAdapter<T>
}
return null
}
} }
} }

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import com.google.gson.JsonObject import com.google.gson.JsonObject
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.RegistryReference import ru.dbotthepony.kstarbound.defs.RegistryReference
import ru.dbotthepony.kstarbound.defs.Species import ru.dbotthepony.kstarbound.defs.Species
import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.SBPattern
@ -58,7 +59,7 @@ data class PlayerDefinition(
val splashConfig: SplashConfig, val splashConfig: SplashConfig,
val effectsAnimator: AnimationDefinition, val effectsAnimator: AssetReference<AnimationDefinition>,
val teleportInTime: Double, val teleportInTime: Double,
val teleportOutTime: Double, val teleportOutTime: Double,

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.kstarbound.defs.tile package ru.dbotthepony.kstarbound.defs.tile
import ru.dbotthepony.kstarbound.defs.AssetReference
sealed interface IRenderableTile { sealed interface IRenderableTile {
val renderTemplate: RenderTemplate val renderTemplate: AssetReference<RenderTemplate>
val renderParameters: RenderParameters val renderParameters: RenderParameters
} }

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
@ -17,7 +18,7 @@ data class MaterialModifier(
val grass: Boolean = false, val grass: Boolean = false,
val miningSounds: ImmutableList<String> = ImmutableList.of(), val miningSounds: ImmutableList<String> = ImmutableList.of(),
val miningParticle: String? = null, val miningParticle: String? = null,
override val renderTemplate: RenderTemplate, override val renderTemplate: AssetReference<RenderTemplate>,
override val renderParameters: RenderParameters override val renderParameters: RenderParameters
) : IRenderableTile { ) : IRenderableTile {
init { init {

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.image.ImageReference
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
@ -9,22 +10,10 @@ const val TILE_COLOR_VARIANTS = 9
@JsonFactory @JsonFactory
data class RenderParameters( data class RenderParameters(
val texture: String, val texture: ImageReference,
val variants: Int = 0, val variants: Int = 0,
val multiColored: Boolean = false, val multiColored: Boolean = false,
val occludesBelow: Boolean = false, val occludesBelow: Boolean = false,
val lightTransparent: Boolean = false, val lightTransparent: Boolean = false,
val zLevel: Int, val zLevel: Int,
) { )
val absoluteTexturePath: String
init {
val dir = Starbound.assetFolder
if (dir == null || texture[0] == '/') {
absoluteTexturePath = texture
} else {
absoluteTexturePath = "$dir/$texture"
}
}
}

View File

@ -2,26 +2,13 @@ package ru.dbotthepony.kstarbound.defs.tile
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableMap import com.google.common.collect.ImmutableMap
import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.json.builder.EnumAdapter
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.io.json.util.asReference
import ru.dbotthepony.kstarbound.registerTypeAdapter
import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ITileGetter import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kstarbound.world.ITileState import ru.dbotthepony.kstarbound.world.ITileState
import ru.dbotthepony.kvector.vector.nint.Vector2i import ru.dbotthepony.kvector.vector.nint.Vector2i
import java.util.concurrent.ConcurrentHashMap
@JsonFactory @JsonFactory
data class RenderPiece( data class RenderPiece(
@ -202,7 +189,7 @@ data class RenderMatchList(
} }
} }
@JsonFactory(asReference = true) @JsonFactory
data class RenderTemplate( data class RenderTemplate(
val pieces: ImmutableMap<String, RenderPiece>, val pieces: ImmutableMap<String, RenderPiece>,
val representativePiece: String, val representativePiece: String,

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.kstarbound.defs.tile package ru.dbotthepony.kstarbound.defs.tile
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.io.json.builder.FactoryAdapter
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.registerTypeAdapter import ru.dbotthepony.kstarbound.registerTypeAdapter
@ -22,6 +23,6 @@ data class TileDefinition(
val health: Double = 0.0, val health: Double = 0.0,
val category: String, val category: String,
override val renderTemplate: RenderTemplate, override val renderTemplate: AssetReference<RenderTemplate>,
override val renderParameters: RenderParameters, override val renderParameters: RenderParameters,
) : IRenderableTile ) : IRenderableTile

View File

@ -1,6 +1,5 @@
package ru.dbotthepony.kstarbound.io.json.builder package ru.dbotthepony.kstarbound.io.json.builder
import ru.dbotthepony.kstarbound.io.json.util.ReferenceAdapter
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory import com.google.gson.TypeAdapterFactory
@ -42,11 +41,6 @@ annotation class JsonBuilder(
* Включать ли свойства родительского класса в данный [BuilderAdapter] * Включать ли свойства родительского класса в данный [BuilderAdapter]
*/ */
val includeSuperclassProperties: Boolean = true, val includeSuperclassProperties: Boolean = true,
/**
* Оборачивает созданный [BuilderAdapter] в [ReferenceAdapter]
*/
val asReference: Boolean = false,
) )
val JsonBuilder.realLogMisses get() = logMisses.toBool() val JsonBuilder.realLogMisses get() = logMisses.toBool()
@ -102,11 +96,6 @@ annotation class JsonFactory(
* @see FactoryAdapter.Builder.inputAsMap * @see FactoryAdapter.Builder.inputAsMap
*/ */
val asList: Boolean = false, val asList: Boolean = false,
/**
* Оборачивает созданный [FactoryAdapter] в [ReferenceAdapter]
*/
val asReference: Boolean = false,
) )
/** /**

View File

@ -20,11 +20,9 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
import ru.dbotthepony.kstarbound.io.json.util.asReference import ru.dbotthepony.kstarbound.io.json.builder.BuilderAdapter.Builder
import ru.dbotthepony.kstarbound.util.NotNullVar import ru.dbotthepony.kstarbound.util.NotNullVar
import java.util.concurrent.ConcurrentHashMap
import kotlin.properties.Delegates import kotlin.properties.Delegates
import kotlin.reflect.KCallable
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.declaredMembers import kotlin.reflect.full.declaredMembers
@ -423,10 +421,6 @@ class BuilderAdapter<T : Any> private constructor(
} }
} }
if (bconfig.asReference) {
return builder.build(gson).asReference()
}
return builder.build(gson) return builder.build(gson)
} }

View File

@ -24,20 +24,13 @@ import ru.dbotthepony.kstarbound.defs.util.enrollMap
import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement import ru.dbotthepony.kstarbound.defs.util.flattenJsonElement
import ru.dbotthepony.kstarbound.getValue import ru.dbotthepony.kstarbound.getValue
import ru.dbotthepony.kstarbound.io.json.ifString import ru.dbotthepony.kstarbound.io.json.ifString
import ru.dbotthepony.kstarbound.io.json.util.LazyTypeProvider
import ru.dbotthepony.kstarbound.io.json.util.ListAdapter
import ru.dbotthepony.kstarbound.io.json.util.MapAdapter
import ru.dbotthepony.kstarbound.io.json.util.String2ObjectAdapter
import ru.dbotthepony.kstarbound.io.json.util.asReference
import ru.dbotthepony.kstarbound.setValue import ru.dbotthepony.kstarbound.setValue
import java.lang.reflect.Constructor import java.lang.reflect.Constructor
import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.jvm.internal.DefaultConstructorMarker
import kotlin.properties.Delegates import kotlin.properties.Delegates
import kotlin.reflect.* import kotlin.reflect.*
import kotlin.reflect.full.declaredMembers import kotlin.reflect.full.declaredMembers
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.isSupertypeOf import kotlin.reflect.full.isSupertypeOf
import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.primaryConstructor
@ -572,10 +565,6 @@ class FactoryAdapter<T : Any> private constructor(
} }
} }
if (bconfig.asReference) {
return builder.build(gson).asReference()
}
return builder.build(gson) return builder.build(gson)
} }

View File

@ -1,79 +0,0 @@
package ru.dbotthepony.kstarbound.io.json.util
import com.google.gson.Gson
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.JsonToken
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.Starbound
import java.util.concurrent.ConcurrentHashMap
/**
* Данный [TypeAdapter] реализует возможности чтения данных по "ссылке" на файл.
*
* К примеру, если в оригинальной структуре может (или обычно) встречаться такое:
*
* ```json
* {
* "thing": { ... },
* }
* ```
*
* и такое:
*
* ```json
* {
* "thing": "/path/to/thing.config"
* }
* ```
*
* То второй вариант будет прочитан из указанного пути и закеширован внутри [ReferenceAdapter]
*/
class ReferenceAdapter<T>(val parent: TypeAdapter<T>) : TypeAdapter<T>() {
private val cache = ConcurrentHashMap<String, T>()
override fun write(out: JsonWriter, value: T) {
parent.write(out, value)
}
override fun read(reader: JsonReader): T {
if (reader.peek() != JsonToken.STRING) {
return parent.read(reader)
}
var path = reader.nextString()
if (path[0] != '/') {
// относительный путь
val readingFolder = Starbound.assetFolder ?: throw NullPointerException("Currently read folder is not specified")
path = "$readingFolder/$path"
}
return cache.computeIfAbsent(path) {
return@computeIfAbsent parent.read(JsonReader(Starbound.locate(it).reader()).also { it.isLenient = true })
}
}
}
class ReferenceAdapterFactory(val parent: TypeAdapterFactory) : TypeAdapterFactory {
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
val candidate = parent.create(gson, type)
if (candidate != null) {
return ReferenceAdapter(candidate)
}
return null
}
}
fun <T> TypeAdapter<T>.asReference(): TypeAdapter<T> {
return ReferenceAdapter(this)
}
fun TypeAdapterFactory.asReference(): TypeAdapterFactory {
return ReferenceAdapterFactory(this)
}

View File

@ -0,0 +1,46 @@
package ru.dbotthepony.kstarbound.util
import com.google.common.collect.Interner
import kotlin.concurrent.getOrSet
class AssetPathStack(private val interner: Interner<String>? = null) {
private val _stack = ThreadLocal<ArrayDeque<String>>()
private val stack: ArrayDeque<String> get() = _stack.getOrSet { ArrayDeque() }
fun push(value: String) {
stack.addLast(value)
}
fun pushFilename(value: String) {
push(value.substringBefore(':').substringBeforeLast('/'))
}
fun last() = stack.lastOrNull()
fun pop() = stack.removeLast()
inline fun <T> block(path: String, block: (String) -> T): T {
try {
push(path)
return block.invoke(path)
} finally {
pop()
}
}
inline operator fun <T> invoke(path: String, block: (String) -> T) = block(path, block)
private fun remap(a: String, b: String): String {
if (b[0] == '/')
return b
return interner?.intern("$a/$b") ?: "$a/$b"
}
fun remap(path: String): String {
return remap(checkNotNull(last()) { "Not reading an asset on current thread" }, path)
}
fun remapSafe(path: String): String? {
return remap(last() ?: return null, path)
}
}