From efadaeb28c604df14d5de0fa8c3dc6573b0412e1 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 1 Dec 2023 20:16:21 +0700 Subject: [PATCH] Get rid of loading log since it stalls threads --- .../dbotthepony/kstarbound/GlobalDefaults.kt | 9 +- .../ru/dbotthepony/kstarbound/LoadingLog.kt | 87 ------ .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 2 +- .../dbotthepony/kstarbound/RecipeRegistry.kt | 72 +++-- .../ru/dbotthepony/kstarbound/Registries.kt | 255 +++++++----------- .../ru/dbotthepony/kstarbound/Registry.kt | 16 ++ .../ru/dbotthepony/kstarbound/Starbound.kt | 35 +-- .../kstarbound/client/StarboundClient.kt | 127 ++++----- 8 files changed, 225 insertions(+), 378 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/GlobalDefaults.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/GlobalDefaults.kt index d10df8b5..06ca1a97 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/GlobalDefaults.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/GlobalDefaults.kt @@ -52,18 +52,13 @@ object GlobalDefaults { } } - fun load(log: ILoadingLog, executor: ForkJoinPool): List> { + fun load(executor: ForkJoinPool): List> { val tasks = ArrayList>() tasks.add(load("/default_actor_movement.config", ::playerMovementParameters, executor)) tasks.add(load("/default_movement.config", ::movementParameters, executor)) tasks.add(load("/client.config", ::clientParameters, executor)) - return listOf(executor.submit { - val line = log.line("Loading global defaults...") - val time = System.nanoTime() - tasks.forEach { it.join() } - line.text = "Loaded global defaults in ${((System.nanoTime() - time) / 1_000_000.0).toLong()}ms" - }) + return tasks } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt deleted file mode 100644 index 793f6ffb..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/LoadingLog.kt +++ /dev/null @@ -1,87 +0,0 @@ -package ru.dbotthepony.kstarbound - -import it.unimi.dsi.fastutil.ints.IntIterators -import it.unimi.dsi.fastutil.objects.ObjectIterators -import java.util.concurrent.atomic.AtomicInteger - -interface ILoadingLog : Iterable { - interface ILine { - var text: String - val progress: Float - get() = if (maxElements > 0) elements.toFloat() / maxElements.toFloat() else 0f - - val elements: AtomicInteger - var maxElements: Int - } - - fun line(text: String): ILine - - companion object : ILoadingLog, ILine { - override var text: String - get() = "" - set(value) {} - - override var elements: AtomicInteger = AtomicInteger() - - override var maxElements: Int - get() = 0 - set(value) {} - - override fun line(text: String): ILine { - return this - } - - override fun iterator(): Iterator { - return ObjectIterators.emptyIterator() - } - } -} - -class LoadingLog : ILoadingLog { - private val lines = arrayOfNulls(128) - @Volatile - private var index = 0 - private val lock = Any() - private var size = 0 - var lastActivity: Long = System.nanoTime() - private set - - override fun line(text: String): ILoadingLog.ILine { - return Line(text) - } - - inner class Line(text: String) : ILoadingLog.ILine { - override var text: String = text - set(value) { - field = value - lastActivity = System.nanoTime() - } - - override val elements = AtomicInteger() - override var maxElements: Int = 0 - - init { - lastActivity = System.nanoTime() - - synchronized(lock) { - lines[index++ and 127] = this - size = (size + 1).coerceAtMost(127) - } - } - } - - override fun iterator(): Iterator { - return object : Iterator { - private val index = (this@LoadingLog.index - 1) and 127 - private val parent = IntIterators.fromTo(0, size) - - override fun hasNext(): Boolean { - return parent.hasNext() - } - - override fun next(): ILoadingLog.ILine { - return lines[(index - parent.nextInt()) and 127]!! - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 58a26018..f34d5f93 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -63,7 +63,7 @@ fun main() { //Starbound.addPakPath(File("packed.pak")) - Starbound.initializeGame(client.loadingLog) + Starbound.initializeGame() var ply: PlayerEntity? = null diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt index 43f1a16a..26c1fe2f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/RecipeRegistry.kt @@ -9,6 +9,7 @@ import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition import ru.dbotthepony.kstarbound.util.KOptional import ru.dbotthepony.kstarbound.util.ParallelPerform import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask import java.util.concurrent.locks.ReentrantLock @@ -33,63 +34,60 @@ object RecipeRegistry { val output2recipes: Map> = Collections.unmodifiableMap(output2recipesBacking) val input2recipes: Map> = Collections.unmodifiableMap(input2recipesBacking) - private val lock = ReentrantLock() + private val backlog = ConcurrentLinkedQueue() - fun add(recipe: Entry) { - lock.withLock { - val value = recipe.value - recipesInternal.add(recipe) + private fun add(recipe: Entry) { + val value = recipe.value + recipesInternal.add(recipe) - for (group in value.groups) { - group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> - ArrayList(1).also { - group2recipesBacking[p as String] = Collections.unmodifiableList(it) - } - }).add(recipe) - } - - output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p -> + for (group in value.groups) { + group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> ArrayList(1).also { - output2recipesBacking[p as String] = Collections.unmodifiableList(it) + group2recipesBacking[p as String] = Collections.unmodifiableList(it) } }).add(recipe) + } - for (input in value.input) { - input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p -> - ArrayList(1).also { - input2recipesBacking[p as String] = Collections.unmodifiableList(it) - } - }).add(recipe) + output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p -> + ArrayList(1).also { + output2recipesBacking[p as String] = Collections.unmodifiableList(it) } + }).add(recipe) + + for (input in value.input) { + input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p -> + ArrayList(1).also { + input2recipesBacking[p as String] = Collections.unmodifiableList(it) + } + }).add(recipe) } } - fun load(log: ILoadingLog, fileTree: Map>, executor: ForkJoinPool): List> { + fun finishLoad() { + var next = backlog.poll() + + while (next != null) { + add(next) + next = backlog.poll() + } + } + + fun load(fileTree: Map>, executor: ForkJoinPool): List> { val files = fileTree["recipe"] ?: return emptyList() val elements = Starbound.gson.getAdapter(JsonElement::class.java) val recipes = Starbound.gson.getAdapter(RecipeDefinition::class.java) - return listOf(executor.submit { - val line = log.line("Loading recipes...") - val time = System.nanoTime() - line.maxElements = files.size - - files.batch(executor) { listedFile -> + return files.map { listedFile -> + executor.submit { try { - line.text = ("Loading $listedFile") val json = elements.read(listedFile.jsonReader()) val value = recipes.fromJsonTree(json) - KOptional.of(Entry(value, json, listedFile)) + backlog.add(Entry(value, json, listedFile)) } catch (err: Throwable) { LOGGER.error("Loading recipe definition file $listedFile", err) - KOptional.empty() - } finally { - line.elements.incrementAndGet() } - }.forEach { add(it) } - - line.text = "Loaded recipes in ${((System.nanoTime() - time) / 1_000_000.0).toLong()}ms" - }) + } + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt index c4fb8528..f1bbfaa4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registries.kt @@ -35,32 +35,32 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.util.AssetPathStack -import ru.dbotthepony.kstarbound.util.KOptional -import ru.dbotthepony.kstarbound.util.ParallelPerform import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask object Registries { private val LOGGER = LogManager.getLogger() - val tiles = Registry("tiles") - val tileModifiers = Registry("tile modifiers") - val liquid = Registry("liquid") - val species = Registry("species") - val statusEffects = Registry("status effects") - val particles = Registry("particles") - val items = Registry("items") - val questTemplates = Registry("quest templates") - val techs = Registry("techs") - val jsonFunctions = Registry("json functions") - val json2Functions = Registry("json 2functions") - val npcTypes = Registry("npc types") - val projectiles = Registry("projectiles") - val tenants = Registry("tenants") - val treasurePools = Registry("treasure pools") - val monsterSkills = Registry("monster skills") - val monsterTypes = Registry("monster types") - val worldObjects = Registry("objects") + private val registries = ArrayList>() + + val tiles = Registry("tiles").also(registries::add) + val tileModifiers = Registry("tile modifiers").also(registries::add) + val liquid = Registry("liquid").also(registries::add) + val species = Registry("species").also(registries::add) + val statusEffects = Registry("status effects").also(registries::add) + val particles = Registry("particles").also(registries::add) + val items = Registry("items").also(registries::add) + val questTemplates = Registry("quest templates").also(registries::add) + val techs = Registry("techs").also(registries::add) + val jsonFunctions = Registry("json functions").also(registries::add) + val json2Functions = Registry("json 2functions").also(registries::add) + val npcTypes = Registry("npc types").also(registries::add) + val projectiles = Registry("projectiles").also(registries::add) + val tenants = Registry("tenants").also(registries::add) + val treasurePools = Registry("treasure pools").also(registries::add) + val monsterSkills = Registry("monster skills").also(registries::add) + val monsterTypes = Registry("monster types").also(registries::add) + val worldObjects = Registry("objects").also(registries::add) private fun key(mapper: (T) -> String): (T) -> Pair { return { mapper.invoke(it) to null } @@ -95,92 +95,67 @@ object Registries { return !any } - private fun loadStage( - log: ILoadingLog, - loader: (ILoadingLog.ILine) -> Unit, - name: String, - ) { - if (Starbound.terminateLoading) - return - - val time = System.currentTimeMillis() - val line = log.line("Loading $name...".also(LOGGER::info)) - loader(line) - line.text = ("Loaded $name in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) - } - - private inline fun loadStage( - log: ILoadingLog, + private inline fun loadRegistry( executor: ForkJoinPool, registry: Registry, files: List, - noinline keyProvider: (T) -> Pair, - name: String = registry.name - ) { + noinline keyProvider: (T) -> Pair + ): List> { val adapter = Starbound.gson.getAdapter(T::class.java) val elementAdapter = Starbound.gson.getAdapter(JsonElement::class.java) - loadStage(log, loader = { - it.maxElements = files.size - - files.batch(executor) { listedFile -> + return files.map { listedFile -> + executor.submit { try { - it.text = "Loading $listedFile" - AssetPathStack(listedFile.computeDirectory()) { val elem = elementAdapter.read(listedFile.jsonReader()) val read = adapter.fromJsonTree(elem) val keys = keyProvider(read) - KOptional.of { - try { - if (keys.second != null) - registry.add(keys.first, keys.second!!, read, elem, listedFile) - else - registry.add(keys.first, read, elem, listedFile) - } catch (err: Throwable) { - LOGGER.error("Loading ${registry.name} definition file $listedFile", err) - } + registry.add { + if (keys.second != null) + registry.add(keys.first, keys.second!!, read, elem, listedFile) + else + registry.add(keys.first, read, elem, listedFile) } } } catch (err: Throwable) { LOGGER.error("Loading ${registry.name} definition file $listedFile", err) - KOptional.empty() - } finally { - it.elements.incrementAndGet() } - }.forEach { it.invoke() } - }, name) + } + } } - fun load(log: ILoadingLog, fileTree: Map>, executor: ForkJoinPool): List> { + fun finishLoad() { + registries.forEach { it.finishLoad() } + } + + fun load(fileTree: Map>, executor: ForkJoinPool): List> { val tasks = ArrayList>() - tasks.addAll(loadItemDefinitions(log, fileTree, executor)) + tasks.addAll(loadItemDefinitions(fileTree, executor)) - tasks.add(executor.submit { loadStage(log, { loadJsonFunctions(it, fileTree["functions"] ?: listOf()) }, "json functions") }) - tasks.add(executor.submit { loadStage(log, { loadJson2Functions(it, fileTree["2functions"] ?: listOf()) }, "json 2functions") }) - tasks.add(executor.submit { loadStage(log, { loadTreasurePools(it, fileTree["treasurepools"] ?: listOf()) }, "treasure pools") }) + tasks.addAll(loadJsonFunctions(fileTree["functions"] ?: listOf(), executor)) + tasks.addAll(loadJson2Functions(fileTree["2functions"] ?: listOf(), executor)) + tasks.addAll(loadTreasurePools(fileTree["treasurepools"] ?: listOf(), executor)) - tasks.add(executor.submit { loadStage(log, executor, tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)) }) - tasks.add(executor.submit { loadStage(log, executor, tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId)) }) - tasks.add(executor.submit { loadStage(log, executor, liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)) }) + tasks.addAll(loadRegistry(executor, tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId))) + tasks.addAll(loadRegistry(executor, tileModifiers, fileTree["matmod"] ?: listOf(), key(MaterialModifier::modName, MaterialModifier::modId))) + tasks.addAll(loadRegistry(executor, liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId))) - tasks.add(executor.submit { loadStage(log, executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)) }) - tasks.add(executor.submit { loadStage(log, executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)) }) - tasks.add(executor.submit { loadStage(log, executor, species, fileTree["species"] ?: listOf(), key(Species::kind)) }) - tasks.add(executor.submit { loadStage(log, executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)) }) - tasks.add(executor.submit { loadStage(log, executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)) }) - tasks.add(executor.submit { loadStage(log, executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)) }) - tasks.add(executor.submit { loadStage(log, executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)) }) - // tasks.add(executor.submit { loadStage(log, executor, projectiles, ext2files["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)) }) - // tasks.add(executor.submit { loadStage(log, executor, tenants, ext2files["tenant"] ?: listOf(), key(TenantDefinition::name)) }) - tasks.add(executor.submit { loadStage(log, executor, monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)) }) + tasks.addAll(loadRegistry(executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName))) + tasks.addAll(loadRegistry(executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name))) + tasks.addAll(loadRegistry(executor, species, fileTree["species"] ?: listOf(), key(Species::kind))) + tasks.addAll(loadRegistry(executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind))) + tasks.addAll(loadRegistry(executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id))) + tasks.addAll(loadRegistry(executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name))) + tasks.addAll(loadRegistry(executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type))) + tasks.addAll(loadRegistry(executor, monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name))) return tasks } - private fun loadItemDefinitions(log: ILoadingLog, files: Map>, executor: ForkJoinPool): List> { + private fun loadItemDefinitions(files: Map>, executor: ForkJoinPool): List> { val fileMap = mapOf( "item" to ItemDefinition::class.java, "currency" to CurrencyItemDefinition::class.java, @@ -201,108 +176,86 @@ object Registries { val fileList = files[ext] ?: continue val adapter = Starbound.gson.getAdapter(clazz) - tasks.add(executor.submit { - val line = log.line("Loading items '$ext'") - line.maxElements = fileList.size - val time = System.nanoTime() - - ParallelPerform(fileList.spliterator(), { listedFile -> + for (listedFile in fileList) { + tasks.add(executor.submit { try { - line.text = "Loading $listedFile" val json = objects.read(listedFile.jsonReader()) val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) } - items.add(def.itemName, def, json, listedFile) + + items.add { + items.add(def.itemName, def, json, listedFile) + } } catch (err: Throwable) { LOGGER.error("Loading item definition file $listedFile", err) - } finally { - line.elements.incrementAndGet() } - }).fork().join() - - line.text = "Loaded items '$ext' in ${((System.nanoTime() - time) / 1_000_000.0).toLong()}ms" - }) + }) + } } return tasks } - private fun loadJsonFunctions(line: ILoadingLog.ILine, files: Collection) { - for (listedFile in files) { - line.text = ("Loading $listedFile") + private fun loadJsonFunctions(files: Collection, executor: ForkJoinPool): List> { + return files.map { listedFile -> + executor.submit { + try { + val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - try { - val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - - for ((k, v) in json.entrySet()) { - try { - line.text = ("Loading $k from $listedFile") - val fn = Starbound.gson.fromJson(JsonTreeReader(v), JsonFunction::class.java) - jsonFunctions.add(k, fn, v, listedFile) - } catch (err: Exception) { - LOGGER.error("Loading json function definition $k from file $listedFile", err) + for ((k, v) in json.entrySet()) { + try { + val fn = Starbound.gson.fromJson(JsonTreeReader(v), JsonFunction::class.java) + jsonFunctions.add(k, fn, v, listedFile) + } catch (err: Exception) { + LOGGER.error("Loading json function definition $k from file $listedFile", err) + } } + } catch (err: Exception) { + LOGGER.error("Loading json function definition $listedFile", err) } - } catch (err: Exception) { - LOGGER.error("Loading json function definition $listedFile", err) - } - - if (Starbound.terminateLoading) { - return } } } - private fun loadJson2Functions(line: ILoadingLog.ILine, files: Collection) { - for (listedFile in files) { - line.text = ("Loading $listedFile") + private fun loadJson2Functions(files: Collection, executor: ForkJoinPool): List> { + return files.map { listedFile -> + executor.submit { + try { + val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - try { - val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - - for ((k, v) in json.entrySet()) { - try { - line.text = ("Loading $k from $listedFile") - val fn = Starbound.gson.fromJson(JsonTreeReader(v), Json2Function::class.java) - json2Functions.add(k, fn, v, listedFile) - } catch (err: Throwable) { - LOGGER.error("Loading json 2function definition $k from file $listedFile", err) + for ((k, v) in json.entrySet()) { + try { + val fn = Starbound.gson.fromJson(JsonTreeReader(v), Json2Function::class.java) + json2Functions.add(k, fn, v, listedFile) + } catch (err: Throwable) { + LOGGER.error("Loading json 2function definition $k from file $listedFile", err) + } } + } catch (err: Exception) { + LOGGER.error("Loading json 2function definition $listedFile", err) } - } catch (err: Exception) { - LOGGER.error("Loading json 2function definition $listedFile", err) - } - - if (Starbound.terminateLoading) { - return } } } - private fun loadTreasurePools(line: ILoadingLog.ILine, files: Collection) { - for (listedFile in files) { - line.text = ("Loading $listedFile") + private fun loadTreasurePools(files: Collection, executor: ForkJoinPool): List> { + return files.map { listedFile -> + executor.submit { + try { + val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - try { - val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) - - for ((k, v) in json.entrySet()) { - try { - line.text = ("Loading $k from $listedFile") - val result = Starbound.gson.fromJson(JsonTreeReader(v), TreasurePoolDefinition::class.java) - result.name = k - treasurePools.add(result.name, result, v, listedFile) - } catch (err: Throwable) { - LOGGER.error("Loading treasure pool definition $k from file $listedFile", err) + for ((k, v) in json.entrySet()) { + try { + val result = Starbound.gson.fromJson(JsonTreeReader(v), TreasurePoolDefinition::class.java) + result.name = k + treasurePools.add(result.name, result, v, listedFile) + } catch (err: Throwable) { + LOGGER.error("Loading treasure pool definition $k from file $listedFile", err) + } } + } catch (err: Exception) { + LOGGER.error("Loading treasure pool definition $listedFile", err) } - } catch (err: Exception) { - LOGGER.error("Loading treasure pool definition $listedFile", err) - } - - if (Starbound.terminateLoading) { - return } } } - } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt index 65d2d6ed..5b51a98e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Registry.kt @@ -30,6 +30,7 @@ import kotlin.collections.contains import kotlin.collections.set import kotlin.concurrent.withLock import ru.dbotthepony.kstarbound.util.traverseJsonPath +import java.util.concurrent.ConcurrentLinkedQueue inline fun Registry.adapter(): TypeAdapterFactory { return object : TypeAdapterFactory { @@ -97,6 +98,21 @@ class Registry(val name: String) { private val idsInternal = Int2ObjectOpenHashMap() private val keyRefs = Object2ObjectOpenHashMap() private val idRefs = Int2ObjectOpenHashMap() + private val backlog = ConcurrentLinkedQueue() + + // it is much cheaper to queue registry additions rather than locking during high congestion + fun add(task: Runnable) { + backlog.add(task) + } + + fun finishLoad() { + var next = backlog.poll() + + while (next != null) { + next.run() + next = backlog.poll() + } + } private val lock = ReentrantLock() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 028004b1..30b9cd56 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -285,6 +285,8 @@ object Starbound : ISBFileLocator { private set var bootstrapped = false private set + var loadingProgress = 0.0 + private set @Volatile var terminateLoading = false @@ -395,7 +397,7 @@ object Starbound : ISBFileLocator { checkMailbox() } - private fun doInitialize(log: ILoadingLog, parallel: Boolean) { + private fun doInitialize(parallel: Boolean) { if (!initializing && !initialized) { initializing = true } else { @@ -404,8 +406,6 @@ object Starbound : ISBFileLocator { doBootstrap() - val time = System.currentTimeMillis() - val ext2files = fileSystems.parallelStream() .flatMap { it.explore() } .filter { it.isFile } @@ -445,32 +445,37 @@ object Starbound : ISBFileLocator { val tasks = ArrayList>() val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1) - tasks.addAll(Registries.load(log, ext2files, pool)) - tasks.addAll(RecipeRegistry.load(log, ext2files, pool)) - tasks.addAll(GlobalDefaults.load(log, pool)) + tasks.addAll(Registries.load(ext2files, pool)) + tasks.addAll(RecipeRegistry.load(ext2files, pool)) + tasks.addAll(GlobalDefaults.load(pool)) - tasks.forEach { it.join() } + 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 - log.line("Finished loading in ${System.currentTimeMillis() - time}ms") } - fun initializeGame(log: ILoadingLog, parallel: Boolean = true) { - mailbox.submit { - doInitialize(log, parallel) - } + fun initializeGame(parallel: Boolean = true) { + mailbox.submit { doInitialize(parallel) } } fun bootstrapGame() { - mailbox.submit { - doBootstrap() - } + mailbox.submit { doBootstrap() } } private fun checkMailbox() { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index bf2a0145..5bfdfac8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -13,7 +13,6 @@ import org.lwjgl.opengl.GL45.* import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil -import ru.dbotthepony.kstarbound.LoadingLog import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf @@ -166,8 +165,6 @@ class StarboundClient : Closeable { private val onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>() private val terminateCallbacks = ArrayList<() -> Unit>() - val loadingLog = LoadingLog() - private val openglCleanQueue = ReferenceQueue() private var openglCleanQueueHead: CleanRef? = null @@ -214,7 +211,6 @@ class StarboundClient : Closeable { window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } - loadingLog.line("Created GLFW window") input.installCallback(window) @@ -269,7 +265,6 @@ class StarboundClient : Closeable { GLFW.glfwSwapInterval(0) GLFW.glfwShowWindow(window) - loadingLog.line("Initialized GLFW window") } val maxTextureBlocks = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS) @@ -761,37 +756,13 @@ class StarboundClient : Closeable { private val dotTime = 4 private var dotInc = 1 - private fun renderLoadingText() { - var alpha = 1f + private fun drawPerformanceBasic(onlyMemory: Boolean) { + val runtime = Runtime.getRuntime() - if (System.nanoTime() - loadingLog.lastActivity >= 3_000_000_000L) { - alpha = 1f - (System.nanoTime() - loadingLog.lastActivity - 3_000_000_000L) / 1_000_000_000f - } - - stack.push() - stack.last().translate(y = viewportHeight.toFloat()) - - var shade = 255 - - for (line in loadingLog) { - if (line.progress in 0.01f ..< 1f) { - quadColor(RGBAColor(0f, shade / 400f * line.progress, 0f, alpha * 0.8f)) { - it.vertex(0f, font.lineHeight * -0.4f) - it.vertex(viewportWidth * line.progress, font.lineHeight * -0.4f) - it.vertex(viewportWidth * line.progress, 0f) - it.vertex(0f, 0f) - } - } - - val size = font.render(line.text, alignY = TextAlignY.BOTTOM, scale = 0.4f, color = RGBAColor(shade / 255f, shade / 255f, shade / 255f, alpha)) - stack.last().translate(y = -size.height * 1.2f) - - if (shade > 120) { - shade -= 10 - } - } - - stack.pop() + if (!onlyMemory) font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) + if (!onlyMemory) font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) + font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f) + if (!onlyMemory) font.render("OGL C: $openglObjectsCreated D: $openglObjectsCleaned A: ${openglObjectsCreated - openglObjectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f) } fun renderFrame(): Boolean { @@ -855,45 +826,50 @@ class StarboundClient : Closeable { program.colorMultiplier = RGBAColor.WHITE - if (!fontInitialized) { - builder.builder.begin(GeometryType.QUADS) + builder.builder.begin(GeometryType.QUADS) - dotsIndex += dotInc + dotsIndex += dotInc - if (dotsIndex < 0) { - dotsIndex = 1 - dotInc = 1 - } else if (dotsIndex > dotTime * 3) { - dotsIndex = dotTime * 3 - 1 - dotInc = -1 - } - - val a = if (dotsIndex / dotTime == 0) RGBAColor.SLATE_GRAY else RGBAColor.WHITE - val b = if (dotsIndex / dotTime == 1) RGBAColor.SLATE_GRAY else RGBAColor.WHITE - val c = if (dotsIndex / dotTime == 2) RGBAColor.SLATE_GRAY else RGBAColor.WHITE - - builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f - size).color(a) - builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f - size).color(a) - builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f + size).color(a) - builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f + size).color(a) - - builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f - size).color(b) - builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f - size).color(b) - builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f + size).color(b) - builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f + size).color(b) - - builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f - size).color(c) - builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f - size).color(c) - builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f + size).color(c) - builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f + size).color(c) - - program.use() - builder.upload() - builder.draw() - } else { - renderLoadingText() + if (dotsIndex < 0) { + dotsIndex = 1 + dotInc = 1 + } else if (dotsIndex > dotTime * 3) { + dotsIndex = dotTime * 3 - 1 + dotInc = -1 } + val a = if (dotsIndex / dotTime == 0) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + val b = if (dotsIndex / dotTime == 1) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + val c = if (dotsIndex / dotTime == 2) RGBAColor.SLATE_GRAY else RGBAColor.WHITE + + builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f - size).color(a) + builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f - size).color(a) + builder.builder.vertex(viewportWidth * 0.5f + size - size * 4f, viewportHeight * 0.5f + size).color(a) + builder.builder.vertex(viewportWidth * 0.5f - size - size * 4f, viewportHeight * 0.5f + size).color(a) + + builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f - size).color(b) + builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f - size).color(b) + builder.builder.vertex(viewportWidth * 0.5f + size, viewportHeight * 0.5f + size).color(b) + builder.builder.vertex(viewportWidth * 0.5f - size, viewportHeight * 0.5f + size).color(b) + + builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f - size).color(c) + builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f - size).color(c) + builder.builder.vertex(viewportWidth * 0.5f + size + size * 4f, viewportHeight * 0.5f + size).color(c) + builder.builder.vertex(viewportWidth * 0.5f - size + size * 4f, viewportHeight * 0.5f + size).color(c) + + builder.builder.vertex(0f, viewportHeight - 20f).color(RGBAColor.GREEN) + builder.builder.vertex(viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight - 20f).color(RGBAColor.GREEN) + builder.builder.vertex(viewportWidth * Starbound.loadingProgress.toFloat(), viewportHeight.toFloat()).color(RGBAColor.GREEN) + builder.builder.vertex(0f, viewportHeight.toFloat()).color(RGBAColor.GREEN) + + if (fontInitialized) { + drawPerformanceBasic(true) + } + + program.use() + builder.upload() + builder.draw() + GLFW.glfwSwapBuffers(window) GLFW.glfwPollEvents() @@ -993,22 +969,13 @@ class StarboundClient : Closeable { uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } - if (System.nanoTime() - loadingLog.lastActivity <= 4_000_000_000L) { - renderLoadingText() - } - stack.clear(Matrix3f.identity()) for (fn in onDrawGUI) { fn.invoke() } - val runtime = Runtime.getRuntime() - - font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) - font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) - font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f) - font.render("OGL C: $openglObjectsCreated D: $openglObjectsCleaned A: ${openglObjectsCreated - openglObjectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f) + drawPerformanceBasic(false) GLFW.glfwSwapBuffers(window) GLFW.glfwPollEvents()