Get rid of loading log since it stalls threads

This commit is contained in:
DBotThePony 2023-12-01 20:16:21 +07:00
parent 35fc841037
commit efadaeb28c
Signed by: DBot
GPG Key ID: DCC23B5715498507
8 changed files with 225 additions and 378 deletions

View File

@ -52,18 +52,13 @@ object GlobalDefaults {
} }
} }
fun load(log: ILoadingLog, executor: ForkJoinPool): List<ForkJoinTask<*>> { fun load(executor: ForkJoinPool): List<ForkJoinTask<*>> {
val tasks = ArrayList<ForkJoinTask<*>>() val tasks = ArrayList<ForkJoinTask<*>>()
tasks.add(load("/default_actor_movement.config", ::playerMovementParameters, executor)) tasks.add(load("/default_actor_movement.config", ::playerMovementParameters, executor))
tasks.add(load("/default_movement.config", ::movementParameters, executor)) tasks.add(load("/default_movement.config", ::movementParameters, executor))
tasks.add(load("/client.config", ::clientParameters, executor)) tasks.add(load("/client.config", ::clientParameters, executor))
return listOf(executor.submit { return tasks
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"
})
} }
} }

View File

@ -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<ILoadingLog.ILine> {
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<ILine> {
return ObjectIterators.emptyIterator()
}
}
}
class LoadingLog : ILoadingLog {
private val lines = arrayOfNulls<Line>(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<ILoadingLog.ILine> {
return object : Iterator<ILoadingLog.ILine> {
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]!!
}
}
}
}

View File

@ -63,7 +63,7 @@ fun main() {
//Starbound.addPakPath(File("packed.pak")) //Starbound.addPakPath(File("packed.pak"))
Starbound.initializeGame(client.loadingLog) Starbound.initializeGame()
var ply: PlayerEntity? = null var ply: PlayerEntity? = null

View File

@ -9,6 +9,7 @@ import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition
import ru.dbotthepony.kstarbound.util.KOptional import ru.dbotthepony.kstarbound.util.KOptional
import ru.dbotthepony.kstarbound.util.ParallelPerform import ru.dbotthepony.kstarbound.util.ParallelPerform
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ForkJoinTask import java.util.concurrent.ForkJoinTask
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
@ -33,63 +34,60 @@ object RecipeRegistry {
val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking) val output2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking)
val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking) val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking)
private val lock = ReentrantLock() private val backlog = ConcurrentLinkedQueue<Entry>()
fun add(recipe: Entry) { private fun add(recipe: Entry) {
lock.withLock { val value = recipe.value
val value = recipe.value recipesInternal.add(recipe)
recipesInternal.add(recipe)
for (group in value.groups) { for (group in value.groups) {
group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p -> group2recipesInternal.computeIfAbsent(group, Object2ObjectFunction { p ->
ArrayList<Entry>(1).also {
group2recipesBacking[p as String] = Collections.unmodifiableList(it)
}
}).add(recipe)
}
output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p ->
ArrayList<Entry>(1).also { ArrayList<Entry>(1).also {
output2recipesBacking[p as String] = Collections.unmodifiableList(it) group2recipesBacking[p as String] = Collections.unmodifiableList(it)
} }
}).add(recipe) }).add(recipe)
}
for (input in value.input) { output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p ->
input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p -> ArrayList<Entry>(1).also {
ArrayList<Entry>(1).also { output2recipesBacking[p as String] = Collections.unmodifiableList(it)
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
}
}).add(recipe)
} }
}).add(recipe)
for (input in value.input) {
input2recipesInternal.computeIfAbsent(input.item.key.left(), Object2ObjectFunction { p ->
ArrayList<Entry>(1).also {
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
}
}).add(recipe)
} }
} }
fun load(log: ILoadingLog, fileTree: Map<String, List<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> { fun finishLoad() {
var next = backlog.poll()
while (next != null) {
add(next)
next = backlog.poll()
}
}
fun load(fileTree: Map<String, List<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
val files = fileTree["recipe"] ?: return emptyList() val files = fileTree["recipe"] ?: return emptyList()
val elements = Starbound.gson.getAdapter(JsonElement::class.java) val elements = Starbound.gson.getAdapter(JsonElement::class.java)
val recipes = Starbound.gson.getAdapter(RecipeDefinition::class.java) val recipes = Starbound.gson.getAdapter(RecipeDefinition::class.java)
return listOf(executor.submit { return files.map { listedFile ->
val line = log.line("Loading recipes...") executor.submit {
val time = System.nanoTime()
line.maxElements = files.size
files.batch(executor) { listedFile ->
try { try {
line.text = ("Loading $listedFile")
val json = elements.read(listedFile.jsonReader()) val json = elements.read(listedFile.jsonReader())
val value = recipes.fromJsonTree(json) val value = recipes.fromJsonTree(json)
KOptional.of(Entry(value, json, listedFile)) backlog.add(Entry(value, json, listedFile))
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Loading recipe definition file $listedFile", err) 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"
})
} }
} }

View File

@ -35,32 +35,32 @@ import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.util.AssetPathStack 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.ForkJoinPool
import java.util.concurrent.ForkJoinTask import java.util.concurrent.ForkJoinTask
object Registries { object Registries {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
val tiles = Registry<TileDefinition>("tiles") private val registries = ArrayList<Registry<*>>()
val tileModifiers = Registry<MaterialModifier>("tile modifiers")
val liquid = Registry<LiquidDefinition>("liquid") val tiles = Registry<TileDefinition>("tiles").also(registries::add)
val species = Registry<Species>("species") val tileModifiers = Registry<MaterialModifier>("tile modifiers").also(registries::add)
val statusEffects = Registry<StatusEffectDefinition>("status effects") val liquid = Registry<LiquidDefinition>("liquid").also(registries::add)
val particles = Registry<ParticleDefinition>("particles") val species = Registry<Species>("species").also(registries::add)
val items = Registry<IItemDefinition>("items") val statusEffects = Registry<StatusEffectDefinition>("status effects").also(registries::add)
val questTemplates = Registry<QuestTemplate>("quest templates") val particles = Registry<ParticleDefinition>("particles").also(registries::add)
val techs = Registry<TechDefinition>("techs") val items = Registry<IItemDefinition>("items").also(registries::add)
val jsonFunctions = Registry<JsonFunction>("json functions") val questTemplates = Registry<QuestTemplate>("quest templates").also(registries::add)
val json2Functions = Registry<Json2Function>("json 2functions") val techs = Registry<TechDefinition>("techs").also(registries::add)
val npcTypes = Registry<NpcTypeDefinition>("npc types") val jsonFunctions = Registry<JsonFunction>("json functions").also(registries::add)
val projectiles = Registry<ProjectileDefinition>("projectiles") val json2Functions = Registry<Json2Function>("json 2functions").also(registries::add)
val tenants = Registry<TenantDefinition>("tenants") val npcTypes = Registry<NpcTypeDefinition>("npc types").also(registries::add)
val treasurePools = Registry<TreasurePoolDefinition>("treasure pools") val projectiles = Registry<ProjectileDefinition>("projectiles").also(registries::add)
val monsterSkills = Registry<MonsterSkillDefinition>("monster skills") val tenants = Registry<TenantDefinition>("tenants").also(registries::add)
val monsterTypes = Registry<MonsterTypeDefinition>("monster types") val treasurePools = Registry<TreasurePoolDefinition>("treasure pools").also(registries::add)
val worldObjects = Registry<ObjectDefinition>("objects") val monsterSkills = Registry<MonsterSkillDefinition>("monster skills").also(registries::add)
val monsterTypes = Registry<MonsterTypeDefinition>("monster types").also(registries::add)
val worldObjects = Registry<ObjectDefinition>("objects").also(registries::add)
private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, Int?> { private fun <T> key(mapper: (T) -> String): (T) -> Pair<String, Int?> {
return { mapper.invoke(it) to null } return { mapper.invoke(it) to null }
@ -95,92 +95,67 @@ object Registries {
return !any return !any
} }
private fun loadStage( private inline fun <reified T : Any> loadRegistry(
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 <reified T : Any> loadStage(
log: ILoadingLog,
executor: ForkJoinPool, executor: ForkJoinPool,
registry: Registry<T>, registry: Registry<T>,
files: List<IStarboundFile>, files: List<IStarboundFile>,
noinline keyProvider: (T) -> Pair<String, Int?>, noinline keyProvider: (T) -> Pair<String, Int?>
name: String = registry.name ): List<ForkJoinTask<*>> {
) {
val adapter = Starbound.gson.getAdapter(T::class.java) val adapter = Starbound.gson.getAdapter(T::class.java)
val elementAdapter = Starbound.gson.getAdapter(JsonElement::class.java) val elementAdapter = Starbound.gson.getAdapter(JsonElement::class.java)
loadStage(log, loader = { return files.map { listedFile ->
it.maxElements = files.size executor.submit {
files.batch(executor) { listedFile ->
try { try {
it.text = "Loading $listedFile"
AssetPathStack(listedFile.computeDirectory()) { AssetPathStack(listedFile.computeDirectory()) {
val elem = elementAdapter.read(listedFile.jsonReader()) val elem = elementAdapter.read(listedFile.jsonReader())
val read = adapter.fromJsonTree(elem) val read = adapter.fromJsonTree(elem)
val keys = keyProvider(read) val keys = keyProvider(read)
KOptional.of { registry.add {
try { if (keys.second != null)
if (keys.second != null) registry.add(keys.first, keys.second!!, read, elem, listedFile)
registry.add(keys.first, keys.second!!, read, elem, listedFile) else
else registry.add(keys.first, read, elem, listedFile)
registry.add(keys.first, read, elem, listedFile)
} catch (err: Throwable) {
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
}
} }
} }
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Loading ${registry.name} definition file $listedFile", err) 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<String, List<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> { fun finishLoad() {
registries.forEach { it.finishLoad() }
}
fun load(fileTree: Map<String, List<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
val tasks = ArrayList<ForkJoinTask<*>>() val tasks = ArrayList<ForkJoinTask<*>>()
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.addAll(loadJsonFunctions(fileTree["functions"] ?: listOf(), executor))
tasks.add(executor.submit { loadStage(log, { loadJson2Functions(it, fileTree["2functions"] ?: listOf()) }, "json 2functions") }) tasks.addAll(loadJson2Functions(fileTree["2functions"] ?: listOf(), executor))
tasks.add(executor.submit { loadStage(log, { loadTreasurePools(it, fileTree["treasurepools"] ?: listOf()) }, "treasure pools") }) tasks.addAll(loadTreasurePools(fileTree["treasurepools"] ?: listOf(), executor))
tasks.add(executor.submit { loadStage(log, executor, tiles, fileTree["material"] ?: listOf(), key(TileDefinition::materialName, TileDefinition::materialId)) }) tasks.addAll(loadRegistry(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.addAll(loadRegistry(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, liquid, fileTree["liquid"] ?: listOf(), key(LiquidDefinition::name, LiquidDefinition::liquidId)))
tasks.add(executor.submit { loadStage(log, executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)) }) tasks.addAll(loadRegistry(executor, worldObjects, fileTree["object"] ?: listOf(), key(ObjectDefinition::objectName)))
tasks.add(executor.submit { loadStage(log, executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)) }) tasks.addAll(loadRegistry(executor, statusEffects, fileTree["statuseffect"] ?: listOf(), key(StatusEffectDefinition::name)))
tasks.add(executor.submit { loadStage(log, executor, species, fileTree["species"] ?: listOf(), key(Species::kind)) }) tasks.addAll(loadRegistry(executor, species, fileTree["species"] ?: listOf(), key(Species::kind)))
tasks.add(executor.submit { loadStage(log, executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)) }) tasks.addAll(loadRegistry(executor, particles, fileTree["particle"] ?: listOf(), key(ParticleDefinition::kind)))
tasks.add(executor.submit { loadStage(log, executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)) }) tasks.addAll(loadRegistry(executor, questTemplates, fileTree["questtemplate"] ?: listOf(), key(QuestTemplate::id)))
tasks.add(executor.submit { loadStage(log, executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)) }) tasks.addAll(loadRegistry(executor, techs, fileTree["tech"] ?: listOf(), key(TechDefinition::name)))
tasks.add(executor.submit { loadStage(log, executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)) }) tasks.addAll(loadRegistry(executor, npcTypes, fileTree["npctype"] ?: listOf(), key(NpcTypeDefinition::type)))
// tasks.add(executor.submit { loadStage(log, executor, projectiles, ext2files["projectile"] ?: listOf(), key(ProjectileDefinition::projectileName)) }) tasks.addAll(loadRegistry(executor, monsterSkills, fileTree["monsterskill"] ?: listOf(), key(MonsterSkillDefinition::name)))
// 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)) })
return tasks return tasks
} }
private fun loadItemDefinitions(log: ILoadingLog, files: Map<String, Collection<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> { private fun loadItemDefinitions(files: Map<String, Collection<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
val fileMap = mapOf( val fileMap = mapOf(
"item" to ItemDefinition::class.java, "item" to ItemDefinition::class.java,
"currency" to CurrencyItemDefinition::class.java, "currency" to CurrencyItemDefinition::class.java,
@ -201,108 +176,86 @@ object Registries {
val fileList = files[ext] ?: continue val fileList = files[ext] ?: continue
val adapter = Starbound.gson.getAdapter(clazz) val adapter = Starbound.gson.getAdapter(clazz)
tasks.add(executor.submit { for (listedFile in fileList) {
val line = log.line("Loading items '$ext'") tasks.add(executor.submit {
line.maxElements = fileList.size
val time = System.nanoTime()
ParallelPerform(fileList.spliterator(), { listedFile ->
try { try {
line.text = "Loading $listedFile"
val json = objects.read(listedFile.jsonReader()) val json = objects.read(listedFile.jsonReader())
val def = AssetPathStack(listedFile.computeDirectory()) { adapter.fromJsonTree(json) } 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) { } catch (err: Throwable) {
LOGGER.error("Loading item definition file $listedFile", err) 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 return tasks
} }
private fun loadJsonFunctions(line: ILoadingLog.ILine, files: Collection<IStarboundFile>) { private fun loadJsonFunctions(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
for (listedFile in files) { return files.map { listedFile ->
line.text = ("Loading $listedFile") executor.submit {
try {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
try { for ((k, v) in json.entrySet()) {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) try {
val fn = Starbound.gson.fromJson<JsonFunction>(JsonTreeReader(v), JsonFunction::class.java)
for ((k, v) in json.entrySet()) { jsonFunctions.add(k, fn, v, listedFile)
try { } catch (err: Exception) {
line.text = ("Loading $k from $listedFile") LOGGER.error("Loading json function definition $k from file $listedFile", err)
val fn = Starbound.gson.fromJson<JsonFunction>(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<IStarboundFile>) { private fun loadJson2Functions(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
for (listedFile in files) { return files.map { listedFile ->
line.text = ("Loading $listedFile") executor.submit {
try {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
try { for ((k, v) in json.entrySet()) {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) try {
val fn = Starbound.gson.fromJson<Json2Function>(JsonTreeReader(v), Json2Function::class.java)
for ((k, v) in json.entrySet()) { json2Functions.add(k, fn, v, listedFile)
try { } catch (err: Throwable) {
line.text = ("Loading $k from $listedFile") LOGGER.error("Loading json 2function definition $k from file $listedFile", err)
val fn = Starbound.gson.fromJson<Json2Function>(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<IStarboundFile>) { private fun loadTreasurePools(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
for (listedFile in files) { return files.map { listedFile ->
line.text = ("Loading $listedFile") executor.submit {
try {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true })
try { for ((k, v) in json.entrySet()) {
val json = Starbound.gson.getAdapter(JsonObject::class.java).read(JsonReader(listedFile.reader()).also { it.isLenient = true }) try {
val result = Starbound.gson.fromJson<TreasurePoolDefinition>(JsonTreeReader(v), TreasurePoolDefinition::class.java)
for ((k, v) in json.entrySet()) { result.name = k
try { treasurePools.add(result.name, result, v, listedFile)
line.text = ("Loading $k from $listedFile") } catch (err: Throwable) {
val result = Starbound.gson.fromJson<TreasurePoolDefinition>(JsonTreeReader(v), TreasurePoolDefinition::class.java) LOGGER.error("Loading treasure pool definition $k from file $listedFile", err)
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
} }
} }
} }
} }

View File

@ -30,6 +30,7 @@ import kotlin.collections.contains
import kotlin.collections.set import kotlin.collections.set
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
import ru.dbotthepony.kstarbound.util.traverseJsonPath import ru.dbotthepony.kstarbound.util.traverseJsonPath
import java.util.concurrent.ConcurrentLinkedQueue
inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory { inline fun <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
return object : TypeAdapterFactory { return object : TypeAdapterFactory {
@ -97,6 +98,21 @@ class Registry<T : Any>(val name: String) {
private val idsInternal = Int2ObjectOpenHashMap<Impl>() private val idsInternal = Int2ObjectOpenHashMap<Impl>()
private val keyRefs = Object2ObjectOpenHashMap<String, RefImpl>() private val keyRefs = Object2ObjectOpenHashMap<String, RefImpl>()
private val idRefs = Int2ObjectOpenHashMap<RefImpl>() private val idRefs = Int2ObjectOpenHashMap<RefImpl>()
private val backlog = ConcurrentLinkedQueue<Runnable>()
// 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() private val lock = ReentrantLock()

View File

@ -285,6 +285,8 @@ object Starbound : ISBFileLocator {
private set private set
var bootstrapped = false var bootstrapped = false
private set private set
var loadingProgress = 0.0
private set
@Volatile @Volatile
var terminateLoading = false var terminateLoading = false
@ -395,7 +397,7 @@ object Starbound : ISBFileLocator {
checkMailbox() checkMailbox()
} }
private fun doInitialize(log: ILoadingLog, parallel: Boolean) { private fun doInitialize(parallel: Boolean) {
if (!initializing && !initialized) { if (!initializing && !initialized) {
initializing = true initializing = true
} else { } else {
@ -404,8 +406,6 @@ object Starbound : ISBFileLocator {
doBootstrap() doBootstrap()
val time = System.currentTimeMillis()
val ext2files = fileSystems.parallelStream() val ext2files = fileSystems.parallelStream()
.flatMap { it.explore() } .flatMap { it.explore() }
.filter { it.isFile } .filter { it.isFile }
@ -445,32 +445,37 @@ object Starbound : ISBFileLocator {
val tasks = ArrayList<ForkJoinTask<*>>() val tasks = ArrayList<ForkJoinTask<*>>()
val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1) val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1)
tasks.addAll(Registries.load(log, ext2files, pool)) tasks.addAll(Registries.load(ext2files, pool))
tasks.addAll(RecipeRegistry.load(log, ext2files, pool)) tasks.addAll(RecipeRegistry.load(ext2files, pool))
tasks.addAll(GlobalDefaults.load(log, 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) if (!parallel)
pool.shutdown() pool.shutdown()
Registries.finishLoad()
RecipeRegistry.finishLoad()
Registries.validate() Registries.validate()
initializing = false initializing = false
initialized = true initialized = true
log.line("Finished loading in ${System.currentTimeMillis() - time}ms")
} }
fun initializeGame(log: ILoadingLog, parallel: Boolean = true) { fun initializeGame(parallel: Boolean = true) {
mailbox.submit { mailbox.submit { doInitialize(parallel) }
doInitialize(log, parallel)
}
} }
fun bootstrapGame() { fun bootstrapGame() {
mailbox.submit { mailbox.submit { doBootstrap() }
doBootstrap()
}
} }
private fun checkMailbox() { private fun checkMailbox() {

View File

@ -13,7 +13,6 @@ import org.lwjgl.opengl.GL45.*
import org.lwjgl.opengl.GLCapabilities import org.lwjgl.opengl.GLCapabilities
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.LoadingLog
import ru.dbotthepony.kstarbound.util.MailboxExecutorService import ru.dbotthepony.kstarbound.util.MailboxExecutorService
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNITf 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 onViewportChanged = ArrayList<(width: Int, height: Int) -> Unit>()
private val terminateCallbacks = ArrayList<() -> Unit>() private val terminateCallbacks = ArrayList<() -> Unit>()
val loadingLog = LoadingLog()
private val openglCleanQueue = ReferenceQueue<Any>() private val openglCleanQueue = ReferenceQueue<Any>()
private var openglCleanQueueHead: CleanRef? = null private var openglCleanQueueHead: CleanRef? = null
@ -214,7 +211,6 @@ class StarboundClient : Closeable {
window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL) window = GLFW.glfwCreateWindow(800, 600, "KStarbound", MemoryUtil.NULL, MemoryUtil.NULL)
require(window != MemoryUtil.NULL) { "Unable to create GLFW window" } require(window != MemoryUtil.NULL) { "Unable to create GLFW window" }
loadingLog.line("Created GLFW window")
input.installCallback(window) input.installCallback(window)
@ -269,7 +265,6 @@ class StarboundClient : Closeable {
GLFW.glfwSwapInterval(0) GLFW.glfwSwapInterval(0)
GLFW.glfwShowWindow(window) GLFW.glfwShowWindow(window)
loadingLog.line("Initialized GLFW window")
} }
val maxTextureBlocks = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS) val maxTextureBlocks = glGetInteger(GL_MAX_TEXTURE_IMAGE_UNITS)
@ -761,37 +756,13 @@ class StarboundClient : Closeable {
private val dotTime = 4 private val dotTime = 4
private var dotInc = 1 private var dotInc = 1
private fun renderLoadingText() { private fun drawPerformanceBasic(onlyMemory: Boolean) {
var alpha = 1f val runtime = Runtime.getRuntime()
if (System.nanoTime() - loadingLog.lastActivity >= 3_000_000_000L) { if (!onlyMemory) font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f)
alpha = 1f - (System.nanoTime() - loadingLog.lastActivity - 3_000_000_000L) / 1_000_000_000f 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)
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()
} }
fun renderFrame(): Boolean { fun renderFrame(): Boolean {
@ -855,45 +826,50 @@ class StarboundClient : Closeable {
program.colorMultiplier = RGBAColor.WHITE program.colorMultiplier = RGBAColor.WHITE
if (!fontInitialized) { builder.builder.begin(GeometryType.QUADS)
builder.builder.begin(GeometryType.QUADS)
dotsIndex += dotInc dotsIndex += dotInc
if (dotsIndex < 0) { if (dotsIndex < 0) {
dotsIndex = 1 dotsIndex = 1
dotInc = 1 dotInc = 1
} else if (dotsIndex > dotTime * 3) { } else if (dotsIndex > dotTime * 3) {
dotsIndex = dotTime * 3 - 1 dotsIndex = dotTime * 3 - 1
dotInc = -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()
} }
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.glfwSwapBuffers(window)
GLFW.glfwPollEvents() GLFW.glfwPollEvents()
@ -993,22 +969,13 @@ class StarboundClient : Closeable {
uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } uberShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen } fontShaderPrograms.forEachValid { it.viewMatrix = viewportMatrixScreen }
if (System.nanoTime() - loadingLog.lastActivity <= 4_000_000_000L) {
renderLoadingText()
}
stack.clear(Matrix3f.identity()) stack.clear(Matrix3f.identity())
for (fn in onDrawGUI) { for (fn in onDrawGUI) {
fn.invoke() fn.invoke()
} }
val runtime = Runtime.getRuntime() drawPerformanceBasic(false)
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)
GLFW.glfwSwapBuffers(window) GLFW.glfwSwapBuffers(window)
GLFW.glfwPollEvents() GLFW.glfwPollEvents()