package ru.dbotthepony.kstarbound import com.github.benmanes.caffeine.cache.Interner import com.google.gson.* import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.gson.AABBTypeAdapter import ru.dbotthepony.kommons.gson.AABBiTypeAdapter import ru.dbotthepony.kommons.gson.ColorTypeAdapter import ru.dbotthepony.kommons.gson.EitherTypeAdapter import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter import ru.dbotthepony.kommons.gson.NothingAdapter import ru.dbotthepony.kommons.gson.Vector2dTypeAdapter import ru.dbotthepony.kommons.gson.Vector2fTypeAdapter import ru.dbotthepony.kommons.gson.Vector2iTypeAdapter import ru.dbotthepony.kommons.gson.Vector4dTypeAdapter import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter import ru.dbotthepony.kommons.util.MailboxExecutorService import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.InventoryIcon import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.json.FastutilTypeAdapterFactory import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.json.InternedStringAdapter import ru.dbotthepony.kstarbound.json.LongRangeAdapter import ru.dbotthepony.kstarbound.json.builder.EnumAdapter import ru.dbotthepony.kstarbound.json.builder.BuilderAdapter import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter import ru.dbotthepony.kstarbound.json.builder.JsonImplementationTypeFactory import ru.dbotthepony.kstarbound.json.factory.ArrayListAdapterFactory import ru.dbotthepony.kstarbound.json.factory.ImmutableCollectionAdapterFactory import ru.dbotthepony.kstarbound.json.factory.PairAdapterFactory import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.util.ItemStack import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.HashTableInterner import ru.dbotthepony.kstarbound.util.traverseJsonPath import ru.dbotthepony.kstarbound.world.physics.Poly import java.io.* import java.lang.ref.Cleaner import java.text.DateFormat import java.util.concurrent.CompletableFuture import java.util.concurrent.Executors import java.util.concurrent.ForkJoinPool import java.util.concurrent.Future import java.util.concurrent.locks.LockSupport import java.util.function.BiConsumer import java.util.function.BinaryOperator import java.util.function.Function import java.util.function.Supplier import java.util.stream.Collector import kotlin.NoSuchElementException import kotlin.collections.ArrayList object Starbound : ISBFileLocator { const val ENGINE_VERSION = "0.0.1" const val NATIVE_PROTOCOL_VERSION = 748 const val LEGACY_PROTOCOL_VERSION = 747 const val TICK_TIME_ADVANCE = 1.0 / 60.0 const val TICK_TIME_ADVANCE_NANOS = (TICK_TIME_ADVANCE * 1_000_000_000L).toLong() // compile flags. uuuugh const val DEDUP_CELL_STATES = true const val USE_CAFFEINE_INTERNER = false fun interner(): Interner { return if (USE_CAFFEINE_INTERNER) Interner.newWeakInterner() else HashTableInterner() } fun interner(bits: Int): Interner { return if (USE_CAFFEINE_INTERNER) Interner.newWeakInterner() else HashTableInterner(bits) } val thread = Thread(::universeThread, "Starbound Universe") val mailbox = MailboxExecutorService(thread) val mailboxBootstrapped = MailboxExecutorService(thread) val mailboxInitialized = MailboxExecutorService(thread) init { thread.isDaemon = true thread.start() } val CLEANER: Cleaner = Cleaner.create { val t = Thread(it, "Starbound Global Cleaner") t.isDaemon = true t.priority = 2 t } // currently Caffeine one saves only 4 megabytes of RAM on pretty big modpack // Hrm. // val strings: Interner = Interner.newWeakInterner() // val strings: Interner = Interner { it } val STRINGS: Interner = interner(5) private val LOGGER = LogManager.getLogger() val gson: Gson = with(GsonBuilder()) { serializeNulls() setDateFormat(DateFormat.LONG) setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) setPrettyPrinting() registerTypeAdapter(InternedStringAdapter(STRINGS)) InternedJsonElementAdapter(STRINGS).also { registerTypeAdapter(it) registerTypeAdapter(it.arrays) registerTypeAdapter(it.objects) } registerTypeAdapter(Nothing::class.java, NothingAdapter) // Обработчик @JsonImplementation registerTypeAdapterFactory(JsonImplementationTypeFactory) // ImmutableList, ImmutableSet, ImmutableMap registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS)) // fastutil collections registerTypeAdapterFactory(FastutilTypeAdapterFactory(STRINGS)) // ArrayList registerTypeAdapterFactory(ArrayListAdapterFactory) // все enum'ы без особых настроек registerTypeAdapterFactory(EnumAdapter.Companion) // @JsonBuilder registerTypeAdapterFactory(BuilderAdapter.Factory(STRINGS)) // @JsonFactory registerTypeAdapterFactory(FactoryAdapter.Factory(STRINGS)) // Either<> registerTypeAdapterFactory(EitherTypeAdapter) // KOptional<> registerTypeAdapterFactory(KOptionalTypeAdapter) // Pair<> registerTypeAdapterFactory(PairAdapterFactory) registerTypeAdapterFactory(SBPattern.Companion) registerTypeAdapterFactory(JsonReference.Companion) registerTypeAdapter(ColorReplacements.Companion) registerTypeAdapterFactory(BlueprintLearnList.Companion) registerTypeAdapter(ColorTypeAdapter.nullSafe()) registerTypeAdapter(Drawable::Adapter) registerTypeAdapter(ObjectOrientation::Adapter) registerTypeAdapter(ObjectDefinition::Adapter) registerTypeAdapter(StatModifier::Adapter) // математические классы registerTypeAdapter(AABBTypeAdapter) registerTypeAdapter(AABBiTypeAdapter) registerTypeAdapter(Vector2dTypeAdapter) registerTypeAdapter(Vector2fTypeAdapter) registerTypeAdapter(Vector2iTypeAdapter) registerTypeAdapter(Vector4iTypeAdapter) registerTypeAdapter(Vector4dTypeAdapter) registerTypeAdapter(LineF::Adapter) // Функции registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER) registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER) registerTypeAdapter(JsonFunction.Companion) registerTypeAdapterFactory(Json2Function.Companion) // Общее registerTypeAdapterFactory(ThingDescription.Factory(STRINGS)) registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) registerTypeAdapter(InventoryIcon.Companion) registerTypeAdapterFactory(IArmorItemDefinition.Frames.Factory) registerTypeAdapterFactory(AssetPath.Companion) registerTypeAdapter(SpriteReference.Companion) registerTypeAdapterFactory(AssetReference.Companion) registerTypeAdapter(ItemStack.Adapter(this@Starbound)) registerTypeAdapterFactory(ItemReference.Factory(STRINGS)) registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapter(Image.Companion) registerTypeAdapterFactory(Poly.Companion) registerTypeAdapterFactory(Registries.tiles.adapter()) registerTypeAdapterFactory(Registries.tileModifiers.adapter()) registerTypeAdapterFactory(Registries.liquid.adapter()) registerTypeAdapterFactory(Registries.items.adapter()) registerTypeAdapterFactory(Registries.species.adapter()) registerTypeAdapterFactory(Registries.statusEffects.adapter()) registerTypeAdapterFactory(Registries.particles.adapter()) registerTypeAdapterFactory(Registries.questTemplates.adapter()) registerTypeAdapterFactory(Registries.techs.adapter()) registerTypeAdapterFactory(Registries.jsonFunctions.adapter()) registerTypeAdapterFactory(Registries.json2Functions.adapter()) registerTypeAdapterFactory(Registries.npcTypes.adapter()) registerTypeAdapterFactory(Registries.projectiles.adapter()) registerTypeAdapterFactory(Registries.tenants.adapter()) registerTypeAdapterFactory(Registries.treasurePools.adapter()) registerTypeAdapterFactory(Registries.monsterSkills.adapter()) registerTypeAdapterFactory(Registries.monsterTypes.adapter()) registerTypeAdapterFactory(Registries.worldObjects.adapter()) registerTypeAdapter(LongRangeAdapter) create() } data class ItemConfig( val directory: String?, val parameters: JsonObject, val config: JsonObject ) fun itemConfig(name: String, parameters: JsonObject, level: Double? = null, seed: Long? = null): ItemConfig { val obj = Registries.items[name] ?: throw NoSuchElementException("No such item $name") val directory = obj.file?.computeDirectory() val parameters = parameters.deepCopy() TODO() } fun item(name: String): ItemStack { return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY) } fun item(name: String, count: Long): ItemStack { if (count <= 0L) return ItemStack.EMPTY return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count) } fun item(name: String, count: Long, parameters: JsonObject): ItemStack { if (count <= 0L) return ItemStack.EMPTY return ItemStack(Registries.items[name] ?: return ItemStack.EMPTY, count = count, parameters = parameters) } fun item(descriptor: JsonObject): ItemStack { return item( (descriptor["name"] as? JsonPrimitive)?.asString ?: return ItemStack.EMPTY, descriptor["count"]?.asLong ?: return ItemStack.EMPTY, (descriptor["parameters"] as? JsonObject)?.deepCopy() ?: JsonObject() ) } fun item(descriptor: JsonElement?): ItemStack { if (descriptor is JsonPrimitive) { return item(descriptor.asString) } else if (descriptor is JsonObject) { return item(descriptor) } else { return ItemStack.EMPTY } } var initializing = false private set var initialized = false private set var bootstrapping = false private set var bootstrapped = false private set var loadingProgress = 0.0 private set @Volatile var terminateLoading = false fun loadJsonAsset(path: String): JsonElement? { val filename: String val jsonPath: String? if (path.contains(':')) { filename = path.substringBefore(':') jsonPath = path.substringAfter(':') } else { filename = path jsonPath = null } val file = locate(filename) if (!file.isFile) { return null } return traverseJsonPath(jsonPath, gson.fromJson(file.reader(), JsonElement::class.java)) } private val archivePaths = ArrayList() private val fileSystems = ArrayList() fun addFilePath(path: File) { fileSystems.add(PhysicalFile(path)) } fun addPak(pak: StarboundPak) { fileSystems.add(pak.root) } override fun exists(path: String): Boolean { @Suppress("name_shadowing") var path = path if (path[0] == '/') { path = path.substring(1) } for (fs in fileSystems) { if (fs.locate(path).exists) { return true } } return false } override fun locate(path: String): IStarboundFile { @Suppress("name_shadowing") var path = path if (path[0] == '/') { path = path.substring(1) } for (fs in fileSystems) { val file = fs.locate(path) if (file.exists) { return file } } return NonExistingFile(path.split("/").last(), fullPath = path) } fun locate(vararg path: String): IStarboundFile { for (p in path) { val get = locate(p) if (get.exists) { return get } } return NonExistingFile(path[0].split("/").last(), fullPath = path[0]) } /** * Добавляет pak к чтению при initializeGame */ fun addPakPath(pak: File) { archivePaths.add(pak) } private fun doBootstrap() { if (!bootstrapped && !bootstrapping) { bootstrapping = true } else { return } for (path in archivePaths) { LOGGER.info("Reading PAK archive $path") addPak(StarboundPak(path)) } LOGGER.info("Finished reading PAK archives") bootstrapped = true bootstrapping = false checkMailbox() } private fun doInitialize(parallel: Boolean) { if (!initializing && !initialized) { initializing = true } else { return } doBootstrap() val ext2files = fileSystems.parallelStream() .flatMap { it.explore() } .filter { it.isFile } .collect(object : Collector>, Map>> { override fun supplier(): Supplier>> { return Supplier { Object2ObjectOpenHashMap() } } override fun accumulator(): BiConsumer>, IStarboundFile> { return BiConsumer { t, u -> t.computeIfAbsent(u.name.substringAfterLast('.'), Object2ObjectFunction { ArrayList() }).add(u) } } override fun combiner(): BinaryOperator>> { return BinaryOperator { t, u -> for ((k, v) in u) t.computeIfAbsent(k, Object2ObjectFunction { ArrayList() }).addAll(v) t } } override fun finisher(): Function>, Map>> { return Function { it } } override fun characteristics(): Set { return setOf(Collector.Characteristics.IDENTITY_FINISH) } }) checkMailbox() val tasks = ArrayList>() val pool = if (parallel) ForkJoinPool.commonPool() else Executors.newFixedThreadPool(1) tasks.addAll(Registries.load(ext2files, pool)) tasks.addAll(RecipeRegistry.load(ext2files, pool)) tasks.addAll(GlobalDefaults.load(pool)) val total = tasks.size.toDouble() while (tasks.isNotEmpty()) { tasks.removeIf { it.isDone } checkMailbox() loadingProgress = (total - tasks.size) / total LockSupport.parkNanos(5_000_000L) } if (!parallel) pool.shutdown() Registries.finishLoad() RecipeRegistry.finishLoad() Registries.validate() initializing = false initialized = true } fun initializeGame(parallel: Boolean = true) { mailbox.submit { doInitialize(parallel) } } fun bootstrapGame() { mailbox.submit { doBootstrap() } } private fun checkMailbox() { mailbox.executeQueuedTasks() if (bootstrapped) mailboxBootstrapped.executeQueuedTasks() if (initialized) mailboxInitialized.executeQueuedTasks() } private var fontPath: File? = null fun loadFont(): CompletableFuture { val fontPath = fontPath if (fontPath != null) return CompletableFuture.completedFuture(fontPath) return CompletableFuture.supplyAsync(Supplier { val fontPath = Starbound.fontPath if (fontPath != null) return@Supplier fontPath val file = locate("/hobo.ttf") if (!file.exists) throw FileNotFoundException("Unable to locate font file /hobo.ttf") else if (!file.isFile) throw FileNotFoundException("/hobo.ttf is not a file!") else { val tempPath = File(System.getProperty("java.io.tmpdir"), "sb-hobo.ttf") tempPath.writeBytes(file.read().array()) Starbound.fontPath = tempPath return@Supplier tempPath } }, mailboxBootstrapped) } private fun universeThread() { while (true) { checkMailbox() LockSupport.park() } } }