package ru.dbotthepony.kstarbound import com.github.benmanes.caffeine.cache.Interner import com.google.gson.* import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.api.ISBFileLocator import ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.api.NonExistingFile import ru.dbotthepony.kstarbound.api.PhysicalFile 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.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition import ru.dbotthepony.kstarbound.util.JsonArrayCollector import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.json.AABBTypeAdapter import ru.dbotthepony.kstarbound.json.AABBiTypeAdapter import ru.dbotthepony.kstarbound.json.ColorTypeAdapter import ru.dbotthepony.kstarbound.json.EitherTypeAdapter import ru.dbotthepony.kstarbound.json.FastutilTypeAdapterFactory import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter import ru.dbotthepony.kstarbound.json.InternedStringAdapter import ru.dbotthepony.kstarbound.json.KOptionalTypeAdapter import ru.dbotthepony.kstarbound.json.LongRangeAdapter import ru.dbotthepony.kstarbound.json.NothingAdapter import ru.dbotthepony.kstarbound.json.OneOfTypeAdapter import ru.dbotthepony.kstarbound.json.Vector2dTypeAdapter import ru.dbotthepony.kstarbound.json.Vector2fTypeAdapter import ru.dbotthepony.kstarbound.json.Vector2iTypeAdapter import ru.dbotthepony.kstarbound.json.Vector4dTypeAdapter import ru.dbotthepony.kstarbound.json.Vector4iTypeAdapter 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.ITimeSource import ru.dbotthepony.kstarbound.util.ItemStack import ru.dbotthepony.kstarbound.util.JVMTimeSource import ru.dbotthepony.kstarbound.util.AssetPathStack import ru.dbotthepony.kstarbound.util.SBPattern import ru.dbotthepony.kstarbound.util.HashTableInterner import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.util.WriteOnce import ru.dbotthepony.kstarbound.util.filterNotNull import ru.dbotthepony.kstarbound.util.set 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.ForkJoinTask 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 import kotlin.random.Random object Starbound : ISBFileLocator { const val TICK_TIME_ADVANCE = 0.01666666666666664 const val TICK_TIME_ADVANCE_NANOS = 16_666_666L 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 = HashTableInterner(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) // OneOf<> registerTypeAdapterFactory(OneOfTypeAdapter) // 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(ru.dbotthepony.kstarbound.json.AABBTypeAdapter) registerTypeAdapter(ru.dbotthepony.kstarbound.json.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() } } }