177 lines
5.6 KiB
Kotlin
177 lines
5.6 KiB
Kotlin
package ru.dbotthepony.kstarbound
|
||
|
||
import com.google.gson.Gson
|
||
import com.google.gson.JsonArray
|
||
import com.google.gson.JsonElement
|
||
import com.google.gson.JsonObject
|
||
import com.google.gson.JsonPrimitive
|
||
import com.google.gson.internal.bind.JsonTreeReader
|
||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps
|
||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||
import org.apache.logging.log4j.LogManager
|
||
import ru.dbotthepony.kstarbound.api.IStarboundFile
|
||
import ru.dbotthepony.kstarbound.lua.LuaState
|
||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||
import ru.dbotthepony.kstarbound.util.set
|
||
import ru.dbotthepony.kstarbound.util.traverseJsonPath
|
||
import java.util.*
|
||
import kotlin.reflect.KClass
|
||
|
||
inline fun <reified T : Any> ObjectRegistry(name: String, noinline key: ((T) -> String)? = null, noinline intKey: ((T) -> Int)? = null): ObjectRegistry<T> {
|
||
return ObjectRegistry(T::class, name, key, intKey)
|
||
}
|
||
|
||
fun mergeJsonElements(source: JsonObject, destination: JsonObject): JsonObject {
|
||
for ((k, v) in source.entrySet()) {
|
||
if (!destination.has(k)) {
|
||
destination[k] = v.deepCopy()
|
||
} else {
|
||
mergeJsonElements(v, destination[k])
|
||
}
|
||
}
|
||
|
||
return destination
|
||
}
|
||
|
||
fun mergeJsonElements(source: JsonArray, destination: JsonArray): JsonArray {
|
||
for ((i, v) in source.withIndex()) {
|
||
if (i >= destination.size()) {
|
||
destination.add(v.deepCopy())
|
||
} else {
|
||
destination[i] = mergeJsonElements(v, destination[i])
|
||
}
|
||
}
|
||
|
||
return destination
|
||
}
|
||
|
||
fun mergeJsonElements(source: JsonElement, destination: JsonElement): JsonElement {
|
||
if (destination is JsonPrimitive) {
|
||
return destination
|
||
}
|
||
|
||
if (destination is JsonObject && source is JsonObject) {
|
||
return mergeJsonElements(source, destination)
|
||
}
|
||
|
||
if (destination is JsonArray && source is JsonArray) {
|
||
return mergeJsonElements(source, destination)
|
||
}
|
||
|
||
return destination
|
||
}
|
||
|
||
class RegistryObject<T : Any>(
|
||
/**
|
||
* Объект реестра
|
||
*/
|
||
val value: T,
|
||
/**
|
||
* Оригинальный JSON объекта без каких либо изменений
|
||
*/
|
||
val json: JsonElement,
|
||
/**
|
||
* Файл, откуда данный объект был загружен
|
||
*/
|
||
val file: IStarboundFile,
|
||
) {
|
||
val jsonObject get() = json as JsonObject
|
||
|
||
fun push(lua: LuaState) {
|
||
lua.push(toJson())
|
||
}
|
||
|
||
fun push(lua: LuaState.ArgStack) {
|
||
lua.push(toJson())
|
||
}
|
||
|
||
/**
|
||
* Возвращает полную (обработанную) структуру [JsonObject] объекта [value]
|
||
*
|
||
* Полнота определяется тем, что [value] может иметь свойства по умолчанию, которые не указаны
|
||
* в оригинальной JSON структуре. [copy] не вернёт данные свойства по умолчанию, а [toJson] вернёт.
|
||
*/
|
||
fun toJson(): JsonElement {
|
||
return mergeJsonElements(json, Starbound.gson.toJsonTree(value))
|
||
}
|
||
|
||
fun traverseJsonPath(path: String): JsonElement? {
|
||
return traverseJsonPath(path, mergeJsonElements(json, Starbound.gson.toJsonTree(value)))
|
||
}
|
||
|
||
override fun equals(other: Any?): Boolean {
|
||
return other === this || other is RegistryObject<*> && other.value == value && other.json == json
|
||
}
|
||
|
||
private var computedHash = false
|
||
private var hash = 0
|
||
|
||
override fun hashCode(): Int {
|
||
if (!computedHash) {
|
||
hash = value.hashCode().rotateRight(13) xor json.hashCode()
|
||
computedHash = true
|
||
}
|
||
|
||
return hash
|
||
}
|
||
|
||
override fun toString(): String {
|
||
return "RegistryObject[$value from $file]"
|
||
}
|
||
}
|
||
|
||
class ObjectRegistry<T : Any>(val clazz: KClass<T>, val name: String, val key: ((T) -> String)? = null, val intKey: ((T) -> Int)? = null) {
|
||
private val objects = Object2ObjectOpenHashMap<String, RegistryObject<T>>()
|
||
private val intObjects = Int2ObjectOpenHashMap<RegistryObject<T>>()
|
||
|
||
val view: Map<String, RegistryObject<T>> = Collections.unmodifiableMap(objects)
|
||
val intView: Int2ObjectMap<RegistryObject<T>> = Int2ObjectMaps.unmodifiable(intObjects)
|
||
|
||
fun clear() {
|
||
objects.clear()
|
||
intObjects.clear()
|
||
}
|
||
|
||
fun add(file: IStarboundFile): Boolean {
|
||
return AssetPathStack(file.computeDirectory()) {
|
||
val elem = Starbound.gson.fromJson(file.reader(), JsonElement::class.java)
|
||
val value = Starbound.gson.fromJson<T>(JsonTreeReader(elem), clazz.java)
|
||
add(RegistryObject(value, elem, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
||
}
|
||
}
|
||
|
||
fun add(value: T, json: JsonElement, file: IStarboundFile): Boolean {
|
||
return add(RegistryObject(value, json, file), this.key?.invoke(value) ?: throw UnsupportedOperationException("No key mapper"))
|
||
}
|
||
|
||
fun add(value: T, json: JsonElement, file: IStarboundFile, key: String): Boolean {
|
||
return add(RegistryObject(value, json, file), key)
|
||
}
|
||
|
||
private fun add(value: RegistryObject<T>, key: String): Boolean {
|
||
val existing = objects.put(key, value)
|
||
|
||
if (existing != null) {
|
||
LOGGER.warn("Registry $name already has object with key $key! Overwriting. (old originated from ${existing.file}, new originate from ${value.file}).")
|
||
}
|
||
|
||
if (this.intKey == null)
|
||
return existing != null
|
||
|
||
val intKey = this.intKey.invoke(value.value)
|
||
val intExisting = intObjects.put(intKey, value)
|
||
|
||
if (intExisting != null) {
|
||
LOGGER.warn("Registry $name already has object with ID $intKey (new $key, old ${ objects.entries.firstOrNull { it.value === intExisting }?.key })! Overwriting. (old originated from ${intExisting.file}, new originate from ${value.file}).")
|
||
}
|
||
|
||
return existing != null || intExisting != null
|
||
}
|
||
|
||
companion object {
|
||
private val LOGGER = LogManager.getLogger()
|
||
}
|
||
}
|