diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 236e7f42..fcffc87b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -7,6 +7,7 @@ import com.google.gson.internal.bind.TypeAdapters import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.api.IStarboundFile @@ -47,6 +48,11 @@ import ru.dbotthepony.kstarbound.util.WriteOnce import java.io.* import java.text.DateFormat import java.util.* +import java.util.function.BiConsumer +import java.util.function.BinaryOperator +import java.util.function.Function +import java.util.function.Supplier +import java.util.stream.Collector import kotlin.collections.ArrayList const val METRES_IN_STARBOUND_UNIT = 0.5 @@ -273,6 +279,9 @@ object Starbound { loader: ((String) -> Unit) -> Unit, name: String, ) { + if (terminateLoading) + return + val time = System.currentTimeMillis() callback(false, false, "Loading $name...") LOGGER.info("Loading $name...") @@ -289,6 +298,150 @@ object Starbound { LOGGER.info("Loaded $name in ${System.currentTimeMillis() - time}ms") } + private fun loadStage( + callback: (Boolean, Boolean, String) -> Unit, + clazz: Class, + getKey: (T) -> K, + put: (K, T) -> T?, + files: List, + name: String, + ) { + loadStage(callback, loader = { + for (listedFile in files) { + try { + it("Loading $listedFile") + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), clazz) } + val key = getKey.invoke(def) + check(put.invoke(key, def) == null) { "Already has $name with name $key loaded!" } + } catch (err: Throwable) { + LOGGER.error("Loading $name definition file $listedFile", err) + } + + if (terminateLoading) { + break + } + } + }, name) + } + + private fun loadStage( + callback: (Boolean, Boolean, String) -> Unit, + clazz: Class, + getKey0: (T) -> K0, + put0: (K0, T) -> T?, + getKey1: (T) -> K1, + put1: (K1, T) -> T?, + files: List, + name: String, + ) { + loadStage(callback, loader = { + for (listedFile in files) { + try { + it("Loading $listedFile") + val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), clazz) } + val key0 = getKey0.invoke(def) + val key1 = getKey1.invoke(def) + check(put0.invoke(key0, def) == null) { "Already has $name with name $key0 loaded!" } + check(put1.invoke(key1, def) == null) { "Already has $name with ID $key1 loaded!" } + } catch (err: Throwable) { + LOGGER.error("Loading $name definition file $listedFile", err) + } + + if (terminateLoading) { + break + } + } + }, name) + } + + private fun doInitialize(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) { + var time = System.currentTimeMillis() + + if (archivePaths.isNotEmpty()) { + callback(false, false, "Reading pak archives...".also(LOGGER::info)) + + for (path in archivePaths) { + callback(false, false, "Reading ${path.name}...".also(LOGGER::info)) + + addPak(StarboundPak(path) { _, status -> + callback(false, true, "${path.name}: $status") + }) + } + } + + callback(false, false, "Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) + time = System.currentTimeMillis() + callback(false, false, "Building file index...".also(LOGGER::info)) + + val ext2files = fileSystems.parallelStream() + .flatMap { it.explore() } + .filter { it.isFile } + .collect(object : + Collector>, + Map>>, + Supplier>>, + BiConsumer>, IStarboundFile>, + BinaryOperator>> + { + override fun accept(t: Object2ObjectOpenHashMap>, u: IStarboundFile) { + t.computeIfAbsent(u.name.substringAfterLast('.'), Object2ObjectFunction { ArrayList() }).add(u) + } + + override fun get(): Object2ObjectOpenHashMap> { + return Object2ObjectOpenHashMap() + } + + override fun supplier(): Supplier>> { + return this + } + + override fun accumulator(): BiConsumer>, IStarboundFile> { + return this + } + + override fun apply( + t: Object2ObjectOpenHashMap>, + u: Object2ObjectOpenHashMap> + ): Object2ObjectOpenHashMap> { + t.putAll(u) + return t + } + + override fun combiner(): BinaryOperator>> { + return this + } + + override fun finisher(): Function>, Map>> { + return Function { it } + } + + override fun characteristics(): Set { + return setOf(Collector.Characteristics.IDENTITY_FINISH, Collector.Characteristics.UNORDERED) + } + }) + + callback(false, false, "Finished building file index in ${System.currentTimeMillis() - time}ms".also(LOGGER::info)) + + loadStage(callback, this::loadFunctions, "functions") + loadStage(callback, this::loadProjectiles, "projectiles") + loadStage(callback, this::loadParallax, "parallax definitions") + loadStage(callback, this::loadItemDefinitions, "item definitions") + + loadStage(callback, TileDefinition::class.java, TileDefinition::materialName, tiles::put, TileDefinition::materialId, tilesByMaterialID::put, ext2files["material"] ?: listOf(), "materials") + loadStage(callback, MaterialModifier::class.java, MaterialModifier::modName, tileModifiers::put, MaterialModifier::modId, tileModifiersByID::put, ext2files["matmod"] ?: listOf(), "material modifier definitions") + loadStage(callback, LiquidDefinition::class.java, LiquidDefinition::name, liquid::put, LiquidDefinition::liquidId, liquidByID::put, ext2files["liquid"] ?: listOf(), "liquid definitions") + loadStage(callback, StatusEffectDefinition::class.java, StatusEffectDefinition::name, statusEffects::put, ext2files["statuseffect"] ?: listOf(), "status effects") + loadStage(callback, Species::class.java, Species::kind, species::put, ext2files["species"] ?: listOf(), "species") + + pathStack.block("/") { + playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) + } + + initializing = false + initialized = true + callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms") + } + fun initializeGame(callback: (finished: Boolean, replaceStatus: Boolean, status: String) -> Unit) { if (initializing) { throw IllegalStateException("Already initializing!") @@ -299,39 +452,7 @@ object Starbound { } initializing = true - - Thread({ - val time = System.currentTimeMillis() - - if (archivePaths.isNotEmpty()) { - callback(false, false, "Reading pak archives...") - - for (path in archivePaths) { - callback(false, false, "Reading ${path.name}...") - - addPak(StarboundPak(path) { _, status -> - callback(false, true, "${path.name}: $status") - }) - } - } - - loadStage(callback, this::loadFunctions, "functions") - loadStage(callback, this::loadTileMaterials, "materials") - loadStage(callback, this::loadProjectiles, "projectiles") - loadStage(callback, this::loadParallax, "parallax definitions") - loadStage(callback, this::loadMaterialModifiers, "material modifier definitions") - loadStage(callback, this::loadLiquidDefinitions, "liquid definitions") - loadStage(callback, this::loadItemDefinitions, "item definitions") - loadStage(callback, this::loadSpecies, "species") - - pathStack.block("/") { - playerDefinition = GSON.fromJson(locate("/player.config").reader(), PlayerDefinition::class.java) - } - - initializing = false - initialized = true - callback(true, false, "Finished loading in ${System.currentTimeMillis() - time}ms") - }, "Asset Loader").start() + Thread({ doInitialize(callback) }, "Asset Loader").start() } fun onInitialize(callback: () -> Unit) { @@ -352,29 +473,6 @@ object Starbound { } } - private fun loadTileMaterials(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".material") }) { - try { - callback("Loading $listedFile") - - val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), TileDefinition::class.java) } - check(tiles[tileDef.materialName] == null) { "Already has material with name ${tileDef.materialName} loaded!" } - check(tilesByMaterialID[tileDef.materialId] == null) { "Already has material with ID ${tileDef.materialId} loaded!" } - tilesByMaterialID[tileDef.materialId] = tileDef - tiles[tileDef.materialName] = tileDef - } catch (err: Throwable) { - //throw TileDefLoadingException("Loading tile file $listedFile", err) - LOGGER.error("Loading tile file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - private fun loadProjectiles(callback: (String) -> Unit) { for (fs in fileSystems) { for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".projectile") }) { @@ -437,66 +535,6 @@ object Starbound { } } - private fun loadMaterialModifiers(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".matmod") }) { - try { - callback("Loading $listedFile") - val tileDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), MaterialModifier::class.java) } - check(tileModifiers[tileDef.modName] == null) { "Already has material with name ${tileDef.modName} loaded!" } - check(tileModifiersByID[tileDef.modId] == null) { "Already has material with ID ${tileDef.modId} loaded!" } - tileModifiersByID[tileDef.modId] = tileDef - tileModifiers[tileDef.modName] = tileDef - } catch (err: Throwable) { - //throw TileDefLoadingException("Loading tile file $listedFile", err) - LOGGER.error("Loading tile modifier file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - - private fun loadLiquidDefinitions(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".liquid") }) { - try { - callback("Loading $listedFile") - val liquidDef = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), LiquidDefinition::class.java) } - check(liquid.put(liquidDef.name, liquidDef) == null) { "Already has liquid with name ${liquidDef.name} loaded!" } - check(liquidByID.put(liquidDef.liquidId, liquidDef) == null) { "Already has liquid with ID ${liquidDef.liquidId} loaded!" } - } catch (err: Throwable) { - //throw TileDefLoadingException("Loading tile file $listedFile", err) - LOGGER.error("Loading liquid definition file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - - private fun loadSpecies(callback: (String) -> Unit) { - for (fs in fileSystems) { - for (listedFile in fs.explore().filter { it.isFile }.filter { it.name.endsWith(".species") }) { - try { - callback("Loading $listedFile") - val def = pathStack(listedFile.computeDirectory()) { GSON.fromJson(listedFile.reader(), Species::class.java) } - check(species.put(def.kind, def) == null) { "Already has liquid with name ${def.kind} loaded!" } - } catch (err: Throwable) { - LOGGER.error("Loading species definition file $listedFile", err) - } - - if (terminateLoading) { - return - } - } - } - } - private fun loadItemDefinitions(callback: (String) -> Unit) { val files = linkedMapOf( ".item" to ItemPrototype::class.java,