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<*>>()
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
}
}

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.initializeGame(client.loadingLog)
Starbound.initializeGame()
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.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<String, List<Entry>> = Collections.unmodifiableMap(output2recipesBacking)
val input2recipes: Map<String, List<Entry>> = Collections.unmodifiableMap(input2recipesBacking)
private val lock = ReentrantLock()
private val backlog = ConcurrentLinkedQueue<Entry>()
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<Entry>(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<Entry>(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<Entry>(1).also {
input2recipesBacking[p as String] = Collections.unmodifiableList(it)
}
}).add(recipe)
output2recipesInternal.computeIfAbsent(value.output.item.key.left(), Object2ObjectFunction { p ->
ArrayList<Entry>(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<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 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"
})
}
}
}
}

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.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<TileDefinition>("tiles")
val tileModifiers = Registry<MaterialModifier>("tile modifiers")
val liquid = Registry<LiquidDefinition>("liquid")
val species = Registry<Species>("species")
val statusEffects = Registry<StatusEffectDefinition>("status effects")
val particles = Registry<ParticleDefinition>("particles")
val items = Registry<IItemDefinition>("items")
val questTemplates = Registry<QuestTemplate>("quest templates")
val techs = Registry<TechDefinition>("techs")
val jsonFunctions = Registry<JsonFunction>("json functions")
val json2Functions = Registry<Json2Function>("json 2functions")
val npcTypes = Registry<NpcTypeDefinition>("npc types")
val projectiles = Registry<ProjectileDefinition>("projectiles")
val tenants = Registry<TenantDefinition>("tenants")
val treasurePools = Registry<TreasurePoolDefinition>("treasure pools")
val monsterSkills = Registry<MonsterSkillDefinition>("monster skills")
val monsterTypes = Registry<MonsterTypeDefinition>("monster types")
val worldObjects = Registry<ObjectDefinition>("objects")
private val registries = ArrayList<Registry<*>>()
val tiles = Registry<TileDefinition>("tiles").also(registries::add)
val tileModifiers = Registry<MaterialModifier>("tile modifiers").also(registries::add)
val liquid = Registry<LiquidDefinition>("liquid").also(registries::add)
val species = Registry<Species>("species").also(registries::add)
val statusEffects = Registry<StatusEffectDefinition>("status effects").also(registries::add)
val particles = Registry<ParticleDefinition>("particles").also(registries::add)
val items = Registry<IItemDefinition>("items").also(registries::add)
val questTemplates = Registry<QuestTemplate>("quest templates").also(registries::add)
val techs = Registry<TechDefinition>("techs").also(registries::add)
val jsonFunctions = Registry<JsonFunction>("json functions").also(registries::add)
val json2Functions = Registry<Json2Function>("json 2functions").also(registries::add)
val npcTypes = Registry<NpcTypeDefinition>("npc types").also(registries::add)
val projectiles = Registry<ProjectileDefinition>("projectiles").also(registries::add)
val tenants = Registry<TenantDefinition>("tenants").also(registries::add)
val treasurePools = Registry<TreasurePoolDefinition>("treasure pools").also(registries::add)
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?> {
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 <reified T : Any> loadStage(
log: ILoadingLog,
private inline fun <reified T : Any> loadRegistry(
executor: ForkJoinPool,
registry: Registry<T>,
files: List<IStarboundFile>,
noinline keyProvider: (T) -> Pair<String, Int?>,
name: String = registry.name
) {
noinline keyProvider: (T) -> Pair<String, Int?>
): List<ForkJoinTask<*>> {
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<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<*>>()
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<String, Collection<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
private fun loadItemDefinitions(files: Map<String, Collection<IStarboundFile>>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
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<IStarboundFile>) {
for (listedFile in files) {
line.text = ("Loading $listedFile")
private fun loadJsonFunctions(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
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<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)
for ((k, v) in json.entrySet()) {
try {
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>) {
for (listedFile in files) {
line.text = ("Loading $listedFile")
private fun loadJson2Functions(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
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<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)
for ((k, v) in json.entrySet()) {
try {
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>) {
for (listedFile in files) {
line.text = ("Loading $listedFile")
private fun loadTreasurePools(files: Collection<IStarboundFile>, executor: ForkJoinPool): List<ForkJoinTask<*>> {
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<TreasurePoolDefinition>(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<TreasurePoolDefinition>(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
}
}
}
}

View File

@ -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 <reified S : Any> Registry<S>.adapter(): TypeAdapterFactory {
return object : TypeAdapterFactory {
@ -97,6 +98,21 @@ class Registry<T : Any>(val name: String) {
private val idsInternal = Int2ObjectOpenHashMap<Impl>()
private val keyRefs = Object2ObjectOpenHashMap<String, 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()

View File

@ -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<ForkJoinTask<*>>()
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() {

View File

@ -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<Any>()
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()