Подгрузка прототипов предметов, немного комментариев в коде

This commit is contained in:
DBotThePony 2022-12-29 15:34:35 +07:00
parent cfe892e220
commit 176ca9db65
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 122 additions and 11 deletions

View File

@ -4,9 +4,18 @@ import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import java.util.Arrays import java.util.Arrays
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.reflect.KProperty
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder { inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
return registerTypeAdapter(T::class.java, adapter) return registerTypeAdapter(T::class.java, adapter)
} }
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this) fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T? {
return get()
}
operator fun <T> ThreadLocal<T>.setValue(thisRef: Any, property: KProperty<*>, value: T?) {
set(value)
}

View File

@ -8,6 +8,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.item.ItemDefinition
import ru.dbotthepony.kstarbound.defs.item.ItemRarity
import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition import ru.dbotthepony.kstarbound.defs.liquid.LiquidDefinition
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
@ -42,14 +44,10 @@ const val PIXELS_IN_STARBOUND_UNITf = 8.0f
object Starbound { object Starbound {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val _readingFolder = ThreadLocal<String>()
/** /**
* Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке * Служит переменной для указания из какой папки происходит чтение asset'а в данном потоке
*/ */
var readingFolder: String? var readingFolder by ThreadLocal<String>()
get() = _readingFolder.get()
private set(value) { _readingFolder.set(value) }
private val tiles = HashMap<String, TileDefinition>() private val tiles = HashMap<String, TileDefinition>()
private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>() private val tilesByMaterialID = Int2ObjectAVLTreeMap<TileDefinition>()
@ -64,6 +62,8 @@ object Starbound {
private val parallax = HashMap<String, ParallaxPrototype>() private val parallax = HashMap<String, ParallaxPrototype>()
private val functions = HashMap<String, JsonFunction>() private val functions = HashMap<String, JsonFunction>()
private val items = HashMap<String, ItemDefinition>()
val liquidAccess: Map<String, LiquidDefinition> = Collections.unmodifiableMap(liquid) val liquidAccess: Map<String, LiquidDefinition> = Collections.unmodifiableMap(liquid)
val liquidByIDAccess: Map<Int, LiquidDefinition> = Collections.unmodifiableMap(liquidByID) val liquidByIDAccess: Map<Int, LiquidDefinition> = Collections.unmodifiableMap(liquidByID)
val tileModifiersAccess: Map<String, MaterialModifier> = Collections.unmodifiableMap(tileModifiers) val tileModifiersAccess: Map<String, MaterialModifier> = Collections.unmodifiableMap(tileModifiers)
@ -73,6 +73,7 @@ object Starbound {
val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles) val projectilesAccess: Map<String, ConfiguredProjectile> = Collections.unmodifiableMap(projectiles)
val parallaxAccess: Map<String, ParallaxPrototype> = Collections.unmodifiableMap(parallax) val parallaxAccess: Map<String, ParallaxPrototype> = Collections.unmodifiableMap(parallax)
val functionsAccess: Map<String, JsonFunction> = Collections.unmodifiableMap(functions) val functionsAccess: Map<String, JsonFunction> = Collections.unmodifiableMap(functions)
val itemAccess: Map<String, ItemDefinition> = Collections.unmodifiableMap(items)
val gson: Gson = GsonBuilder() val gson: Gson = GsonBuilder()
.enableComplexMapKeySerialization() .enableComplexMapKeySerialization()
@ -100,6 +101,8 @@ object Starbound {
.also(RenderTemplate::registerGson) .also(RenderTemplate::registerGson)
.also(TileDefinition::registerGson) .also(TileDefinition::registerGson)
.also(LiquidDefinition::registerGson) .also(LiquidDefinition::registerGson)
.also(ItemDefinition::registerGson)
.also(ItemRarity::registerGson)
.registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe()) .registerTypeAdapter(DamageType::class.java, CustomEnumTypeAdapter(DamageType.values()).nullSafe())
@ -239,6 +242,7 @@ object Starbound {
loadStage(callback, this::loadParallax, "parallax definitions") loadStage(callback, this::loadParallax, "parallax definitions")
loadStage(callback, this::loadMaterialModifiers, "material modifier definitions") loadStage(callback, this::loadMaterialModifiers, "material modifier definitions")
loadStage(callback, this::loadLiquidDefinitions, "liquid definitions") loadStage(callback, this::loadLiquidDefinitions, "liquid definitions")
loadStage(callback, this::loadItemDefinitions, "item definitions")
initializing = false initializing = false
initialized = true initialized = true
@ -339,7 +343,7 @@ object Starbound {
private fun loadParallax(callback: (String) -> Unit) { private fun loadParallax(callback: (String) -> Unit) {
for (fs in fileSystems) { for (fs in fileSystems) {
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") && it.isFile }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
@ -360,7 +364,7 @@ object Starbound {
readingFolder = "/tiles/materials" readingFolder = "/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") && it.isFile }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
@ -387,7 +391,7 @@ object Starbound {
private fun loadLiquidDefinitions(callback: (String) -> Unit) { private fun loadLiquidDefinitions(callback: (String) -> Unit) {
for (fs in fileSystems) { for (fs in fileSystems) {
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") && it.isFile }) {
try { try {
callback("Loading $listedFile") callback("Loading $listedFile")
@ -409,4 +413,27 @@ object Starbound {
readingFolder = null readingFolder = null
} }
private fun loadItemDefinitions(callback: (String) -> Unit) {
for (fs in fileSystems) {
for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".item") && it.isFile }) {
try {
callback("Loading $listedFile")
readingFolder = listedFile.computeDirectory()
val def = gson.fromJson(listedFile.reader(), ItemDefinition::class.java)
check(items.put(def.itemName, def) == null) { "Already has item with name ${def.itemName} loaded!" }
} catch (err: Throwable) {
LOGGER.error("Loading item definition file $listedFile", err)
}
if (terminateLoading) {
return
}
}
}
readingFolder = null
}
} }

View File

@ -0,0 +1,33 @@
package ru.dbotthepony.kstarbound.defs.item
import com.google.gson.GsonBuilder
import ru.dbotthepony.kstarbound.io.KConcreteTypeAdapter
import ru.dbotthepony.kstarbound.registerTypeAdapter
data class ItemDefinition(
val itemName: String,
val price: Long = 0L,
val rarity: ItemRarity = ItemRarity.COMMON,
val category: String? = null,
val inventoryIcon: String? = null,
val description: String = "...",
val shortdescription: String = "...",
val itemTags: List<String> = listOf()
) {
companion object {
val ADAPTER = KConcreteTypeAdapter.Builder(ItemDefinition::class)
.plain(ItemDefinition::itemName)
.plain(ItemDefinition::price)
.plain(ItemDefinition::rarity)
.plain(ItemDefinition::category)
.plain(ItemDefinition::inventoryIcon)
.plain(ItemDefinition::description)
.plain(ItemDefinition::shortdescription)
.list(ItemDefinition::itemTags)
.build()
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(ADAPTER)
}
}
}

View File

@ -0,0 +1,32 @@
package ru.dbotthepony.kstarbound.defs.item
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kstarbound.io.CustomEnumTypeAdapter
import ru.dbotthepony.kstarbound.io.IStringSerializable
import ru.dbotthepony.kstarbound.registerTypeAdapter
enum class ItemRarity(val canonical: String) : IStringSerializable {
COMMON("Common"),
UNCOMMON("Uncommon"),
RARE("Rare"),
LEGENDARY("Legendary"),
ESSENTIAL("Essential");
override fun match(name: String): Boolean {
return name == this.canonical || name.lowercase() == this.name.lowercase()
}
override fun write(out: JsonWriter) {
out.value(canonical)
}
companion object {
val ADAPTER: TypeAdapter<ItemRarity> = CustomEnumTypeAdapter(values()).nullSafe()
fun registerGson(gsonBuilder: GsonBuilder) {
gsonBuilder.registerTypeAdapter(ADAPTER)
}
}
}

View File

@ -26,6 +26,6 @@ class CustomEnumTypeAdapter<T : Enum<T>>(private val clazz: Array<T>) : TypeAdap
} }
} }
throw IllegalArgumentException("${clazz[0]::class.java.name} does not have value for $str") throw IllegalArgumentException("${clazz[0]::class.qualifiedName} does not have value for $str")
} }
} }

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.kstarbound.io
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.common.collect.Interners
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonSyntaxException import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
@ -174,7 +175,7 @@ class KConcreteTypeAdapter<T : Any>(
private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>() private val returnTypeCache = Object2ObjectArrayMap<KProperty1<T, *>, KType>()
private val mapped = Object2IntArrayMap<String>() private val mapped = Object2IntArrayMap<String>()
private val internedStrings = Interners.newWeakInterner<String>()
private val loggedMisses = ObjectArraySet<String>() private val loggedMisses = ObjectArraySet<String>()
init { init {
@ -267,9 +268,11 @@ class KConcreteTypeAdapter<T : Any>(
reader.beginObject() reader.beginObject()
} }
// таблица присутствия значений (если значение true то на i было значение внутри json)
val presentValues = BooleanArray(types.size) val presentValues = BooleanArray(types.size)
val readValues = arrayOfNulls<Any>(types.size) val readValues = arrayOfNulls<Any>(types.size)
// Если нам необходимо читать объект как набор данных массива, то давай
if (asList) { if (asList) {
val iterator = types.iterator() val iterator = types.iterator()
var fieldId = 0 var fieldId = 0
@ -299,6 +302,7 @@ class KConcreteTypeAdapter<T : Any>(
fieldId++ fieldId++
} }
// иначе - читаем как json object
} else { } else {
while (reader.peek() != JsonToken.END_OBJECT) { while (reader.peek() != JsonToken.END_OBJECT) {
val name = reader.nextName() val name = reader.nextName()
@ -323,9 +327,10 @@ class KConcreteTypeAdapter<T : Any>(
} }
} }
// intern'им строки для более быстрой работы сравнения последних
for (i in readValues.indices) { for (i in readValues.indices) {
if (readValues[i] is String) { if (readValues[i] is String) {
readValues[i] = (readValues[i] as String).intern() readValues[i] = internedStrings.intern(readValues[i] as String)
} }
} }
@ -335,6 +340,8 @@ class KConcreteTypeAdapter<T : Any>(
reader.endObject() reader.endObject()
} }
// если у нас есть все значения для конструктора (все значения присутствуют в исходном json объекте)
// или у нас нет Java'вского конструктора, то делаем вызов "обычного" конструктора
if (syntheticFactory == null || presentValues.all { it }) { if (syntheticFactory == null || presentValues.all { it }) {
for ((i, pair) in types.withIndex()) { for ((i, pair) in types.withIndex()) {
val (field) = pair val (field) = pair
@ -407,6 +414,9 @@ class KConcreteTypeAdapter<T : Any>(
} }
} }
/**
* Позволяет построить класс [KConcreteTypeAdapter] на основе заданных параметров
*/
class Builder<T : Any>(val clazz: KClass<T>) { class Builder<T : Any>(val clazz: KClass<T>) {
private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>() private val types = ArrayList<Pair<KProperty1<T, *>, TypeAdapter<*>>>()