646 lines
19 KiB
Kotlin
646 lines
19 KiB
Kotlin
package ru.dbotthepony.kstarbound
|
|
|
|
import com.github.benmanes.caffeine.cache.Interner
|
|
import com.google.gson.*
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
|
|
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.asCoroutineDispatcher
|
|
import org.apache.logging.log4j.LogManager
|
|
import ru.dbotthepony.kommons.gson.AABBTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.AABBiTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.EitherTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.KOptionalTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.NothingAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector2dTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector2fTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector2iTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector3dTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector3fTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector3iTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector4dTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector4fTypeAdapter
|
|
import ru.dbotthepony.kommons.gson.Vector4iTypeAdapter
|
|
import ru.dbotthepony.kstarbound.collect.WeightedList
|
|
import ru.dbotthepony.kstarbound.defs.*
|
|
import ru.dbotthepony.kstarbound.defs.image.Image
|
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
|
import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition
|
|
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
|
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
|
import ru.dbotthepony.kstarbound.defs.actor.player.BlueprintLearnList
|
|
import ru.dbotthepony.kstarbound.defs.animation.Particle
|
|
import ru.dbotthepony.kstarbound.defs.quest.QuestParameter
|
|
import ru.dbotthepony.kstarbound.defs.world.CelestialParameters
|
|
import ru.dbotthepony.kstarbound.defs.world.VisitableWorldParametersType
|
|
import ru.dbotthepony.kstarbound.defs.world.BiomePlaceables
|
|
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementDistributionType
|
|
import ru.dbotthepony.kstarbound.defs.world.BiomePlacementItemType
|
|
import ru.dbotthepony.kstarbound.defs.world.WorldLayout
|
|
import ru.dbotthepony.kstarbound.world.terrain.TerrainSelectorType
|
|
import ru.dbotthepony.kstarbound.io.*
|
|
import ru.dbotthepony.kstarbound.json.factory.MapsTypeAdapterFactory
|
|
import ru.dbotthepony.kstarbound.json.InternedJsonElementAdapter
|
|
import ru.dbotthepony.kstarbound.json.InternedStringAdapter
|
|
import ru.dbotthepony.kstarbound.json.LongRangeAdapter
|
|
import ru.dbotthepony.kstarbound.json.builder.EnumAdapter
|
|
import ru.dbotthepony.kstarbound.json.builder.BuilderAdapter
|
|
import ru.dbotthepony.kstarbound.json.builder.FactoryAdapter
|
|
import ru.dbotthepony.kstarbound.json.JsonImplementationTypeFactory
|
|
import ru.dbotthepony.kstarbound.json.factory.CollectionAdapterFactory
|
|
import ru.dbotthepony.kstarbound.json.factory.ImmutableCollectionAdapterFactory
|
|
import ru.dbotthepony.kstarbound.json.factory.PairAdapterFactory
|
|
import ru.dbotthepony.kstarbound.json.factory.RGBAColorTypeAdapter
|
|
import ru.dbotthepony.kstarbound.json.factory.SingletonTypeAdapterFactory
|
|
import ru.dbotthepony.kstarbound.server.world.UniverseChunk
|
|
import ru.dbotthepony.kstarbound.item.ItemStack
|
|
import ru.dbotthepony.kstarbound.json.JsonAdapterTypeFactory
|
|
import ru.dbotthepony.kstarbound.json.JsonPath
|
|
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
|
import ru.dbotthepony.kstarbound.util.Directives
|
|
import ru.dbotthepony.kstarbound.util.ExceptionLogger
|
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
|
import ru.dbotthepony.kstarbound.util.HashTableInterner
|
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
|
import ru.dbotthepony.kstarbound.util.random.AbstractPerlinNoise
|
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
|
import java.io.*
|
|
import java.lang.ref.Cleaner
|
|
import java.text.DateFormat
|
|
import java.util.concurrent.CompletableFuture
|
|
import java.util.concurrent.ExecutorService
|
|
import java.util.concurrent.ForkJoinPool
|
|
import java.util.concurrent.Future
|
|
import java.util.concurrent.LinkedBlockingQueue
|
|
import java.util.concurrent.ThreadFactory
|
|
import java.util.concurrent.ThreadPoolExecutor
|
|
import java.util.concurrent.TimeUnit
|
|
import java.util.concurrent.atomic.AtomicInteger
|
|
import java.util.concurrent.locks.LockSupport
|
|
import java.util.function.BiConsumer
|
|
import java.util.function.BinaryOperator
|
|
import java.util.function.Function
|
|
import java.util.function.Supplier
|
|
import java.util.stream.Collector
|
|
import kotlin.NoSuchElementException
|
|
import kotlin.collections.ArrayList
|
|
|
|
object Starbound : ISBFileLocator {
|
|
const val ENGINE_VERSION = "0.0.1"
|
|
const val NATIVE_PROTOCOL_VERSION = 748
|
|
const val LEGACY_PROTOCOL_VERSION = 747
|
|
const val TIMESTEP = 1.0 / 60.0
|
|
const val TIMESTEP_NANOS = (TIMESTEP * 1_000_000_000L).toLong()
|
|
|
|
// compile flags. uuuugh
|
|
const val DEDUP_CELL_STATES = true
|
|
const val USE_CAFFEINE_INTERNER = false
|
|
|
|
fun <E : Any> interner(): Interner<E> {
|
|
return if (USE_CAFFEINE_INTERNER) Interner.newWeakInterner() else HashTableInterner()
|
|
}
|
|
|
|
fun <E : Any> interner(bits: Int): Interner<E> {
|
|
return if (USE_CAFFEINE_INTERNER) Interner.newWeakInterner() else HashTableInterner(bits)
|
|
}
|
|
|
|
private val LOGGER = LogManager.getLogger()
|
|
|
|
val thread = Thread(::universeThread, "Universe Thread")
|
|
val mailbox = MailboxExecutorService(thread).also { it.exceptionHandler = ExceptionLogger(LOGGER) }
|
|
val mailboxBootstrapped = MailboxExecutorService(thread).also { it.exceptionHandler = ExceptionLogger(LOGGER) }
|
|
val mailboxInitialized = MailboxExecutorService(thread).also { it.exceptionHandler = ExceptionLogger(LOGGER) }
|
|
|
|
init {
|
|
thread.isDaemon = true
|
|
thread.start()
|
|
}
|
|
|
|
private val ioPoolCounter = AtomicInteger()
|
|
|
|
@JvmField
|
|
val IO_EXECUTOR: ExecutorService = ThreadPoolExecutor(0, 64, 30L, TimeUnit.SECONDS, LinkedBlockingQueue(), ThreadFactory {
|
|
val thread = Thread(it, "Starbound Storage IO ${ioPoolCounter.getAndIncrement()}")
|
|
thread.isDaemon = true
|
|
thread.priority = Thread.MIN_PRIORITY
|
|
|
|
thread.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
|
|
LOGGER.error("I/O thread died due to uncaught exception", e)
|
|
}
|
|
|
|
return@ThreadFactory thread
|
|
})
|
|
|
|
@JvmField
|
|
val EXECUTOR: ForkJoinPool = ForkJoinPool.commonPool()
|
|
@JvmField
|
|
val COROUTINE_EXECUTOR = EXECUTOR.asCoroutineDispatcher()
|
|
@JvmField
|
|
val COROUTINES = CoroutineScope(COROUTINE_EXECUTOR)
|
|
|
|
@JvmField
|
|
val CLEANER: Cleaner = Cleaner.create {
|
|
val t = Thread(it, "Starbound Global Cleaner")
|
|
t.isDaemon = true
|
|
t.priority = 2
|
|
t
|
|
}
|
|
|
|
// currently Caffeine one saves only 4 megabytes of RAM on pretty big modpack
|
|
// Hrm.
|
|
// val strings: Interner<String> = Interner.newWeakInterner()
|
|
// val strings: Interner<String> = Interner { it }
|
|
@JvmField
|
|
val STRINGS: Interner<String> = interner(5)
|
|
|
|
// immeasurably lazy and fragile solution, too bad!
|
|
// While having four separate Gson instances look like a (much) better solution (and it indeed could have been!),
|
|
// we must not forget the fact that 'Starbound' and 'Consistent data format' are opposites,
|
|
// and there are cases of where discStore() calls toJson() on children data, despite it having its own discStore() too.
|
|
var IS_LEGACY_JSON: Boolean by ThreadLocal.withInitial { false }
|
|
private set
|
|
var IS_STORE_JSON: Boolean by ThreadLocal.withInitial { false }
|
|
private set
|
|
|
|
fun legacyJson(data: Any): JsonElement {
|
|
try {
|
|
IS_LEGACY_JSON = true
|
|
return gson.toJsonTree(data)
|
|
} finally {
|
|
IS_LEGACY_JSON = false
|
|
}
|
|
}
|
|
|
|
fun storeJson(data: Any): JsonElement {
|
|
try {
|
|
IS_STORE_JSON = true
|
|
return gson.toJsonTree(data)
|
|
} finally {
|
|
IS_STORE_JSON = false
|
|
}
|
|
}
|
|
|
|
fun legacyStoreJson(data: Any): JsonElement {
|
|
try {
|
|
IS_STORE_JSON = true
|
|
IS_LEGACY_JSON = true
|
|
return gson.toJsonTree(data)
|
|
} finally {
|
|
IS_STORE_JSON = false
|
|
IS_LEGACY_JSON = false
|
|
}
|
|
}
|
|
|
|
fun <T> legacyJson(block: () -> T): T {
|
|
try {
|
|
IS_LEGACY_JSON = true
|
|
return block.invoke()
|
|
} finally {
|
|
IS_LEGACY_JSON = false
|
|
}
|
|
}
|
|
|
|
fun <T> storeJson(block: () -> T): T {
|
|
try {
|
|
IS_STORE_JSON = true
|
|
return block.invoke()
|
|
} finally {
|
|
IS_STORE_JSON = false
|
|
}
|
|
}
|
|
|
|
fun <T> legacyStoreJson(block: () -> T): T {
|
|
try {
|
|
IS_STORE_JSON = true
|
|
IS_LEGACY_JSON = true
|
|
return block.invoke()
|
|
} finally {
|
|
IS_STORE_JSON = false
|
|
IS_LEGACY_JSON = false
|
|
}
|
|
}
|
|
|
|
val gson: Gson = with(GsonBuilder()) {
|
|
// serializeNulls()
|
|
setDateFormat(DateFormat.LONG)
|
|
setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
|
|
setPrettyPrinting()
|
|
|
|
registerTypeAdapter(InternedStringAdapter(STRINGS))
|
|
|
|
InternedJsonElementAdapter(STRINGS).also {
|
|
registerTypeAdapter(it)
|
|
registerTypeAdapter(it.arrays)
|
|
registerTypeAdapter(it.objects)
|
|
}
|
|
|
|
registerTypeAdapter(Nothing::class.java, NothingAdapter)
|
|
|
|
// Обработчик @JsonImplementation
|
|
registerTypeAdapterFactory(JsonImplementationTypeFactory)
|
|
registerTypeAdapterFactory(JsonAdapterTypeFactory)
|
|
|
|
// списки, наборы, т.п.
|
|
registerTypeAdapterFactory(CollectionAdapterFactory)
|
|
|
|
// ImmutableList, ImmutableSet, ImmutableMap
|
|
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS))
|
|
|
|
// fastutil collections
|
|
registerTypeAdapterFactory(MapsTypeAdapterFactory(STRINGS))
|
|
|
|
// все enum'ы без особых настроек
|
|
registerTypeAdapterFactory(EnumAdapter.Companion)
|
|
|
|
// @JsonBuilder
|
|
registerTypeAdapterFactory(BuilderAdapter.Factory(STRINGS))
|
|
|
|
// @JsonFactory
|
|
registerTypeAdapterFactory(FactoryAdapter.Factory(STRINGS))
|
|
|
|
// Either<>
|
|
registerTypeAdapterFactory(EitherTypeAdapter)
|
|
// KOptional<>
|
|
registerTypeAdapterFactory(KOptionalTypeAdapter)
|
|
|
|
registerTypeAdapterFactory(SingletonTypeAdapterFactory)
|
|
|
|
// Pair<>
|
|
registerTypeAdapterFactory(PairAdapterFactory)
|
|
registerTypeAdapterFactory(SBPattern.Companion)
|
|
|
|
registerTypeAdapterFactory(JsonReference.Companion)
|
|
|
|
registerTypeAdapter(ColorReplacements.Companion)
|
|
registerTypeAdapterFactory(BlueprintLearnList.Companion)
|
|
|
|
registerTypeAdapter(RGBAColorTypeAdapter)
|
|
|
|
registerTypeAdapterFactory(NativeLegacy.Companion)
|
|
|
|
// математические классы
|
|
registerTypeAdapter(AABBTypeAdapter.nullSafe())
|
|
registerTypeAdapter(AABBiTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector2dTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector2fTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector2iTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector3dTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector3fTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector3iTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector4iTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector4dTypeAdapter.nullSafe())
|
|
registerTypeAdapter(Vector4fTypeAdapter.nullSafe())
|
|
registerTypeAdapterFactory(AbstractPerlinNoise.Companion)
|
|
registerTypeAdapterFactory(WeightedList.Companion)
|
|
|
|
// Функции
|
|
registerTypeAdapter(JsonFunction.CONSTRAINT_ADAPTER)
|
|
registerTypeAdapter(JsonFunction.INTERPOLATION_ADAPTER)
|
|
registerTypeAdapter(JsonFunction.Companion)
|
|
registerTypeAdapter(JsonConfigFunction::Adapter)
|
|
registerTypeAdapterFactory(Json2Function.Companion)
|
|
|
|
// Общее
|
|
registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
|
|
registerTypeAdapterFactory(TerrainSelectorType.Companion)
|
|
|
|
registerTypeAdapter(Directives.Companion)
|
|
|
|
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.DAMAGE))
|
|
|
|
registerTypeAdapter(InventoryIcon.Companion)
|
|
|
|
registerTypeAdapterFactory(IArmorItemDefinition.Frames.Factory)
|
|
registerTypeAdapterFactory(AssetPath.Companion)
|
|
registerTypeAdapter(SpriteReference.Companion)
|
|
|
|
registerTypeAdapterFactory(AssetReference.Companion)
|
|
|
|
registerTypeAdapter(ItemStack.Adapter(this@Starbound))
|
|
|
|
registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
|
|
|
|
registerTypeAdapterFactory(UniverseChunk.Companion)
|
|
|
|
registerTypeAdapter(Image.Companion)
|
|
|
|
registerTypeAdapterFactory(Poly.Companion)
|
|
|
|
registerTypeAdapter(CelestialParameters::Adapter)
|
|
registerTypeAdapter(Particle::Adapter)
|
|
registerTypeAdapter(QuestParameter::Adapter)
|
|
|
|
registerTypeAdapterFactory(BiomePlacementDistributionType.DEFINITION_ADAPTER)
|
|
registerTypeAdapterFactory(BiomePlacementItemType.DATA_ADAPTER)
|
|
registerTypeAdapterFactory(BiomePlacementItemType.DEFINITION_ADAPTER)
|
|
registerTypeAdapterFactory(BiomePlaceables.Item.Companion)
|
|
|
|
// register companion first, so it has lesser priority than dispatching adapter
|
|
registerTypeAdapterFactory(VisitableWorldParametersType.Companion)
|
|
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
|
registerTypeAdapter(WorldLayout.Companion)
|
|
|
|
Registries.registerAdapters(this)
|
|
|
|
registerTypeAdapter(LongRangeAdapter)
|
|
|
|
create()
|
|
}
|
|
|
|
data class ItemConfig(
|
|
val directory: String?,
|
|
val parameters: JsonObject,
|
|
val config: JsonObject
|
|
)
|
|
|
|
fun itemConfig(name: String, parameters: JsonObject, level: Double? = null, seed: Long? = null): ItemConfig {
|
|
val obj = Registries.items[name] ?: throw NoSuchElementException("No such item $name")
|
|
|
|
val directory = obj.file?.computeDirectory()
|
|
val parameters = parameters.deepCopy()
|
|
|
|
TODO()
|
|
}
|
|
|
|
fun item(name: String): ItemStack {
|
|
TODO()
|
|
}
|
|
|
|
fun item(name: String, count: Long): ItemStack {
|
|
TODO()
|
|
}
|
|
|
|
fun item(name: String, count: Long, parameters: JsonObject): ItemStack {
|
|
TODO()
|
|
}
|
|
|
|
fun item(descriptor: JsonObject): ItemStack {
|
|
return item(
|
|
(descriptor["name"] as? JsonPrimitive)?.asString ?: return ItemStack.EMPTY,
|
|
descriptor["count"]?.asLong ?: return ItemStack.EMPTY,
|
|
(descriptor["parameters"] as? JsonObject)?.deepCopy() ?: JsonObject()
|
|
)
|
|
}
|
|
|
|
fun item(descriptor: JsonElement?): ItemStack {
|
|
if (descriptor is JsonPrimitive) {
|
|
return item(descriptor.asString)
|
|
} else if (descriptor is JsonObject) {
|
|
return item(descriptor)
|
|
} else {
|
|
return ItemStack.EMPTY
|
|
}
|
|
}
|
|
|
|
var initializing = false
|
|
private set
|
|
var initialized = false
|
|
private set
|
|
var bootstrapping = false
|
|
private set
|
|
var bootstrapped = false
|
|
private set
|
|
var loadingProgress = 0.0
|
|
private set
|
|
var toLoad = 0
|
|
private set
|
|
var loaded = 0
|
|
private set
|
|
|
|
@Volatile
|
|
var terminateLoading = false
|
|
|
|
fun loadJsonAsset(path: String): JsonElement? {
|
|
val filename: String
|
|
val jsonPath: String?
|
|
|
|
if (path.contains(':')) {
|
|
filename = path.substringBefore(':')
|
|
jsonPath = path.substringAfter(':')
|
|
} else {
|
|
filename = path
|
|
jsonPath = null
|
|
}
|
|
|
|
val file = locate(filename)
|
|
|
|
if (!file.isFile)
|
|
return null
|
|
|
|
val pathTraverser = if (jsonPath == null) JsonPath.EMPTY else JsonPath.query(jsonPath)
|
|
return pathTraverser.get(gson.fromJson(file.reader(), JsonElement::class.java))
|
|
}
|
|
|
|
private val archivePaths = ArrayList<File>()
|
|
private val fileSystems = ArrayList<IStarboundFile>()
|
|
|
|
fun addFilePath(path: File) {
|
|
fileSystems.add(PhysicalFile(path))
|
|
}
|
|
|
|
fun addPak(pak: StarboundPak) {
|
|
fileSystems.add(pak.root)
|
|
}
|
|
|
|
override fun exists(path: String): Boolean {
|
|
@Suppress("name_shadowing")
|
|
var path = path
|
|
|
|
if (path[0] == '/') {
|
|
path = path.substring(1)
|
|
}
|
|
|
|
for (fs in fileSystems) {
|
|
if (fs.locate(path).exists) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
override fun locate(path: String): IStarboundFile {
|
|
@Suppress("name_shadowing")
|
|
var path = path
|
|
|
|
if (path[0] == '/') {
|
|
path = path.substring(1)
|
|
}
|
|
|
|
for (fs in fileSystems) {
|
|
val file = fs.locate(path)
|
|
|
|
if (file.exists) {
|
|
return file
|
|
}
|
|
}
|
|
|
|
return NonExistingFile(path.split("/").last(), fullPath = path)
|
|
}
|
|
|
|
fun locate(vararg path: String): IStarboundFile {
|
|
for (p in path) {
|
|
val get = locate(p)
|
|
|
|
if (get.exists) {
|
|
return get
|
|
}
|
|
}
|
|
|
|
return NonExistingFile(path[0].split("/").last(), fullPath = path[0])
|
|
}
|
|
|
|
/**
|
|
* Добавляет pak к чтению при initializeGame
|
|
*/
|
|
fun addPakPath(pak: File) {
|
|
archivePaths.add(pak)
|
|
}
|
|
|
|
fun doBootstrap() {
|
|
if (!bootstrapped && !bootstrapping) {
|
|
bootstrapping = true
|
|
} else {
|
|
return
|
|
}
|
|
|
|
for (path in archivePaths) {
|
|
LOGGER.info("Reading PAK archive $path")
|
|
addPak(StarboundPak(path))
|
|
}
|
|
|
|
LOGGER.info("Finished reading PAK archives")
|
|
bootstrapped = true
|
|
bootstrapping = false
|
|
|
|
checkMailbox()
|
|
}
|
|
|
|
private fun doInitialize() {
|
|
if (!initializing && !initialized) {
|
|
initializing = true
|
|
} else {
|
|
return
|
|
}
|
|
|
|
doBootstrap()
|
|
|
|
val ext2files = fileSystems.parallelStream()
|
|
.flatMap { it.explore() }
|
|
.filter { it.isFile }
|
|
.collect(object :
|
|
Collector<IStarboundFile, Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, Map<String, List<IStarboundFile>>>
|
|
{
|
|
override fun supplier(): Supplier<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>> {
|
|
return Supplier { Object2ObjectOpenHashMap() }
|
|
}
|
|
|
|
override fun accumulator(): BiConsumer<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, IStarboundFile> {
|
|
return BiConsumer { t, u ->
|
|
t.computeIfAbsent(u.name.substringAfterLast('.'), Object2ObjectFunction { ArrayList() }).add(u)
|
|
}
|
|
}
|
|
|
|
override fun combiner(): BinaryOperator<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>> {
|
|
return BinaryOperator { t, u ->
|
|
for ((k, v) in u)
|
|
t.computeIfAbsent(k, Object2ObjectFunction { ArrayList() }).addAll(v)
|
|
|
|
t
|
|
}
|
|
}
|
|
|
|
override fun finisher(): Function<Object2ObjectOpenHashMap<String, ArrayList<IStarboundFile>>, Map<String, List<IStarboundFile>>> {
|
|
return Function { it }
|
|
}
|
|
|
|
override fun characteristics(): Set<Collector.Characteristics> {
|
|
return setOf(Collector.Characteristics.IDENTITY_FINISH)
|
|
}
|
|
})
|
|
|
|
checkMailbox()
|
|
|
|
val tasks = ArrayList<Future<*>>()
|
|
|
|
tasks.addAll(Registries.load(ext2files))
|
|
tasks.addAll(RecipeRegistry.load(ext2files))
|
|
tasks.addAll(Globals.load())
|
|
tasks.add(VersionRegistry.load())
|
|
|
|
val total = tasks.size.toDouble()
|
|
toLoad = tasks.size
|
|
|
|
while (tasks.isNotEmpty()) {
|
|
tasks.removeIf { it.isDone }
|
|
checkMailbox()
|
|
loaded = toLoad - tasks.size
|
|
loadingProgress = (total - tasks.size) / total
|
|
LockSupport.parkNanos(5_000_000L)
|
|
}
|
|
|
|
Registries.finishLoad()
|
|
RecipeRegistry.finishLoad()
|
|
|
|
Registries.validate()
|
|
|
|
initializing = false
|
|
initialized = true
|
|
}
|
|
|
|
fun initializeGame(): Future<*> {
|
|
return mailbox.submit { doInitialize() }
|
|
}
|
|
|
|
fun bootstrapGame(): Future<*> {
|
|
return mailbox.submit { doBootstrap() }
|
|
}
|
|
|
|
private fun checkMailbox() {
|
|
mailbox.executeQueuedTasks()
|
|
|
|
if (bootstrapped)
|
|
mailboxBootstrapped.executeQueuedTasks()
|
|
|
|
if (initialized)
|
|
mailboxInitialized.executeQueuedTasks()
|
|
}
|
|
|
|
private var fontPath: File? = null
|
|
|
|
fun loadFont(): CompletableFuture<File> {
|
|
val fontPath = fontPath
|
|
|
|
if (fontPath != null)
|
|
return CompletableFuture.completedFuture(fontPath)
|
|
|
|
return CompletableFuture.supplyAsync(Supplier {
|
|
val fontPath = Starbound.fontPath
|
|
|
|
if (fontPath != null)
|
|
return@Supplier fontPath
|
|
|
|
val file = locate("/hobo.ttf")
|
|
|
|
if (!file.exists)
|
|
throw FileNotFoundException("Unable to locate font file /hobo.ttf")
|
|
else if (!file.isFile)
|
|
throw FileNotFoundException("/hobo.ttf is not a file!")
|
|
else {
|
|
val tempPath = File(System.getProperty("java.io.tmpdir"), "sb-hobo.ttf")
|
|
tempPath.writeBytes(file.read().array())
|
|
Starbound.fontPath = tempPath
|
|
return@Supplier tempPath
|
|
}
|
|
}, mailboxBootstrapped)
|
|
}
|
|
|
|
private fun universeThread() {
|
|
while (true) {
|
|
checkMailbox()
|
|
LockSupport.park()
|
|
}
|
|
}
|
|
}
|
|
|