diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 260fc6eb..ec72ecfa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -1,7 +1,12 @@ package ru.dbotthepony.kstarbound import com.google.common.collect.ImmutableList +import com.google.common.collect.Interner +import com.google.common.collect.Interners import com.google.gson.* +import com.google.gson.internal.bind.TypeAdapters +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager @@ -104,6 +109,18 @@ object Starbound { val functionsAccess: Map = Collections.unmodifiableMap(functions) val itemAccess: Map = Collections.unmodifiableMap(items) + val assetStringInterner: Interner = Interners.newStrongInterner() + + val stringTypeAdapter: TypeAdapter = object : TypeAdapter() { + override fun write(out: JsonWriter, value: String) { + out.value(value) + } + + override fun read(`in`: JsonReader): String { + return assetStringInterner.intern(TypeAdapters.STRING.read(`in`)) + } + }.nullSafe() + val gson: Gson = GsonBuilder() .enableComplexMapKeySerialization() .serializeNulls() @@ -112,6 +129,9 @@ object Starbound { .setPrettyPrinting() .registerTypeAdapter(Color::class.java, ColorTypeAdapter.nullSafe()) + // чтоб строки всегда intern'ились + .registerTypeAdapter(stringTypeAdapter) + // math .registerTypeAdapter(AABB::class.java, AABBTypeAdapter) .registerTypeAdapter(AABBi::class.java, AABBiTypeAdapter) @@ -137,6 +157,19 @@ object Starbound { .create() + @Suppress("unchecked_cast") + fun getTypeAdapter(type: Class): TypeAdapter { + return when (type) { + Float::class.java -> TypeAdapters.FLOAT as TypeAdapter + Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter + String::class.java -> stringTypeAdapter as TypeAdapter + Int::class.java -> TypeAdapters.INTEGER as TypeAdapter + Long::class.java -> TypeAdapters.LONG as TypeAdapter + Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter + else -> gson.getAdapter(type) + } + } + var initializing = false private set var initialized = false @@ -442,7 +475,8 @@ object Starbound { } private fun loadItemDefinitions(callback: (String) -> Unit) { - val files = listOf(".item", ".currency", ".head", ".chest", ".legs") + //val files = listOf(".item", ".currency", ".head", ".chest", ".legs") + val files = listOf(".item", ".currency") for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { f -> files.any { f.name.endsWith(it) } }) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt index 0c68d848..c0529570 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/item/ItemDefinition.kt @@ -313,7 +313,6 @@ data class ItemDefinition( .auto(FossilSetDescription::price) .auto(FossilSetDescription::shortdescription) .auto(FossilSetDescription::description) - .specifyStringInterner(ADAPTER.stringInterner) .build() val ARMOR_FRAMES_ADAPTER = KConcreteTypeAdapter.Builder(ArmorFrames::class) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KConcreteTypeAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KConcreteTypeAdapter.kt index 84b588b1..bf2e4037 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KConcreteTypeAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/KConcreteTypeAdapter.kt @@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound.io.json import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableMap -import com.google.common.collect.Interner -import com.google.common.collect.Interners import com.google.gson.JsonArray import com.google.gson.JsonObject import com.google.gson.JsonParseException @@ -27,145 +25,8 @@ import java.lang.reflect.Constructor import kotlin.jvm.internal.DefaultConstructorMarker import kotlin.reflect.* import kotlin.reflect.full.isSubclassOf -import kotlin.reflect.full.isSuperclassOf import kotlin.reflect.full.isSupertypeOf -@Suppress("unchecked_cast") -private fun resolveBound(bound: Class, stringAdapter: TypeAdapter): TypeAdapter { - return when (bound) { - Float::class.java -> TypeAdapters.FLOAT as TypeAdapter - Double::class.java -> TypeAdapters.DOUBLE as TypeAdapter - String::class.java -> stringAdapter as TypeAdapter - Int::class.java -> TypeAdapters.INTEGER as TypeAdapter - Long::class.java -> TypeAdapters.LONG as TypeAdapter - Boolean::class.java -> TypeAdapters.BOOLEAN as TypeAdapter - else -> Starbound.gson.getAdapter(bound) - } -} - -private class ListAdapter(private val type: Class, private val stringAdapter: TypeAdapter) : TypeAdapter?>() { - private val resolvedBound by lazy { resolveBound(type, stringAdapter) } - - override fun write(out: JsonWriter, value: List?) { - out.beginArray() - - if (value != null) { - val resolvedBound = resolvedBound - - for (v in value) { - resolvedBound.write(out, v) - } - } - - out.endArray() - } - - override fun read(reader: JsonReader): List { - reader.beginArray() - - val builder = ImmutableList.builder() - val resolvedBound = resolvedBound - - while (reader.peek() != JsonToken.END_ARRAY) { - val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls") - builder.add(readObject as T) - } - - reader.endArray() - - return builder.build() - } -} - -private class MapAdapter(private val keyType: Class, private val valueType: Class, private val stringAdapter: TypeAdapter) : TypeAdapter?>() { - private val resolvedKey by lazy { resolveBound(keyType, stringAdapter) } - private val resolvedValue by lazy { resolveBound(valueType, stringAdapter) } - - override fun write(out: JsonWriter, value: Map?) { - out.beginArray() - - if (value != null) { - val resolvedKey = resolvedKey - val resolvedValue = resolvedValue - - for ((k, v) in value) { - out.beginArray() - resolvedKey.write(out, k) - resolvedValue.write(out, v) - out.endArray() - } - } - - out.endArray() - } - - override fun read(reader: JsonReader): Map { - reader.beginArray() - - val builder = ImmutableMap.builder() - - val resolvedKey = resolvedKey - val resolvedValue = resolvedValue - - while (reader.peek() != JsonToken.END_ARRAY) { - reader.beginArray() - builder.put(resolvedKey.read(reader), resolvedValue.read(reader)) - reader.endArray() - } - - reader.endArray() - - return builder.build() - } -} - -private class StringMapAdapter(private val type: Class, private val stringAdapter: TypeAdapter, private val interner: Interner) : TypeAdapter?>() { - private val resolvedBound by lazy { resolveBound(type, stringAdapter) } - - override fun write(out: JsonWriter, value: Map?) { - val resolvedBound = resolvedBound - - out.beginObject() - - if (value != null) { - for ((k, v) in value) { - out.name(k) - resolvedBound.write(out, v) - } - } - - out.endObject() - } - - override fun read(reader: JsonReader): Map { - val builder = ImmutableMap.builder() - - reader.beginObject() - - val resolvedBound = resolvedBound - - while (reader.peek() != JsonToken.END_OBJECT) { - builder.put(interner.intern(reader.nextName()), resolvedBound.read(reader)) - } - - reader.endObject() - - return builder.build() - } -} - -private class LazyTypeProvider(private val bound: Class) : TypeAdapter() { - private val resolved by lazy { resolveBound(bound, TypeAdapters.STRING) } - - override fun write(out: JsonWriter, value: T) { - resolved.write(out, value) - } - - override fun read(`in`: JsonReader): T { - return resolved.read(`in`) - } -} - private data class PackedProperty( val property: KProperty1, val adapter: TypeAdapter, @@ -181,19 +42,18 @@ class KConcreteTypeAdapter private constructor( val bound: KClass, private val types: ImmutableList>, val asJsonArray: Boolean, - val stringInterner: Interner, val storesJson: Boolean ) : TypeAdapter() { - private val mapped = Object2IntArrayMap() + private val name2index = Object2IntArrayMap() private val loggedMisses = ObjectArraySet() var currentSymbolicName by ThreadLocal() init { - mapped.defaultReturnValue(-1) + name2index.defaultReturnValue(-1) for ((i, pair) in types.withIndex()) { - mapped[pair.property.name] = i + name2index[pair.property.name] = i } } @@ -328,7 +188,7 @@ class KConcreteTypeAdapter private constructor( } reader = JsonTreeReader(readArray) - readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, stringInterner::intern) + readValues[readValues.size - 1] = enrollList(flattenJsonElement(readArray) as List, Starbound.assetStringInterner::intern) } reader.beginArray() @@ -373,14 +233,14 @@ class KConcreteTypeAdapter private constructor( } reader = JsonTreeReader(readMap) - readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, stringInterner::intern) + readValues[readValues.size - 1] = enrollMap(flattenJsonElement(readMap) as Map, Starbound.assetStringInterner::intern) } reader.beginObject() while (reader.peek() != JsonToken.END_OBJECT) { val name = reader.nextName() - val fieldId = mapped.getInt(name) + val fieldId = name2index.getInt(name) if (fieldId == -1) { if (!storesJson && loggedMisses.add(name)) { @@ -498,7 +358,6 @@ class KConcreteTypeAdapter private constructor( */ class Builder(val clazz: KClass) { private val types = ArrayList>() - var stringInterner: Interner = Interners.newWeakInterner() /** * Принимает ли класс *последним* аргументом JSON объект @@ -515,21 +374,6 @@ class KConcreteTypeAdapter private constructor( return this } - fun specifyStringInterner(interner: Interner): Builder { - stringInterner = interner - return this - } - - private val internedStringAdapter: TypeAdapter = object : TypeAdapter() { - override fun write(out: JsonWriter, value: String) { - return TypeAdapters.STRING.write(out, value) - } - - override fun read(`in`: JsonReader): String { - return stringInterner.intern(TypeAdapters.STRING.read(`in`)) - } - } - /** * Добавляет поле с определённым адаптером */ @@ -556,23 +400,7 @@ class KConcreteTypeAdapter private constructor( fun auto(field: KProperty1, transformer: (In) -> In = { it }): Builder { val returnType = field.returnType val classifier = returnType.classifier as? KClass<*> ?: throw ClassCastException("Unable to cast ${returnType.classifier} to KClass of property ${field.name}!") - - if (classifier.isSuperclassOf(Float::class)) { - types.add(PackedProperty(field as KProperty1, TypeAdapters.FLOAT, transformer = transformer as (Number) -> Number)) - } else if (classifier.isSuperclassOf(Double::class)) { - types.add(PackedProperty(field as KProperty1, TypeAdapters.DOUBLE, transformer = transformer as (Number) -> Number)) - } else if (classifier.isSuperclassOf(Int::class)) { - types.add(PackedProperty(field as KProperty1, TypeAdapters.INTEGER, transformer = transformer as (Number) -> Number)) - } else if (classifier.isSuperclassOf(Long::class)) { - types.add(PackedProperty(field as KProperty1, TypeAdapters.LONG, transformer = transformer as (Number) -> Number)) - } else if (classifier.isSuperclassOf(String::class)) { - types.add(PackedProperty(field as KProperty1, internedStringAdapter, transformer = transformer as (String) -> String)) - } else if (classifier.isSuperclassOf(Boolean::class)) { - types.add(PackedProperty(field as KProperty1, TypeAdapters.BOOLEAN, transformer = transformer as (Boolean) -> Boolean)) - } else { - types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter, transformer = transformer as (Any?) -> Any?)) - } - + types.add(PackedProperty(field, LazyTypeProvider(classifier.java) as TypeAdapter, transformer = transformer as (Any?) -> Any?)) return this } @@ -582,7 +410,7 @@ class KConcreteTypeAdapter private constructor( * Список неизменяем (создаётся объект [ImmutableList]) */ fun list(field: KProperty1?>, type: Class, transformer: (List?) -> List? = { it }): Builder { - types.add(PackedProperty(field, ListAdapter(type, internedStringAdapter), transformer = transformer)) + types.add(PackedProperty(field, ListAdapter(type), transformer = transformer)) return this } @@ -601,7 +429,7 @@ class KConcreteTypeAdapter private constructor( * Таблица неизменяема (создаётся объект [ImmutableMap]) */ fun map(field: KProperty1>, keyType: Class, valueType: Class): Builder { - types.add(PackedProperty(field, MapAdapter(keyType, valueType, internedStringAdapter))) + types.add(PackedProperty(field, MapAdapter(keyType, valueType))) return this } @@ -625,7 +453,7 @@ class KConcreteTypeAdapter private constructor( * Таблица неизменяема (создаётся объект [ImmutableMap]) */ fun map(field: KProperty1?>, keyType: KClass, valueType: KClass): Builder { - types.add(PackedProperty(field, MapAdapter(keyType.java, valueType.java, internedStringAdapter))) + types.add(PackedProperty(field, MapAdapter(keyType.java, valueType.java))) return this } @@ -635,7 +463,7 @@ class KConcreteTypeAdapter private constructor( * Таблица неизменяема (создаётся объект [ImmutableMap]) */ fun map(field: KProperty1?>, valueType: Class): Builder { - types.add(PackedProperty(field, StringMapAdapter(valueType, internedStringAdapter, stringInterner))) + types.add(PackedProperty(field, StringMapAdapter(valueType))) return this } @@ -645,7 +473,7 @@ class KConcreteTypeAdapter private constructor( * Таблица неизменяема (создаётся объект [ImmutableMap]) */ fun map(field: KProperty1?>, valueType: KClass): Builder { - types.add(PackedProperty(field, StringMapAdapter(valueType.java, internedStringAdapter, stringInterner))) + types.add(PackedProperty(field, StringMapAdapter(valueType.java))) return this } @@ -666,7 +494,6 @@ class KConcreteTypeAdapter private constructor( bound = clazz, types = ImmutableList.copyOf(types), asJsonArray = asList, - stringInterner = stringInterner, storesJson = storesJson ) } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/LazyTypeProvider.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/LazyTypeProvider.kt new file mode 100644 index 00000000..d7074117 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/LazyTypeProvider.kt @@ -0,0 +1,18 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Starbound + +class LazyTypeProvider(private val bound: Class) : TypeAdapter() { + private val resolved by lazy { Starbound.getTypeAdapter(bound) } + + override fun write(out: JsonWriter, value: T) { + resolved.write(out, value) + } + + override fun read(`in`: JsonReader): T { + return resolved.read(`in`) + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ListAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ListAdapter.kt new file mode 100644 index 00000000..e8f2f19a --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/ListAdapter.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.common.collect.ImmutableList +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 ru.dbotthepony.kstarbound.Starbound + +class ListAdapter(private val type: Class) : TypeAdapter?>() { + private val resolvedBound by lazy { Starbound.getTypeAdapter(type) } + + override fun write(out: JsonWriter, value: List?) { + out.beginArray() + + if (value != null) { + val resolvedBound = resolvedBound + + for (v in value) { + resolvedBound.write(out, v) + } + } + + out.endArray() + } + + override fun read(reader: JsonReader): List { + reader.beginArray() + + val builder = ImmutableList.builder() + val resolvedBound = resolvedBound + + while (reader.peek() != JsonToken.END_ARRAY) { + val readObject = resolvedBound.read(reader) ?: throw JsonSyntaxException("List does not accept nulls") + builder.add(readObject as T) + } + + reader.endArray() + + return builder.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MapAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MapAdapter.kt new file mode 100644 index 00000000..cc17a15f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/MapAdapter.kt @@ -0,0 +1,50 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.common.collect.ImmutableMap +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Starbound + +class MapAdapter(private val keyType: Class, private val valueType: Class) : TypeAdapter?>() { + private val resolvedKey by lazy { Starbound.getTypeAdapter(keyType) } + private val resolvedValue by lazy { Starbound.getTypeAdapter(valueType) } + + override fun write(out: JsonWriter, value: Map?) { + out.beginArray() + + if (value != null) { + val resolvedKey = resolvedKey + val resolvedValue = resolvedValue + + for ((k, v) in value) { + out.beginArray() + resolvedKey.write(out, k) + resolvedValue.write(out, v) + out.endArray() + } + } + + out.endArray() + } + + override fun read(reader: JsonReader): Map { + reader.beginArray() + + val builder = ImmutableMap.builder() + + val resolvedKey = resolvedKey + val resolvedValue = resolvedValue + + while (reader.peek() != JsonToken.END_ARRAY) { + reader.beginArray() + builder.put(resolvedKey.read(reader), resolvedValue.read(reader)) + reader.endArray() + } + + reader.endArray() + + return builder.build() + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/StringMapAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/StringMapAdapter.kt new file mode 100644 index 00000000..f9d34361 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/StringMapAdapter.kt @@ -0,0 +1,43 @@ +package ru.dbotthepony.kstarbound.io.json + +import com.google.common.collect.ImmutableMap +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter +import ru.dbotthepony.kstarbound.Starbound + +class StringMapAdapter(private val type: Class) : TypeAdapter?>() { + private val resolvedBound by lazy { Starbound.getTypeAdapter(type) } + + override fun write(out: JsonWriter, value: Map?) { + val resolvedBound = resolvedBound + + out.beginObject() + + if (value != null) { + for ((k, v) in value) { + out.name(k) + resolvedBound.write(out, v) + } + } + + out.endObject() + } + + override fun read(reader: JsonReader): Map { + val builder = ImmutableMap.builder() + + reader.beginObject() + + val resolvedBound = resolvedBound + + while (reader.peek() != JsonToken.END_OBJECT) { + builder.put(Starbound.assetStringInterner.intern(reader.nextName()), resolvedBound.read(reader)) + } + + reader.endObject() + + return builder.build() + } +} \ No newline at end of file