package ru.dbotthepony.kstarbound.defs import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory import com.google.gson.internal.bind.JsonTreeReader 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.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.consumeNull import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.sbIntern import java.lang.reflect.ParameterizedType import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.ConcurrentHashMap import java.util.function.Consumer import kotlin.collections.HashMap class AssetReference { constructor(value: V?) { this.value = CompletableFuture.completedFuture(value) path = null fullPath = null this.json = CompletableFuture.completedFuture(null) } constructor(value: CompletableFuture) { this.value = value path = null fullPath = null this.json = CompletableFuture.completedFuture(null) } constructor(path: String?, fullPath: String?, value: V?, json: JsonElement?) { this.path = path this.fullPath = fullPath this.value = CompletableFuture.completedFuture(value) this.json = CompletableFuture.completedFuture(json) } constructor(path: String?, fullPath: String?, value: CompletableFuture, json: CompletableFuture) { this.path = path this.fullPath = fullPath this.value = value this.json = json } val path: String? val fullPath: String? val json: CompletableFuture val value: CompletableFuture companion object : TypeAdapterFactory { private val LOGGER = LogManager.getLogger() val EMPTY = AssetReference(null, null, null, null) fun empty() = EMPTY as AssetReference override fun create(gson: Gson, type: TypeToken): TypeAdapter? { if (type.rawType == AssetReference::class.java) { val param = type.type as? ParameterizedType ?: return null return object : TypeAdapter>() { private val cache = ConcurrentHashMap, CompletableFuture>>() private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter private val strings = gson.getAdapter(String::class.java) private val missing = Collections.synchronizedSet(ObjectOpenHashSet()) private val logger = LogManager.getLogger() override fun write(out: JsonWriter, value: AssetReference?) { if (value == null) out.nullValue() else out.value(value.fullPath) } override fun read(`in`: JsonReader): AssetReference? { if (`in`.consumeNull()) { return null } else if (`in`.peek() == JsonToken.STRING) { val path = strings.read(`in`)!! val fullPath = AssetPathStack.remap(path) val get = cache.computeIfAbsent(fullPath) { val json = Starbound.loadJsonAsset(fullPath) json.thenAccept { if (it == null && missing.add(fullPath)) { logger.error("JSON asset does not exist: $fullPath") } } val value = json.thenApplyAsync({ j -> AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) { adapter.fromJsonTree(j) } }, Starbound.EXECUTOR) value.exceptionally { LOGGER.error("Exception loading $fullPath", it) null } value to json } return AssetReference(path.sbIntern(), fullPath.sbIntern(), get.first, get.second) } else { val json = Starbound.ELEMENTS_ADAPTER.read(`in`) val value = adapter.read(JsonTreeReader(json)) ?: return null return AssetReference(null, null, value, json) } } } as TypeAdapter } return null } } }