Sequential or parallel disk access now handled properly
This commit is contained in:
parent
999f3a8d4f
commit
ac55422c3b
@ -4,6 +4,9 @@ import com.google.common.collect.ImmutableList
|
|||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
import com.google.common.collect.ImmutableSet
|
import com.google.common.collect.ImmutableSet
|
||||||
import com.google.gson.TypeAdapter
|
import com.google.gson.TypeAdapter
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
import ru.dbotthepony.kstarbound.defs.ActorMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.ClientConfig
|
import ru.dbotthepony.kstarbound.defs.ClientConfig
|
||||||
@ -135,32 +138,31 @@ object Globals {
|
|||||||
words.build()
|
words.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: Lazy<TypeAdapter<T>>): Future<*> {
|
val onLoadedFuture = CompletableFuture<Unit>()
|
||||||
val file = Starbound.loadJsonAsset(path)
|
|
||||||
|
private suspend fun <T> load(path: String, accept: KMutableProperty0<T>, adapter: Lazy<TypeAdapter<T>>) {
|
||||||
|
val file = Starbound.loadJsonAsset(path).await()
|
||||||
|
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!")
|
LOGGER.fatal("$path does not exist or is not a file, expect bad things to happen!")
|
||||||
return CompletableFuture.completedFuture(Unit)
|
|
||||||
} else {
|
} else {
|
||||||
return Starbound.EXECUTOR.submit {
|
try {
|
||||||
try {
|
AssetPathStack("/") {
|
||||||
AssetPathStack("/") {
|
accept.set(adapter.value.fromJsonTree(file))
|
||||||
accept.set(adapter.value.fromJsonTree(file))
|
|
||||||
}
|
|
||||||
} catch (err: Throwable) {
|
|
||||||
LOGGER.fatal("Error while reading $path, expect bad things to happen!", err)
|
|
||||||
throw err
|
|
||||||
}
|
}
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.fatal("Error while reading $path, expect bad things to happen!", err)
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> load(path: String, accept: KMutableProperty0<T>): Future<*> {
|
private inline fun <reified T> load(path: String, accept: KMutableProperty0<T>): CompletableFuture<*> {
|
||||||
return load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) })
|
return Starbound.GLOBAL_SCOPE.launch { load(path, accept, lazy(LazyThreadSafetyMode.NONE) { Starbound.gson.getAdapter(T::class.java) }) }.asCompletableFuture()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun load(): List<Future<*>> {
|
fun load(): List<Future<*>> {
|
||||||
val tasks = ArrayList<Future<*>>()
|
val tasks = ArrayList<CompletableFuture<*>>()
|
||||||
|
|
||||||
tasks.add(load("/default_actor_movement.config", ::actorMovementParameters))
|
tasks.add(load("/default_actor_movement.config", ::actorMovementParameters))
|
||||||
tasks.add(load("/default_movement.config", ::movementParameters))
|
tasks.add(load("/default_movement.config", ::movementParameters))
|
||||||
@ -184,12 +186,18 @@ object Globals {
|
|||||||
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
tasks.add(load("/plants/bushDamage.config", ::bushDamage))
|
||||||
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
tasks.add(load("/tiles/defaultDamage.config", ::tileDamage))
|
||||||
|
|
||||||
tasks.add(load("/dungeon_worlds.config", ::dungeonWorlds, lazy { Starbound.gson.mapAdapter() }))
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/dungeon_worlds.config", ::dungeonWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
|
||||||
tasks.add(load("/currencies.config", ::currencies, lazy { Starbound.gson.mapAdapter() }))
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/currencies.config", ::currencies, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
|
||||||
tasks.add(load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }))
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/system_objects.config", ::systemObjects, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
|
||||||
tasks.add(load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }))
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/instance_worlds.config", ::instanceWorlds, lazy { Starbound.gson.mapAdapter() }) }.asCompletableFuture())
|
||||||
|
|
||||||
tasks.add(load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }))
|
tasks.add(Starbound.GLOBAL_SCOPE.launch { load("/names/profanityfilter.config", ::profanityFilterInternal, lazy { Starbound.gson.listAdapter() }) }.asCompletableFuture())
|
||||||
|
|
||||||
|
CompletableFuture.allOf(*tasks.toTypedArray()).thenApply {
|
||||||
|
onLoadedFuture.complete(Unit)
|
||||||
|
}.exceptionally {
|
||||||
|
onLoadedFuture.completeExceptionally(it)
|
||||||
|
}
|
||||||
|
|
||||||
return tasks
|
return tasks
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,10 @@ import com.google.gson.JsonObject
|
|||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
import com.google.gson.TypeAdapterFactory
|
import com.google.gson.TypeAdapterFactory
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.util.KOptional
|
import ru.dbotthepony.kommons.util.KOptional
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
@ -27,7 +31,7 @@ import ru.dbotthepony.kstarbound.defs.actor.player.TechDefinition
|
|||||||
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
import ru.dbotthepony.kstarbound.defs.animation.ParticleConfig
|
||||||
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasureChestDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
|
import ru.dbotthepony.kstarbound.defs.ProjectileDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
import ru.dbotthepony.kstarbound.defs.tile.RenderParameters
|
||||||
@ -100,7 +104,7 @@ object Registries {
|
|||||||
val futures = ArrayList<CompletableFuture<Boolean>>()
|
val futures = ArrayList<CompletableFuture<Boolean>>()
|
||||||
|
|
||||||
for (registry in registriesInternal)
|
for (registry in registriesInternal)
|
||||||
futures.add(CompletableFuture.supplyAsync { registry.validate() })
|
futures.add(CompletableFuture.supplyAsync({ registry.validate() }, Starbound.EXECUTOR))
|
||||||
|
|
||||||
return CompletableFuture.allOf(*futures.toTypedArray()).thenApply { futures.all { it.get() } }
|
return CompletableFuture.allOf(*futures.toTypedArray()).thenApply { futures.all { it.get() } }
|
||||||
}
|
}
|
||||||
@ -115,10 +119,11 @@ object Registries {
|
|||||||
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
||||||
|
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
Starbound.EXECUTOR.submit {
|
Starbound.GLOBAL_SCOPE.launch {
|
||||||
try {
|
try {
|
||||||
|
val elem = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()])
|
||||||
|
|
||||||
AssetPathStack(listedFile.computeDirectory()) {
|
AssetPathStack(listedFile.computeDirectory()) {
|
||||||
val elem = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patches[listedFile.computeFullPath()])
|
|
||||||
val read = adapter.fromJsonTree(elem)
|
val read = adapter.fromJsonTree(elem)
|
||||||
val keys = keyProvider(read)
|
val keys = keyProvider(read)
|
||||||
|
|
||||||
@ -137,7 +142,7 @@ object Registries {
|
|||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
|
LOGGER.error("Loading ${registry.name} definition file $listedFile", err)
|
||||||
}
|
}
|
||||||
}
|
}.asCompletableFuture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,9 +192,9 @@ object Registries {
|
|||||||
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
val adapter by lazy { Starbound.gson.getAdapter(T::class.java) }
|
||||||
|
|
||||||
return files.map { listedFile ->
|
return files.map { listedFile ->
|
||||||
Starbound.EXECUTOR.submit {
|
Starbound.GLOBAL_SCOPE.launch {
|
||||||
try {
|
try {
|
||||||
val json = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patches[listedFile.computeFullPath()]) as JsonObject
|
val json = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
||||||
|
|
||||||
for ((k, v) in json.entrySet()) {
|
for ((k, v) in json.entrySet()) {
|
||||||
try {
|
try {
|
||||||
@ -206,13 +211,13 @@ object Registries {
|
|||||||
} catch (err: Exception) {
|
} catch (err: Exception) {
|
||||||
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
LOGGER.error("Loading ${registry.name} definition $listedFile", err)
|
||||||
}
|
}
|
||||||
}
|
}.asCompletableFuture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
private suspend fun loadTerrainSelector(listedFile: IStarboundFile, type: TerrainSelectorType?, patches: Map<String, Collection<IStarboundFile>>) {
|
||||||
try {
|
try {
|
||||||
val json = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(listedFile.jsonReader()), patches[listedFile.computeFullPath()]) as JsonObject
|
val json = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(listedFile.asyncJsonReader().await()), patches[listedFile.computeFullPath()]) as JsonObject
|
||||||
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
|
val name = json["name"]?.asString ?: throw JsonSyntaxException("Missing 'name' field")
|
||||||
val factory = TerrainSelectorType.factory(json, false, type)
|
val factory = TerrainSelectorType.factory(json, false, type)
|
||||||
|
|
||||||
@ -228,17 +233,17 @@ object Registries {
|
|||||||
val tasks = ArrayList<Future<*>>()
|
val tasks = ArrayList<Future<*>>()
|
||||||
|
|
||||||
tasks.addAll((files["terrain"] ?: listOf()).map { listedFile ->
|
tasks.addAll((files["terrain"] ?: listOf()).map { listedFile ->
|
||||||
Starbound.EXECUTOR.submit {
|
Starbound.GLOBAL_SCOPE.async {
|
||||||
loadTerrainSelector(listedFile, null, patches)
|
loadTerrainSelector(listedFile, null, patches)
|
||||||
}
|
}.asCompletableFuture()
|
||||||
})
|
})
|
||||||
|
|
||||||
// legacy files
|
// legacy files
|
||||||
for (type in TerrainSelectorType.entries) {
|
for (type in TerrainSelectorType.entries) {
|
||||||
tasks.addAll((files[type.jsonName.lowercase()] ?: listOf()).map { listedFile ->
|
tasks.addAll((files[type.jsonName.lowercase()] ?: listOf()).map { listedFile ->
|
||||||
Starbound.EXECUTOR.submit {
|
Starbound.GLOBAL_SCOPE.async {
|
||||||
loadTerrainSelector(listedFile, type, patches)
|
loadTerrainSelector(listedFile, type, patches)
|
||||||
}
|
}.asCompletableFuture()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,8 +261,8 @@ object Registries {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private fun loadMetaMaterials(): Future<*> {
|
private fun loadMetaMaterials(): Future<*> {
|
||||||
return Starbound.EXECUTOR.submit {
|
return Starbound.GLOBAL_SCOPE.async {
|
||||||
val read = Starbound.loadJsonAsset("/metamaterials.config") ?: return@submit
|
val read = Starbound.loadJsonAsset("/metamaterials.config").await() ?: return@async
|
||||||
val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTree(read)
|
val read2 = Starbound.gson.getAdapter(object : TypeToken<ImmutableList<MetaMaterialDef>>() {}).fromJsonTree(read)
|
||||||
|
|
||||||
for (def in read2) {
|
for (def in read2) {
|
||||||
@ -282,6 +287,6 @@ object Registries {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}.asCompletableFuture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,20 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.AsyncCacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
import com.github.benmanes.caffeine.cache.Interner
|
import com.github.benmanes.caffeine.cache.Interner
|
||||||
import com.github.benmanes.caffeine.cache.Scheduler
|
import com.github.benmanes.caffeine.cache.Scheduler
|
||||||
|
import com.google.common.base.Predicate
|
||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
import it.unimi.dsi.fastutil.objects.ObjectArraySet
|
||||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Runnable
|
import kotlinx.coroutines.Runnable
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.asCoroutineDispatcher
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import org.classdump.luna.compiler.CompilerChunkLoader
|
import org.classdump.luna.compiler.CompilerChunkLoader
|
||||||
import org.classdump.luna.compiler.CompilerSettings
|
import org.classdump.luna.compiler.CompilerSettings
|
||||||
@ -55,6 +61,7 @@ import ru.dbotthepony.kstarbound.json.JsonAdapterTypeFactory
|
|||||||
import ru.dbotthepony.kstarbound.json.JsonPatch
|
import ru.dbotthepony.kstarbound.json.JsonPatch
|
||||||
import ru.dbotthepony.kstarbound.json.JsonPath
|
import ru.dbotthepony.kstarbound.json.JsonPath
|
||||||
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
import ru.dbotthepony.kstarbound.json.NativeLegacy
|
||||||
|
import ru.dbotthepony.kstarbound.json.factory.CompletableFutureAdapter
|
||||||
import ru.dbotthepony.kstarbound.math.AABBiTypeAdapter
|
import ru.dbotthepony.kstarbound.math.AABBiTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2dTypeAdapter
|
import ru.dbotthepony.kstarbound.math.Vector2dTypeAdapter
|
||||||
import ru.dbotthepony.kstarbound.math.Vector2fTypeAdapter
|
import ru.dbotthepony.kstarbound.math.Vector2fTypeAdapter
|
||||||
@ -80,12 +87,13 @@ import java.io.*
|
|||||||
import java.lang.ref.Cleaner
|
import java.lang.ref.Cleaner
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.Collections
|
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import java.util.concurrent.ExecutorService
|
import java.util.concurrent.ExecutorService
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
|
import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory
|
||||||
|
import java.util.concurrent.ForkJoinWorkerThread
|
||||||
import java.util.concurrent.Future
|
import java.util.concurrent.Future
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import java.util.concurrent.ThreadFactory
|
import java.util.concurrent.ThreadFactory
|
||||||
@ -96,6 +104,8 @@ import java.util.concurrent.locks.LockSupport
|
|||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import kotlin.NoSuchElementException
|
import kotlin.NoSuchElementException
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLocator {
|
object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLocator {
|
||||||
const val ENGINE_VERSION = "0.0.1"
|
const val ENGINE_VERSION = "0.0.1"
|
||||||
@ -136,25 +146,44 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val ioPoolCounter = AtomicInteger()
|
private fun makeExecutor(parallelism: Int, threadNameString: String, threadPriority: Int): ForkJoinPool {
|
||||||
|
val counter = AtomicInteger()
|
||||||
|
|
||||||
@JvmField
|
val factory = ForkJoinWorkerThreadFactory {
|
||||||
val IO_EXECUTOR: ExecutorService = ThreadPoolExecutor(0, 64, 30L, TimeUnit.SECONDS, LinkedBlockingQueue(), ThreadFactory {
|
object : ForkJoinWorkerThread(it) {
|
||||||
val thread = Thread(it, "IO Worker ${ioPoolCounter.getAndIncrement()}")
|
init {
|
||||||
thread.isDaemon = true
|
name = threadNameString.format(counter.getAndIncrement())
|
||||||
thread.priority = Thread.MIN_PRIORITY
|
priority = threadPriority
|
||||||
|
}
|
||||||
thread.uncaughtExceptionHandler = Thread.UncaughtExceptionHandler { t, e ->
|
}
|
||||||
LOGGER.error("I/O thread died due to uncaught exception", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return@ThreadFactory thread
|
val handler = UncaughtExceptionHandler { t, e ->
|
||||||
})
|
LOGGER.error("Worker thread died due to unhandled exception", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ForkJoinPool(
|
||||||
|
parallelism, factory, handler, false,
|
||||||
|
0, parallelism + 256, 1, null, 30L, TimeUnit.SECONDS
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val EXECUTOR: ForkJoinPool = ForkJoinPool.commonPool()
|
val IO_EXECUTOR: ExecutorService = makeExecutor(8, "Disk IO %d", MIN_PRIORITY)
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val COROUTINE_EXECUTOR = ExecutorWithScheduler(EXECUTOR, this).asCoroutineDispatcher()
|
val IO_COROUTINES = ExecutorWithScheduler(IO_EXECUTOR, this).asCoroutineDispatcher()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val EXECUTOR: ForkJoinPool = makeExecutor(Runtime.getRuntime().availableProcessors(), "Worker %d", NORM_PRIORITY)
|
||||||
|
@JvmField
|
||||||
|
val COROUTINES = ExecutorWithScheduler(EXECUTOR, this).asCoroutineDispatcher()
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val GLOBAL_SCOPE = CoroutineScope(COROUTINES + SupervisorJob())
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val IO_GLOBAL_SCOPE = CoroutineScope(IO_COROUTINES + SupervisorJob())
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val CLEANER: Cleaner = Cleaner.create {
|
val CLEANER: Cleaner = Cleaner.create {
|
||||||
@ -377,6 +406,8 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
registerTypeAdapterFactory(VisitableWorldParametersType.ADAPTER)
|
||||||
registerTypeAdapter(WorldLayout.Companion)
|
registerTypeAdapter(WorldLayout.Companion)
|
||||||
|
|
||||||
|
registerTypeAdapterFactory(CompletableFutureAdapter)
|
||||||
|
|
||||||
Registries.registerAdapters(this)
|
Registries.registerAdapters(this)
|
||||||
|
|
||||||
registerTypeAdapter(LongRangeAdapter)
|
registerTypeAdapter(LongRangeAdapter)
|
||||||
@ -399,14 +430,33 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
var loaded = 0
|
var loaded = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private suspend fun loadJsonAsset0(it: String): KOptional<JsonElement> {
|
||||||
|
val file = locate(it)
|
||||||
|
|
||||||
|
if (!file.isFile)
|
||||||
|
return KOptional()
|
||||||
|
|
||||||
|
val findPatches = locateAll("$it.patch")
|
||||||
|
return KOptional(JsonPatch.applyAsync(ELEMENTS_ADAPTER.read(file.asyncJsonReader().await()), findPatches))
|
||||||
|
}
|
||||||
|
|
||||||
private val jsonAssetsCache = Caffeine.newBuilder()
|
private val jsonAssetsCache = Caffeine.newBuilder()
|
||||||
.maximumSize(4096L)
|
.maximumSize(4096L)
|
||||||
.expireAfterAccess(Duration.ofMinutes(5L))
|
.expireAfterAccess(Duration.ofMinutes(5L))
|
||||||
.scheduler(this)
|
.scheduler(this)
|
||||||
.executor(EXECUTOR)
|
.executor(EXECUTOR)
|
||||||
.build<String, KOptional<JsonElement>>()
|
.buildAsync<String, KOptional<JsonElement>>(AsyncCacheLoader { key, executor ->
|
||||||
|
IO_GLOBAL_SCOPE.async {
|
||||||
|
try {
|
||||||
|
loadJsonAsset0(key)
|
||||||
|
} catch (err: Throwable) {
|
||||||
|
LOGGER.error("Exception loading JSON asset at $key", err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}.asCompletableFuture()
|
||||||
|
})
|
||||||
|
|
||||||
fun loadJsonAsset(path: String): JsonElement? {
|
fun loadJsonAsset(path: String): CompletableFuture<JsonElement?> {
|
||||||
val filename: String
|
val filename: String
|
||||||
val jsonPath: String?
|
val jsonPath: String?
|
||||||
|
|
||||||
@ -418,27 +468,27 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
jsonPath = null
|
jsonPath = null
|
||||||
}
|
}
|
||||||
|
|
||||||
val json = jsonAssetsCache.get(filename) {
|
return jsonAssetsCache.get(filename).thenApply { json ->
|
||||||
val file = locate(it)
|
if (jsonPath == null || json.isEmpty)
|
||||||
|
json.orNull()
|
||||||
if (!file.isFile)
|
else
|
||||||
return@get KOptional()
|
JsonPath.query(jsonPath).get(json.value)
|
||||||
|
}
|
||||||
val findPatches = locateAll("$filename.patch")
|
|
||||||
KOptional(JsonPatch.apply(ELEMENTS_ADAPTER.read(file.jsonReader()), findPatches))
|
|
||||||
}.orNull() ?: return null
|
|
||||||
|
|
||||||
if (jsonPath == null)
|
|
||||||
return json
|
|
||||||
|
|
||||||
return JsonPath.query(jsonPath).get(json)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadJsonAsset(path: JsonElement, relative: String): JsonElement {
|
fun loadJsonAsset(path: JsonElement, relative: String): CompletableFuture<JsonElement> {
|
||||||
if (path is JsonPrimitive) {
|
if (path is JsonPrimitive) {
|
||||||
return loadJsonAsset(AssetPathStack.relativeTo(relative, path.asString)) ?: JsonNull.INSTANCE
|
return loadJsonAsset(AssetPathStack.relativeTo(relative, path.asString)).thenApply { it ?: JsonNull.INSTANCE }
|
||||||
} else {
|
} else {
|
||||||
return path
|
return CompletableFuture.completedFuture(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadJsonAsset(path: JsonElement): CompletableFuture<JsonElement> {
|
||||||
|
if (path is JsonPrimitive) {
|
||||||
|
return loadJsonAsset(path.asString).thenApply { it ?: JsonNull.INSTANCE }
|
||||||
|
} else {
|
||||||
|
return CompletableFuture.completedFuture(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -650,7 +700,7 @@ object Starbound : BlockableEventLoop("Universe Thread"), Scheduler, ISBFileLoca
|
|||||||
throw FileNotFoundException("/hobo.ttf is not a file!")
|
throw FileNotFoundException("/hobo.ttf is not a file!")
|
||||||
else {
|
else {
|
||||||
val tempPath = File(System.getProperty("java.io.tmpdir"), "sb-hobo.ttf")
|
val tempPath = File(System.getProperty("java.io.tmpdir"), "sb-hobo.ttf")
|
||||||
tempPath.writeBytes(file.read().array())
|
tempPath.writeBytes(file.read())
|
||||||
Starbound.fontPath = tempPath
|
Starbound.fontPath = tempPath
|
||||||
return@supplyAsync tempPath
|
return@supplyAsync tempPath
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package ru.dbotthepony.kstarbound
|
package ru.dbotthepony.kstarbound
|
||||||
|
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
|
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
|
||||||
import ru.dbotthepony.kstarbound.io.StarboundPak
|
import ru.dbotthepony.kstarbound.io.StarboundPak
|
||||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||||
|
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
@ -10,42 +12,12 @@ import java.io.InputStream
|
|||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
fun interface ISBFileLocator {
|
fun interface ISBFileLocator {
|
||||||
fun locate(path: String): IStarboundFile
|
fun locate(path: String): IStarboundFile
|
||||||
fun exists(path: String): Boolean = locate(path).exists
|
fun exists(path: String): Boolean = locate(path).exists
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if file is a directory
|
|
||||||
* @throws FileNotFoundException if file does not exist
|
|
||||||
*/
|
|
||||||
fun read(path: String) = locate(path).read()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if file is a directory
|
|
||||||
* @throws FileNotFoundException if file does not exist
|
|
||||||
*/
|
|
||||||
fun readDirect(path: String) = locate(path).readDirect()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if file is a directory
|
|
||||||
* @throws FileNotFoundException if file does not exist
|
|
||||||
*/
|
|
||||||
fun open(path: String) = locate(path).open()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if file is a directory
|
|
||||||
* @throws FileNotFoundException if file does not exist
|
|
||||||
*/
|
|
||||||
fun reader(path: String) = locate(path).reader()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws IllegalStateException if file is a directory
|
|
||||||
* @throws FileNotFoundException if file does not exist
|
|
||||||
*/
|
|
||||||
@Deprecated("This does not reflect json patches")
|
|
||||||
fun jsonReader(path: String) = locate(path).jsonReader()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,11 +148,34 @@ interface IStarboundFile : ISBFileLocator {
|
|||||||
* @throws IllegalStateException if file is a directory
|
* @throws IllegalStateException if file is a directory
|
||||||
* @throws FileNotFoundException if file does not exist
|
* @throws FileNotFoundException if file does not exist
|
||||||
*/
|
*/
|
||||||
fun read(): ByteBuffer {
|
fun asyncRead(): CompletableFuture<ByteArray>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun asyncReader(): CompletableFuture<Reader> {
|
||||||
|
return asyncRead().thenApply { InputStreamReader(FastByteArrayInputStream(it)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
@Deprecated("Careful! This does not reflect json patches")
|
||||||
|
fun asyncJsonReader(): CompletableFuture<JsonReader> {
|
||||||
|
return asyncReader().thenApply { JsonReader(it).also { it.isLenient = true } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws IllegalStateException if file is a directory
|
||||||
|
* @throws FileNotFoundException if file does not exist
|
||||||
|
*/
|
||||||
|
fun read(): ByteArray {
|
||||||
val stream = open()
|
val stream = open()
|
||||||
val read = stream.readAllBytes()
|
val read = stream.readAllBytes()
|
||||||
stream.close()
|
stream.close()
|
||||||
return ByteBuffer.wrap(read)
|
return read
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -200,16 +195,9 @@ interface IStarboundFile : ISBFileLocator {
|
|||||||
*/
|
*/
|
||||||
fun readDirect(): ByteBuffer {
|
fun readDirect(): ByteBuffer {
|
||||||
val read = read()
|
val read = read()
|
||||||
val buf = ByteBuffer.allocateDirect(read.capacity())
|
val buf = ByteBuffer.allocateDirect(read.size)
|
||||||
|
buf.put(read)
|
||||||
read.position(0)
|
|
||||||
|
|
||||||
for (i in 0 until read.capacity()) {
|
|
||||||
buf.put(read[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.position(0)
|
buf.position(0)
|
||||||
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,6 +218,10 @@ interface IStarboundFile : ISBFileLocator {
|
|||||||
override val name: String
|
override val name: String
|
||||||
get() = ""
|
get() = ""
|
||||||
|
|
||||||
|
override fun asyncRead(): CompletableFuture<ByteArray> {
|
||||||
|
throw FileNotFoundException()
|
||||||
|
}
|
||||||
|
|
||||||
override fun open(): InputStream {
|
override fun open(): InputStream {
|
||||||
throw FileNotFoundException()
|
throw FileNotFoundException()
|
||||||
}
|
}
|
||||||
@ -251,6 +243,10 @@ class NonExistingFile(
|
|||||||
override val exists: Boolean
|
override val exists: Boolean
|
||||||
get() = false
|
get() = false
|
||||||
|
|
||||||
|
override fun asyncRead(): CompletableFuture<ByteArray> {
|
||||||
|
throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
override fun open(): InputStream {
|
override fun open(): InputStream {
|
||||||
throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist")
|
throw FileNotFoundException("File ${fullPath ?: computeFullPath()} does not exist")
|
||||||
}
|
}
|
||||||
@ -297,6 +293,10 @@ class PhysicalFile(val real: File, override val parent: PhysicalFile? = null) :
|
|||||||
return BufferedInputStream(real.inputStream())
|
return BufferedInputStream(real.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun asyncRead(): CompletableFuture<ByteArray> {
|
||||||
|
return Starbound.IO_EXECUTOR.supplyAsync { real.readBytes() }
|
||||||
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is IStarboundFile && computeFullPath() == other.computeFullPath()
|
return other is IStarboundFile && computeFullPath() == other.computeFullPath()
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package ru.dbotthepony.kstarbound.client.render
|
package ru.dbotthepony.kstarbound.client.render
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap
|
import com.google.common.collect.ImmutableMap
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.annotations.JsonAdapter
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
|
import ru.dbotthepony.kstarbound.world.api.AbstractTileState
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||||
@ -44,6 +49,7 @@ enum class RenderLayer {
|
|||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonAdapter(Adapter::class)
|
||||||
data class Point(val base: RenderLayer, val offset: Long = 0L, val index: Long = 0L, val hueShift: Float = 0f, val colorVariant: TileColor = TileColor.DEFAULT) : Comparable<Point> {
|
data class Point(val base: RenderLayer, val offset: Long = 0L, val index: Long = 0L, val hueShift: Float = 0f, val colorVariant: TileColor = TileColor.DEFAULT) : Comparable<Point> {
|
||||||
override fun compareTo(other: Point): Int {
|
override fun compareTo(other: Point): Int {
|
||||||
if (this === other) return 0
|
if (this === other) return 0
|
||||||
@ -56,6 +62,16 @@ enum class RenderLayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Adapter(gson: Gson) : TypeAdapter<Point>() {
|
||||||
|
override fun write(out: JsonWriter, value: Point) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): Point {
|
||||||
|
return parse(`in`.nextString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L, index: Long = 0L, hueShift: Float = 0f, colorVariant: TileColor = TileColor.DEFAULT): Point {
|
fun tileLayer(isBackground: Boolean, isModifier: Boolean, offset: Long = 0L, index: Long = 0L, hueShift: Float = 0f, colorVariant: TileColor = TileColor.DEFAULT): Point {
|
||||||
if (isBackground && isModifier) {
|
if (isBackground && isModifier) {
|
||||||
|
@ -235,7 +235,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
|
|||||||
|
|
||||||
// если у нас нет renderTemplate
|
// если у нас нет renderTemplate
|
||||||
// то мы просто не можем его отрисовать
|
// то мы просто не можем его отрисовать
|
||||||
val template = def.renderTemplate.value ?: return
|
val template = def.renderTemplate.value.get() ?: return
|
||||||
|
|
||||||
val vertexBuilder = meshBuilder
|
val vertexBuilder = meshBuilder
|
||||||
.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, self), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!)
|
.getBuilder(RenderLayer.tileLayer(isBackground, isModifier, self), if (isBackground) bakedBackgroundProgramState!! else bakedProgramState!!)
|
||||||
|
@ -18,41 +18,47 @@ import ru.dbotthepony.kstarbound.util.AssetPathStack
|
|||||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||||
import java.lang.reflect.ParameterizedType
|
import java.lang.reflect.ParameterizedType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.function.Consumer
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
class AssetReference<V> {
|
class AssetReference<V> {
|
||||||
constructor(value: V?) {
|
constructor(value: V?) {
|
||||||
lazy = lazy { value }
|
this.value = CompletableFuture.completedFuture(value)
|
||||||
path = null
|
path = null
|
||||||
fullPath = null
|
fullPath = null
|
||||||
json = null
|
this.json = CompletableFuture.completedFuture(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(value: () -> V?) {
|
constructor(value: CompletableFuture<V?>) {
|
||||||
lazy = lazy { value() }
|
this.value = value
|
||||||
path = null
|
path = null
|
||||||
fullPath = null
|
fullPath = null
|
||||||
json = null
|
this.json = CompletableFuture.completedFuture(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(path: String?, fullPath: String?, value: V?, json: JsonElement?) {
|
constructor(path: String?, fullPath: String?, value: V?, json: JsonElement?) {
|
||||||
this.path = path
|
this.path = path
|
||||||
this.fullPath = fullPath
|
this.fullPath = fullPath
|
||||||
this.lazy = lazy { value }
|
this.value = CompletableFuture.completedFuture(value)
|
||||||
|
this.json = CompletableFuture.completedFuture(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(path: String?, fullPath: String?, value: CompletableFuture<V?>, json: CompletableFuture<JsonElement?>) {
|
||||||
|
this.path = path
|
||||||
|
this.fullPath = fullPath
|
||||||
|
this.value = value
|
||||||
this.json = json
|
this.json = json
|
||||||
}
|
}
|
||||||
|
|
||||||
val path: String?
|
val path: String?
|
||||||
val fullPath: String?
|
val fullPath: String?
|
||||||
val json: JsonElement?
|
val json: CompletableFuture<JsonElement?>
|
||||||
|
val value: CompletableFuture<V?>
|
||||||
val value: V?
|
|
||||||
get() = lazy.value
|
|
||||||
|
|
||||||
private val lazy: Lazy<V?>
|
|
||||||
|
|
||||||
companion object : TypeAdapterFactory {
|
companion object : TypeAdapterFactory {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
val EMPTY = AssetReference(null, null, null, null)
|
val EMPTY = AssetReference(null, null, null, null)
|
||||||
|
|
||||||
fun <V> empty() = EMPTY as AssetReference<V>
|
fun <V> empty() = EMPTY as AssetReference<V>
|
||||||
@ -62,7 +68,7 @@ class AssetReference<V> {
|
|||||||
val param = type.type as? ParameterizedType ?: return null
|
val param = type.type as? ParameterizedType ?: return null
|
||||||
|
|
||||||
return object : TypeAdapter<AssetReference<T>>() {
|
return object : TypeAdapter<AssetReference<T>>() {
|
||||||
private val cache = Collections.synchronizedMap(HashMap<String, Pair<T, JsonElement>>())
|
private val cache = ConcurrentHashMap<String, Pair<CompletableFuture<T?>, CompletableFuture<JsonElement?>>>()
|
||||||
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<T>
|
private val adapter = gson.getAdapter(TypeToken.get(param.actualTypeArguments[0])) as TypeAdapter<T>
|
||||||
private val strings = gson.getAdapter(String::class.java)
|
private val strings = gson.getAdapter(String::class.java)
|
||||||
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
private val missing = Collections.synchronizedSet(ObjectOpenHashSet<String>())
|
||||||
@ -81,34 +87,31 @@ class AssetReference<V> {
|
|||||||
} else if (`in`.peek() == JsonToken.STRING) {
|
} else if (`in`.peek() == JsonToken.STRING) {
|
||||||
val path = strings.read(`in`)!!
|
val path = strings.read(`in`)!!
|
||||||
val fullPath = AssetPathStack.remap(path)
|
val fullPath = AssetPathStack.remap(path)
|
||||||
val get = cache[fullPath]
|
|
||||||
|
|
||||||
if (get != null)
|
val get = cache.computeIfAbsent(fullPath) {
|
||||||
return AssetReference(path.sbIntern(), fullPath.sbIntern(), get.first, get.second)
|
val json = Starbound.loadJsonAsset(fullPath)
|
||||||
|
|
||||||
if (fullPath in missing)
|
json.thenAccept {
|
||||||
return null
|
if (it == null && missing.add(fullPath)) {
|
||||||
|
logger.error("JSON asset does not exist: $fullPath")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val json = Starbound.loadJsonAsset(fullPath)
|
val value = json.thenApplyAsync({ j ->
|
||||||
|
AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
|
||||||
|
adapter.fromJsonTree(j)
|
||||||
|
}
|
||||||
|
}, Starbound.EXECUTOR)
|
||||||
|
|
||||||
if (json == null) {
|
value.exceptionally {
|
||||||
if (missing.add(fullPath))
|
LOGGER.error("Exception loading $fullPath", it)
|
||||||
logger.error("JSON asset does not exist: $fullPath")
|
null
|
||||||
|
}
|
||||||
|
|
||||||
return AssetReference(path.sbIntern(), fullPath.sbIntern(), null, null)
|
value to json
|
||||||
}
|
}
|
||||||
|
|
||||||
val value = AssetPathStack(fullPath.substringBefore(':').substringBeforeLast('/')) {
|
return AssetReference(path.sbIntern(), fullPath.sbIntern(), get.first, get.second)
|
||||||
adapter.fromJsonTree(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value == null) {
|
|
||||||
missing.add(fullPath)
|
|
||||||
return AssetReference(path.sbIntern(), fullPath.sbIntern(), null, json)
|
|
||||||
}
|
|
||||||
|
|
||||||
cache[fullPath] = value to json
|
|
||||||
return AssetReference(path.sbIntern(), fullPath.sbIntern(), value, json)
|
|
||||||
} else {
|
} else {
|
||||||
val json = Starbound.ELEMENTS_ADAPTER.read(`in`)
|
val json = Starbound.ELEMENTS_ADAPTER.read(`in`)
|
||||||
val value = adapter.read(JsonTreeReader(json)) ?: return null
|
val value = adapter.read(JsonTreeReader(json)) ?: return null
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
|
||||||
|
|
||||||
|
enum class ClientEntityMode(override val jsonName: String) : IStringSerializable {
|
||||||
|
CLIENT_SLAVE_ONLY("ClientSlaveOnly"),
|
||||||
|
CLIENT_MASTER_ALLOWED("ClientMasterAllowed"),
|
||||||
|
CLIENT_PRESENCE_MASTER("ClientPresenceMaster");
|
||||||
|
}
|
@ -62,7 +62,8 @@ sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: St
|
|||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
val path = `in`.nextString()
|
val path = `in`.nextString()
|
||||||
val full = AssetPathStack.remapSafe(path)
|
val full = AssetPathStack.remapSafe(path)
|
||||||
val get = Starbound.loadJsonAsset(full) ?: return factory(path, full, null)
|
// TODO: this blocks thread, need coroutine aware version
|
||||||
|
val get = Starbound.loadJsonAsset(full).get() ?: return factory(path, full, null)
|
||||||
return factory(path, full, adapter.fromJsonTree(get))
|
return factory(path, full, adapter.fromJsonTree(get))
|
||||||
} else {
|
} else {
|
||||||
return factory(null, null, adapter.read(`in`))
|
return factory(null, null, adapter.read(`in`))
|
||||||
@ -89,7 +90,8 @@ sealed class JsonReference<E : JsonElement?>(val path: String?, val fullPath: St
|
|||||||
if (`in`.peek() == JsonToken.STRING) {
|
if (`in`.peek() == JsonToken.STRING) {
|
||||||
val path = `in`.nextString()
|
val path = `in`.nextString()
|
||||||
val full = AssetPathStack.remapSafe(path)
|
val full = AssetPathStack.remapSafe(path)
|
||||||
val get = Starbound.loadJsonAsset(full) ?: throw JsonSyntaxException("Json asset at $full does not exist")
|
// TODO: this blocks thread, need coroutine aware version
|
||||||
|
val get = Starbound.loadJsonAsset(full).get() ?: throw JsonSyntaxException("Json asset at $full does not exist")
|
||||||
return factory(path, full, adapter.fromJsonTree(get) ?: throw JsonSyntaxException("Json asset at $full is literal null, which is not allowed"))
|
return factory(path, full, adapter.fromJsonTree(get) ?: throw JsonSyntaxException("Json asset at $full is literal null, which is not allowed"))
|
||||||
} else {
|
} else {
|
||||||
return factory(null, null, adapter.read(`in`))
|
return factory(null, null, adapter.read(`in`))
|
||||||
|
@ -0,0 +1,77 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList
|
||||||
|
import com.google.common.collect.ImmutableSet
|
||||||
|
import com.google.gson.JsonArray
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
|
import ru.dbotthepony.kommons.util.Either
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.RenderLayer
|
||||||
|
import ru.dbotthepony.kstarbound.defs.actor.StatModifier
|
||||||
|
import ru.dbotthepony.kstarbound.defs.image.SpriteReference
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kstarbound.world.PIXELS_IN_STARBOUND_UNIT
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class ProjectileDefinition(
|
||||||
|
val projectileName: String,
|
||||||
|
val description: String = "",
|
||||||
|
val boundBox: AABB = AABB.withSide(Vector2d.ZERO, 5.0, 5.0),
|
||||||
|
val speed: Double = 50.0,
|
||||||
|
val acceleration: Double = 0.0,
|
||||||
|
val power: Double = 1.0,
|
||||||
|
@Deprecated("", replaceWith = ReplaceWith("this.actualDamagePoly"))
|
||||||
|
val damagePoly: Poly? = null,
|
||||||
|
val piercing: Boolean = false,
|
||||||
|
val falldown: Boolean = false,
|
||||||
|
val bounces: Int = 0,
|
||||||
|
val actionOnCollide: JsonArray = JsonArray(),
|
||||||
|
val actionOnReap: JsonArray = JsonArray(),
|
||||||
|
val actionOnHit: JsonArray = JsonArray(),
|
||||||
|
val actionOnTimeout: JsonArray = JsonArray(),
|
||||||
|
val periodicActions: JsonArray = JsonArray(),
|
||||||
|
val image: SpriteReference,
|
||||||
|
val frameNumber: Int = 1,
|
||||||
|
val animationCycle: Double = 1.0,
|
||||||
|
val animationLoops: Boolean = true,
|
||||||
|
val windupFrames: Int = 0,
|
||||||
|
val intangibleWindup: Boolean = false,
|
||||||
|
val winddownFrames: Int = 0,
|
||||||
|
val intangibleWinddown: Boolean = false,
|
||||||
|
val flippable: Boolean = false,
|
||||||
|
val orientationLocked: Boolean = false,
|
||||||
|
val fullbright: Boolean = false,
|
||||||
|
val renderLayer: RenderLayer.Point = RenderLayer.Point(RenderLayer.Projectile),
|
||||||
|
val lightColor: RGBAColor = RGBAColor.TRANSPARENT_BLACK,
|
||||||
|
val lightPosition: Vector2d = Vector2d.ZERO,
|
||||||
|
val pointLight: Boolean = false,
|
||||||
|
val persistentAudio: AssetPath = AssetPath(""),
|
||||||
|
|
||||||
|
// Initialize timeToLive after animationCycle so we can have the default be
|
||||||
|
// based on animationCycle
|
||||||
|
val timeToLive: Double = if (animationLoops) animationCycle else 5.0,
|
||||||
|
val damageKindImage: AssetPath = AssetPath(""),
|
||||||
|
val damageKind: String = "",
|
||||||
|
val damageType: String = "",
|
||||||
|
val damageRepeatGroup: String? = null,
|
||||||
|
val damageRepeatTimeout: Double? = null,
|
||||||
|
val statusEffects: ImmutableList<EphemeralStatusEffect> = ImmutableList.of(),
|
||||||
|
val emitters: ImmutableSet<String> = ImmutableSet.of(),
|
||||||
|
val hydrophobic: Boolean = false,
|
||||||
|
val rayCheckToSource: Boolean = false,
|
||||||
|
val knockback: Double = 0.0,
|
||||||
|
val knockbackDirectional: Boolean = false,
|
||||||
|
val onlyHitTerrain: Boolean = false,
|
||||||
|
val clientEntityMode: ClientEntityMode = ClientEntityMode.CLIENT_MASTER_ALLOWED,
|
||||||
|
val masterOnly: Boolean = false,
|
||||||
|
val scripts: ImmutableList<AssetPath> = ImmutableList.of(),
|
||||||
|
val physicsForces: JsonObject = JsonObject(),
|
||||||
|
val physicsCollisions: JsonObject = JsonObject(),
|
||||||
|
val persistentStatusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
|
val statusEffectArea: Poly = Poly.EMPTY,
|
||||||
|
) {
|
||||||
|
val actualDamagePoly = if (damagePoly != null) damagePoly * (1.0 / PIXELS_IN_STARBOUND_UNIT) else null
|
||||||
|
}
|
@ -218,7 +218,7 @@ data class DungeonDefinition(
|
|||||||
|
|
||||||
val anchor = validAnchors.random(world.random)
|
val anchor = validAnchors.random(world.random)
|
||||||
|
|
||||||
return CoroutineScope(Starbound.COROUTINE_EXECUTOR)
|
return CoroutineScope(Starbound.COROUTINES)
|
||||||
.async {
|
.async {
|
||||||
if (forcePlacement || anchor.canPlace(x, y - anchor.placementLevelConstraint, dungeonWorld)) {
|
if (forcePlacement || anchor.canPlace(x, y - anchor.placementLevelConstraint, dungeonWorld)) {
|
||||||
generate0(anchor, dungeonWorld, x, y - anchor.placementLevelConstraint, forcePlacement, dungeonID)
|
generate0(anchor, dungeonWorld, x, y - anchor.placementLevelConstraint, forcePlacement, dungeonID)
|
||||||
@ -237,7 +237,7 @@ data class DungeonDefinition(
|
|||||||
require(anchor in anchorParts) { "$anchor does not belong to $name" }
|
require(anchor in anchorParts) { "$anchor does not belong to $name" }
|
||||||
val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends)
|
val dungeonWorld = DungeonWorld(world, random, if (markSurfaceAndTerrain) y else null, terrainSurfaceSpaceExtends)
|
||||||
|
|
||||||
return CoroutineScope(Starbound.COROUTINE_EXECUTOR)
|
return CoroutineScope(Starbound.COROUTINES)
|
||||||
.async {
|
.async {
|
||||||
generate0(anchor, dungeonWorld, x, y, forcePlacement, dungeonID)
|
generate0(anchor, dungeonWorld, x, y, forcePlacement, dungeonID)
|
||||||
|
|
||||||
|
@ -56,8 +56,9 @@ class TiledTileSet private constructor(
|
|||||||
return DungeonTile.INTERNER.intern(DungeonTile(ImmutableList.copyOf(brushes), ImmutableList.copyOf(rules), 0, connector))
|
return DungeonTile.INTERNER.intern(DungeonTile(ImmutableList.copyOf(brushes), ImmutableList.copyOf(rules), 0, connector))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: coroutine aware version
|
||||||
private fun load0(location: String): Either<TiledTileSet, Throwable> {
|
private fun load0(location: String): Either<TiledTileSet, Throwable> {
|
||||||
val locate = Starbound.loadJsonAsset(location)
|
val locate = Starbound.loadJsonAsset(location).get()
|
||||||
?: return Either.right(NoSuchElementException("Tileset at $location does not exist"))
|
?: return Either.right(NoSuchElementException("Tileset at $location does not exist"))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.image
|
package ru.dbotthepony.kstarbound.defs.image
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.AsyncCacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
import com.github.benmanes.caffeine.cache.AsyncLoadingCache
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader
|
import com.github.benmanes.caffeine.cache.CacheLoader
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine
|
import com.github.benmanes.caffeine.cache.Caffeine
|
||||||
@ -48,7 +49,9 @@ import java.util.Collections
|
|||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.function.BiFunction
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
class Image private constructor(
|
class Image private constructor(
|
||||||
val source: IStarboundFile,
|
val source: IStarboundFile,
|
||||||
@ -169,14 +172,24 @@ class Image private constructor(
|
|||||||
return whole.isTransparent(x, y, flip)
|
return whole.isTransparent(x, y, flip)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
@Deprecated("Blocks thread")
|
||||||
|
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): ImmutableSet<Vector2i> {
|
||||||
return whole.worldSpaces(pixelOffset, spaceScan, flip)
|
return whole.worldSpaces(pixelOffset, spaceScan, flip)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun worldSpaces(pixelOffset: Vector2d, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
@Deprecated("Blocks thread")
|
||||||
|
fun worldSpaces(pixelOffset: Vector2d, spaceScan: Double, flip: Boolean): ImmutableSet<Vector2i> {
|
||||||
return whole.worldSpaces(Vector2i(pixelOffset.x.toInt(), pixelOffset.y.toInt()), spaceScan, flip)
|
return whole.worldSpaces(Vector2i(pixelOffset.x.toInt(), pixelOffset.y.toInt()), spaceScan, flip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun worldSpacesAsync(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): CompletableFuture<ImmutableSet<Vector2i>> {
|
||||||
|
return whole.worldSpacesAsync(pixelOffset, spaceScan, flip)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun worldSpacesAsync(pixelOffset: Vector2d, spaceScan: Double, flip: Boolean): CompletableFuture<ImmutableSet<Vector2i>> {
|
||||||
|
return whole.worldSpacesAsync(Vector2i(pixelOffset.x.toInt(), pixelOffset.y.toInt()), spaceScan, flip)
|
||||||
|
}
|
||||||
|
|
||||||
private data class DataSprite(val name: String, val coordinates: Vector4i)
|
private data class DataSprite(val name: String, val coordinates: Vector4i)
|
||||||
private data class SpaceScanKey(val sprite: Sprite, val pixelOffset: Vector2i, val spaceScan: Double, val flip: Boolean)
|
private data class SpaceScanKey(val sprite: Sprite, val pixelOffset: Vector2i, val spaceScan: Double, val flip: Boolean)
|
||||||
|
|
||||||
@ -256,56 +269,60 @@ class Image private constructor(
|
|||||||
nonEmptyRegion + Vector4i(x, y, x, y)
|
nonEmptyRegion + Vector4i(x, y, x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
@Deprecated("Blocks thread")
|
||||||
return spaceScanCache.get(SpaceScanKey(this, pixelOffset, spaceScan, flip)) {
|
fun worldSpaces(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): ImmutableSet<Vector2i> {
|
||||||
ImmutableSet.copyOf(worldSpaces0(pixelOffset, spaceScan, flip))
|
return worldSpacesAsync(pixelOffset, spaceScan, flip).get()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun worldSpaces0(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): Set<Vector2i> {
|
fun worldSpacesAsync(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): CompletableFuture<ImmutableSet<Vector2i>> {
|
||||||
val minX = pixelOffset.x / PIXELS_IN_STARBOUND_UNITi
|
return spaceScanCache.get(SpaceScanKey(this, pixelOffset, spaceScan, flip), BiFunction { _, _ ->
|
||||||
val minY = pixelOffset.y / PIXELS_IN_STARBOUND_UNITi
|
worldSpaces0(pixelOffset, spaceScan, flip)
|
||||||
val maxX = (width + pixelOffset.x + PIXELS_IN_STARBOUND_UNITi - 1) / PIXELS_IN_STARBOUND_UNITi
|
})
|
||||||
val maxY = (height + pixelOffset.y + PIXELS_IN_STARBOUND_UNITi - 1) / PIXELS_IN_STARBOUND_UNITi
|
}
|
||||||
|
|
||||||
val result = ObjectArraySet<Vector2i>()
|
private fun worldSpaces0(pixelOffset: Vector2i, spaceScan: Double, flip: Boolean): CompletableFuture<ImmutableSet<Vector2i>> {
|
||||||
|
return dataFuture.thenApplyAsync({
|
||||||
|
val minX = pixelOffset.x / PIXELS_IN_STARBOUND_UNITi
|
||||||
|
val minY = pixelOffset.y / PIXELS_IN_STARBOUND_UNITi
|
||||||
|
val maxX = (width + pixelOffset.x + PIXELS_IN_STARBOUND_UNITi - 1) / PIXELS_IN_STARBOUND_UNITi
|
||||||
|
val maxY = (height + pixelOffset.y + PIXELS_IN_STARBOUND_UNITi - 1) / PIXELS_IN_STARBOUND_UNITi
|
||||||
|
|
||||||
// this is weird, but that's how original game handles this
|
val result = ImmutableSet.Builder<Vector2i>()
|
||||||
// also we don't cache this info since that's a waste of precious ram
|
|
||||||
|
|
||||||
val data = data
|
// this is weird, but that's how original game handles this
|
||||||
|
// also we don't cache this info since that's a waste of precious ram
|
||||||
|
|
||||||
for (yspace in minY until maxY) {
|
for (yspace in minY until maxY) {
|
||||||
for (xspace in minX until maxX) {
|
for (xspace in minX until maxX) {
|
||||||
var fillRatio = 0.0
|
var fillRatio = 0.0
|
||||||
|
|
||||||
for (y in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
for (y in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
||||||
val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y)
|
val ypixel = (yspace * PIXELS_IN_STARBOUND_UNITi + y - pixelOffset.y)
|
||||||
|
|
||||||
if (ypixel !in 0 until height)
|
if (ypixel !in 0 until height)
|
||||||
continue
|
|
||||||
|
|
||||||
for (x in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
|
||||||
val xpixel = (xspace * PIXELS_IN_STARBOUND_UNITi + x - pixelOffset.x)
|
|
||||||
|
|
||||||
if (xpixel !in 0 until width)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if (!isTransparent(xpixel, height - ypixel - 1, flip, data)) {
|
for (x in 0 until PIXELS_IN_STARBOUND_UNITi) {
|
||||||
fillRatio += 1.0 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT)
|
val xpixel = (xspace * PIXELS_IN_STARBOUND_UNITi + x - pixelOffset.x)
|
||||||
|
|
||||||
|
if (xpixel !in 0 until width)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (!isTransparent(xpixel, height - ypixel - 1, flip, it)) {
|
||||||
|
fillRatio += 1.0 / (PIXELS_IN_STARBOUND_UNIT * PIXELS_IN_STARBOUND_UNIT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (fillRatio >= spaceScan) {
|
if (fillRatio >= spaceScan) {
|
||||||
result.add(Vector2i(xspace, yspace))
|
result.add(Vector2i(xspace, yspace))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
result.build()
|
||||||
|
}, Starbound.EXECUTOR)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : TypeAdapter<Image>() {
|
companion object : TypeAdapter<Image>() {
|
||||||
@ -350,10 +367,9 @@ class Image private constructor(
|
|||||||
|
|
||||||
private val spaceScanCache = Caffeine.newBuilder()
|
private val spaceScanCache = Caffeine.newBuilder()
|
||||||
.expireAfterAccess(Duration.ofMinutes(30))
|
.expireAfterAccess(Duration.ofMinutes(30))
|
||||||
.softValues()
|
|
||||||
.scheduler(Starbound)
|
.scheduler(Starbound)
|
||||||
.executor(Starbound.EXECUTOR)
|
.executor(Starbound.EXECUTOR)
|
||||||
.build<SpaceScanKey, ImmutableSet<Vector2i>>()
|
.buildAsync<SpaceScanKey, ImmutableSet<Vector2i>>()
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun get(path: String): Image? {
|
fun get(path: String): Image? {
|
||||||
|
@ -14,6 +14,10 @@ import com.google.gson.TypeAdapter
|
|||||||
import com.google.gson.annotations.JsonAdapter
|
import com.google.gson.annotations.JsonAdapter
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.util.Either
|
import ru.dbotthepony.kommons.util.Either
|
||||||
import ru.dbotthepony.kommons.math.RGBAColor
|
import ru.dbotthepony.kommons.math.RGBAColor
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
@ -39,6 +43,9 @@ import ru.dbotthepony.kstarbound.defs.item.ItemDescriptor
|
|||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.function.Function
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
@JsonAdapter(ObjectDefinition.Adapter::class)
|
@JsonAdapter(ObjectDefinition.Adapter::class)
|
||||||
data class ObjectDefinition(
|
data class ObjectDefinition(
|
||||||
@ -75,7 +82,7 @@ data class ObjectDefinition(
|
|||||||
val soundEffectRangeMultiplier: Double = 1.0,
|
val soundEffectRangeMultiplier: Double = 1.0,
|
||||||
val price: Long = 1L,
|
val price: Long = 1L,
|
||||||
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
val statusEffects: ImmutableList<Either<String, StatModifier>> = ImmutableList.of(),
|
||||||
val touchDamage: JsonElement,
|
val touchDamage: CompletableFuture<JsonElement>,
|
||||||
val minimumLiquidLevel: Float? = null,
|
val minimumLiquidLevel: Float? = null,
|
||||||
val maximumLiquidLevel: Float? = null,
|
val maximumLiquidLevel: Float? = null,
|
||||||
val liquidCheckInterval: Float = 0.5f,
|
val liquidCheckInterval: Float = 0.5f,
|
||||||
@ -84,29 +91,33 @@ data class ObjectDefinition(
|
|||||||
val biomePlaced: Boolean = false,
|
val biomePlaced: Boolean = false,
|
||||||
val printable: Boolean = false,
|
val printable: Boolean = false,
|
||||||
val smashOnBreak: Boolean = false,
|
val smashOnBreak: Boolean = false,
|
||||||
val damageConfig: TileDamageParameters,
|
val damageConfig: CompletableFuture<TileDamageParameters>,
|
||||||
val flickerPeriod: PeriodicFunction? = null,
|
val flickerPeriod: PeriodicFunction? = null,
|
||||||
val orientations: ImmutableList<ObjectOrientation>,
|
val orientations: ImmutableList<Supplier<ObjectOrientation>>,
|
||||||
) {
|
) {
|
||||||
fun findValidOrientation(world: World<*, *>, position: Vector2i, directionAffinity: Direction? = null, ignoreProtectedDungeons: Boolean = false): Int {
|
fun findValidOrientation(world: World<*, *>, position: Vector2i, directionAffinity: Direction? = null, ignoreProtectedDungeons: Boolean = false): Int {
|
||||||
// If we are given a direction affinity, try and find an orientation with a
|
// If we are given a direction affinity, try and find an orientation with a
|
||||||
// matching affinity *first*
|
// matching affinity *first*
|
||||||
if (directionAffinity != null) {
|
if (directionAffinity != null) {
|
||||||
for ((i, orientation) in orientations.withIndex()) {
|
for ((i, orientation) in orientations.withIndex()) {
|
||||||
if (orientation.directionAffinity == directionAffinity && orientation.placementValid(world, position, ignoreProtectedDungeons) && orientation.anchorsValid(world, position))
|
if (orientation.get().directionAffinity == directionAffinity && orientation.get().placementValid(world, position, ignoreProtectedDungeons) && orientation.get().anchorsValid(world, position))
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Then, fallback and try and find any valid affinity
|
// Then, fallback and try and find any valid affinity
|
||||||
for ((i, orientation) in orientations.withIndex()) {
|
for ((i, orientation) in orientations.withIndex()) {
|
||||||
if (orientation.placementValid(world, position) && orientation.anchorsValid(world, position))
|
if (orientation.get().placementValid(world, position) && orientation.get().anchorsValid(world, position))
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
}
|
||||||
|
|
||||||
class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() {
|
class Adapter(gson: Gson) : TypeAdapter<ObjectDefinition>() {
|
||||||
@JsonFactory(logMisses = false)
|
@JsonFactory(logMisses = false)
|
||||||
data class PlainData(
|
data class PlainData(
|
||||||
@ -153,11 +164,10 @@ data class ObjectDefinition(
|
|||||||
val biomePlaced: Boolean = false,
|
val biomePlaced: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val objectRef = gson.getAdapter(JsonReference.Object::class.java)
|
|
||||||
private val basic = gson.getAdapter(PlainData::class.java)
|
private val basic = gson.getAdapter(PlainData::class.java)
|
||||||
private val damageConfig = gson.getAdapter(TileDamageParameters::class.java)
|
private val damageConfig = gson.getAdapter(TileDamageParameters::class.java)
|
||||||
private val damageTeam = gson.getAdapter(DamageTeam::class.java)
|
private val damageTeam = gson.getAdapter(DamageTeam::class.java)
|
||||||
private val orientations = gson.getAdapter(ObjectOrientation::class.java)
|
private val orientations = ObjectOrientation.Adapter(gson)
|
||||||
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
||||||
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
||||||
|
|
||||||
@ -179,13 +189,21 @@ data class ObjectDefinition(
|
|||||||
val printable = basic.hasObjectItem && read.get("printable", basic.scannable)
|
val printable = basic.hasObjectItem && read.get("printable", basic.scannable)
|
||||||
val smashOnBreak = read.get("smashOnBreak", basic.smashable)
|
val smashOnBreak = read.get("smashOnBreak", basic.smashable)
|
||||||
|
|
||||||
val getDamageParams = objectRef.fromJsonTree(read.get("damageTable", JsonPrimitive("/objects/defaultParameters.config:damageTable")))
|
val getPath = read.get("damageTable", JsonPrimitive("/objects/defaultParameters.config:damageTable"))
|
||||||
getDamageParams?.value ?: throw JsonSyntaxException("No valid damageTable specified")
|
|
||||||
|
|
||||||
getDamageParams.value["health"] = read["health"]
|
val damageConfig = Starbound
|
||||||
getDamageParams.value["harvestLevel"] = read["harvestLevel"]
|
.loadJsonAsset(getPath)
|
||||||
|
.thenApplyAsync(Function {
|
||||||
|
val value = it.asJsonObject.deepCopy()
|
||||||
|
value["health"] = read["health"]
|
||||||
|
value["harvestLevel"] = read["harvestLevel"]
|
||||||
|
damageConfig.fromJsonTree(value)
|
||||||
|
}, Starbound.EXECUTOR)
|
||||||
|
|
||||||
val damageConfig = damageConfig.fromJsonTree(getDamageParams.value)
|
damageConfig.exceptionally {
|
||||||
|
LOGGER.error("Exception loading damage config $getPath", it)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
val flickerPeriod = if ("flickerPeriod" in read) {
|
val flickerPeriod = if ("flickerPeriod" in read) {
|
||||||
PeriodicFunction(
|
PeriodicFunction(
|
||||||
@ -199,17 +217,24 @@ data class ObjectDefinition(
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val orientations = ObjectOrientation.preprocess(read.getArray("orientations"))
|
val orientations = ImmutableList.Builder<Supplier<ObjectOrientation>>()
|
||||||
.stream()
|
|
||||||
.map { orientations.fromJsonTree(it) }
|
|
||||||
.collect(ImmutableList.toImmutableList())
|
|
||||||
|
|
||||||
if ("particleEmitter" in read) {
|
val path = AssetPathStack.last()
|
||||||
orientations.forEach { it.particleEmitters.add(emitter.fromJsonTree(read["particleEmitter"])) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("particleEmitters" in read) {
|
for (v in ObjectOrientation.preprocess(read.getArray("orientations"))) {
|
||||||
orientations.forEach { it.particleEmitters.addAll(emitters.fromJsonTree(read["particleEmitters"])) }
|
val future = Starbound.GLOBAL_SCOPE.async { AssetPathStack(path) { this@Adapter.orientations.read(v as JsonObject) } }.asCompletableFuture()
|
||||||
|
|
||||||
|
future.thenAccept {
|
||||||
|
if ("particleEmitter" in read) {
|
||||||
|
it.particleEmitters.add(emitter.fromJsonTree(read["particleEmitter"]))
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("particleEmitters" in read) {
|
||||||
|
it.particleEmitters.addAll(emitters.fromJsonTree(read["particleEmitters"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
orientations.add(future::get)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ObjectDefinition(
|
return ObjectDefinition(
|
||||||
@ -256,7 +281,7 @@ data class ObjectDefinition(
|
|||||||
smashOnBreak = smashOnBreak,
|
smashOnBreak = smashOnBreak,
|
||||||
damageConfig = damageConfig,
|
damageConfig = damageConfig,
|
||||||
flickerPeriod = flickerPeriod,
|
flickerPeriod = flickerPeriod,
|
||||||
orientations = orientations,
|
orientations = orientations.build(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import com.google.gson.annotations.JsonAdapter
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.gson.clear
|
import ru.dbotthepony.kommons.gson.clear
|
||||||
import ru.dbotthepony.kstarbound.math.AABB
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
@ -40,9 +41,9 @@ import ru.dbotthepony.kstarbound.defs.tile.isNotEmptyTile
|
|||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import kotlin.math.PI
|
import kotlin.math.PI
|
||||||
|
|
||||||
@JsonAdapter(ObjectOrientation.Adapter::class)
|
|
||||||
data class ObjectOrientation(
|
data class ObjectOrientation(
|
||||||
val json: JsonObject,
|
val json: JsonObject,
|
||||||
val flipImages: Boolean = false,
|
val flipImages: Boolean = false,
|
||||||
@ -142,32 +143,18 @@ data class ObjectOrientation(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Adapter(gson: Gson) : TypeAdapter<ObjectOrientation>() {
|
class Adapter(gson: Gson) {
|
||||||
private val vectors = gson.getAdapter(Vector2f::class.java)
|
private val vectors = gson.getAdapter(Vector2f::class.java)
|
||||||
private val vectorsi = gson.getAdapter(Vector2i::class.java)
|
private val vectorsi = gson.getAdapter(Vector2i::class.java)
|
||||||
private val vectorsd = gson.getAdapter(Vector2d::class.java)
|
private val vectorsd = gson.getAdapter(Vector2d::class.java)
|
||||||
private val drawables = gson.getAdapter(Drawable::class.java)
|
private val drawables = gson.getAdapter(Drawable::class.java)
|
||||||
private val aabbs = gson.getAdapter(AABB::class.java)
|
private val aabbs = gson.getAdapter(AABB::class.java)
|
||||||
private val objectRefs = gson.getAdapter(JsonReference.Object::class.java)
|
|
||||||
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
private val emitter = gson.getAdapter(ParticleEmissionEntry::class.java)
|
||||||
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
private val emitters = gson.listAdapter<ParticleEmissionEntry>()
|
||||||
private val spaces = gson.setAdapter<Vector2i>()
|
private val spaces = gson.setAdapter<Vector2i>()
|
||||||
private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter<ImmutableList<Pair<Vector2i, String>>>
|
private val materialSpaces = gson.getAdapter(TypeToken.getParameterized(ImmutableList::class.java, TypeToken.getParameterized(Pair::class.java, Vector2i::class.java, String::class.java).type)) as TypeAdapter<ImmutableList<Pair<Vector2i, String>>>
|
||||||
|
|
||||||
override fun write(out: JsonWriter, value: ObjectOrientation?) {
|
suspend fun read(obj: JsonObject): ObjectOrientation {
|
||||||
if (value == null) {
|
|
||||||
out.nullValue()
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
TODO()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(`in`: JsonReader): ObjectOrientation? {
|
|
||||||
if (`in`.consumeNull())
|
|
||||||
return null
|
|
||||||
|
|
||||||
val obj = Starbound.ELEMENTS_ADAPTER.objects.read(`in`)
|
|
||||||
val drawables = ArrayList<Drawable>()
|
val drawables = ArrayList<Drawable>()
|
||||||
val flipImages = obj.get("flipImages", false)
|
val flipImages = obj.get("flipImages", false)
|
||||||
val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object"))
|
val renderLayer = RenderLayer.parse(obj.get("renderLayer", "Object"))
|
||||||
@ -201,24 +188,20 @@ data class ObjectOrientation(
|
|||||||
if ("spaceScan" in obj) {
|
if ("spaceScan" in obj) {
|
||||||
occupySpaces = ImmutableSet.of()
|
occupySpaces = ImmutableSet.of()
|
||||||
|
|
||||||
try {
|
for (drawable in drawables) {
|
||||||
for (drawable in drawables) {
|
if (drawable is Drawable.Image) {
|
||||||
if (drawable is Drawable.Image) {
|
val bound = drawable.path.with { "default" }
|
||||||
val bound = drawable.path.with { "default" }
|
val sprite = bound.sprite
|
||||||
val sprite = bound.sprite
|
|
||||||
|
|
||||||
if (sprite != null) {
|
if (sprite != null) {
|
||||||
val new = ImmutableSet.Builder<Vector2i>()
|
val new = ImmutableSet.Builder<Vector2i>()
|
||||||
new.addAll(occupySpaces)
|
new.addAll(occupySpaces)
|
||||||
new.addAll(sprite.worldSpaces(imagePositionI, obj["spaceScan"].asDouble, flipImages))
|
new.addAll(sprite.worldSpacesAsync(imagePositionI, obj["spaceScan"].asDouble, flipImages).await())
|
||||||
occupySpaces = new.build()
|
occupySpaces = new.build()
|
||||||
} else {
|
} else {
|
||||||
LOGGER.error("Unable to space scan image, not a valid sprite reference: $bound")
|
LOGGER.error("Unable to space scan image, not a valid sprite reference: $bound")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err: Throwable) {
|
|
||||||
throw JsonSyntaxException("Unable to space scan image", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +271,7 @@ data class ObjectOrientation(
|
|||||||
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
|
val lightPosition = obj["lightPosition"]?.let { vectorsi.fromJsonTree(it) } ?: Vector2i.ZERO
|
||||||
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
|
val beamAngle = obj.get("beamAngle", 0.0) / 180.0 * PI
|
||||||
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
|
val statusEffectArea = obj["statusEffectArea"]?.let { vectorsd.fromJsonTree(it) }
|
||||||
val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last())
|
val touchDamage = Starbound.loadJsonAsset(obj["touchDamage"] ?: JsonNull.INSTANCE, AssetPathStack.last()).await()
|
||||||
|
|
||||||
val emitters = ArrayList<ParticleEmissionEntry>()
|
val emitters = ArrayList<ParticleEmissionEntry>()
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ data class TileDefinition(
|
|||||||
val category: String,
|
val category: String,
|
||||||
|
|
||||||
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
||||||
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals::tileDamage),
|
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals.onLoadedFuture.thenApply { Globals.tileDamage }),
|
||||||
|
|
||||||
val health: Double? = null,
|
val health: Double? = null,
|
||||||
val requiredHarvestLevel: Int? = null,
|
val requiredHarvestLevel: Int? = null,
|
||||||
@ -62,7 +62,7 @@ data class TileDefinition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val actualDamageTable: TileDamageParameters by lazy {
|
val actualDamageTable: TileDamageParameters by lazy {
|
||||||
val dmg = damageTable.value ?: TileDamageParameters.EMPTY
|
val dmg = damageTable.value.get() ?: TileDamageParameters.EMPTY
|
||||||
|
|
||||||
return@lazy if (health == null && requiredHarvestLevel == null) {
|
return@lazy if (health == null && requiredHarvestLevel == null) {
|
||||||
dmg
|
dmg
|
||||||
|
@ -25,7 +25,7 @@ data class TileModifierDefinition(
|
|||||||
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
val miningSounds: ImmutableList<String> = ImmutableList.of(),
|
||||||
|
|
||||||
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
@Deprecated("", replaceWith = ReplaceWith("this.actualDamageTable"))
|
||||||
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals::tileDamage),
|
val damageTable: AssetReference<TileDamageParameters> = AssetReference(Globals.onLoadedFuture.thenApply { Globals.tileDamage }),
|
||||||
|
|
||||||
@JsonFlat
|
@JsonFlat
|
||||||
val descriptionData: ThingDescription,
|
val descriptionData: ThingDescription,
|
||||||
@ -43,7 +43,7 @@ data class TileModifierDefinition(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val actualDamageTable: TileDamageParameters by lazy {
|
val actualDamageTable: TileDamageParameters by lazy {
|
||||||
val dmg = damageTable.value ?: TileDamageParameters.EMPTY
|
val dmg = damageTable.value.get() ?: TileDamageParameters.EMPTY
|
||||||
|
|
||||||
return@lazy if (health == null && requiredHarvestLevel == null) {
|
return@lazy if (health == null && requiredHarvestLevel == null) {
|
||||||
dmg
|
dmg
|
||||||
|
@ -74,7 +74,7 @@ data class BiomeDefinition(
|
|||||||
surfacePlaceables = surfacePlaceables,
|
surfacePlaceables = surfacePlaceables,
|
||||||
undergroundPlaceables = undergroundPlaceables,
|
undergroundPlaceables = undergroundPlaceables,
|
||||||
|
|
||||||
parallax = parallax?.value?.create(random, verticalMidPoint.toDouble(), hueShift, surfacePlaceables.firstTreeVariant()),
|
parallax = parallax?.value?.get()?.create(random, verticalMidPoint.toDouble(), hueShift, surfacePlaceables.firstTreeVariant()),
|
||||||
|
|
||||||
ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map {
|
ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map {
|
||||||
it.stream()
|
it.stream()
|
||||||
|
@ -45,6 +45,7 @@ import ru.dbotthepony.kstarbound.util.random.staticRandomInt
|
|||||||
import ru.dbotthepony.kstarbound.world.Direction
|
import ru.dbotthepony.kstarbound.world.Direction
|
||||||
import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
import ru.dbotthepony.kstarbound.world.entities.tile.PlantEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
import ru.dbotthepony.kstarbound.world.entities.tile.WorldObject
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
@ -58,10 +59,9 @@ data class BiomePlaceables(
|
|||||||
) {
|
) {
|
||||||
fun firstTreeVariant(): TreeVariant? {
|
fun firstTreeVariant(): TreeVariant? {
|
||||||
return itemDistributions.stream()
|
return itemDistributions.stream()
|
||||||
.flatMap { it.data.itemStream() }
|
.flatMap { it.data.get().itemStream() }
|
||||||
.map { it as? Tree }
|
.filter { it is Tree }
|
||||||
.filterNotNull()
|
.flatMap { (it as Tree).trees.stream() }
|
||||||
.flatMap { it.trees.stream() }
|
|
||||||
.findAny()
|
.findAny()
|
||||||
.orElse(null)
|
.orElse(null)
|
||||||
}
|
}
|
||||||
@ -73,10 +73,10 @@ data class BiomePlaceables(
|
|||||||
val variants: Int = 1,
|
val variants: Int = 1,
|
||||||
val mode: BiomePlaceablesDefinition.Placement = BiomePlaceablesDefinition.Placement.FLOOR,
|
val mode: BiomePlaceablesDefinition.Placement = BiomePlaceablesDefinition.Placement.FLOOR,
|
||||||
@JsonFlat
|
@JsonFlat
|
||||||
val data: DistributionData,
|
val data: CompletableFuture<DistributionData>,
|
||||||
) {
|
) {
|
||||||
fun itemToPlace(x: Int, y: Int): Placement? {
|
fun itemToPlace(x: Int, y: Int): Placement? {
|
||||||
return data.itemToPlace(x, y, priority)
|
return data.get().itemToPlace(x, y, priority)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,16 +46,12 @@ data class BiomePlaceablesDefinition(
|
|||||||
@JsonFlat
|
@JsonFlat
|
||||||
val data: DistributionItemData,
|
val data: DistributionItemData,
|
||||||
) {
|
) {
|
||||||
init {
|
|
||||||
checkNotNull(distribution.value) { "Distribution data is missing" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.DistributionItem {
|
fun create(biome: BiomeDefinition.CreationParams): BiomePlaceables.DistributionItem {
|
||||||
return BiomePlaceables.DistributionItem(
|
return BiomePlaceables.DistributionItem(
|
||||||
priority = priority,
|
priority = priority,
|
||||||
variants = variants,
|
variants = variants,
|
||||||
mode = mode,
|
mode = mode,
|
||||||
data = distribution.value!!.create(this, biome),
|
data = distribution.value.thenApply { (it ?: throw NullPointerException("Distribution data is missing")).create(this, biome) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,4 +370,4 @@ data class BiomePlaceablesDefinition(
|
|||||||
.collect(ImmutableList.toImmutableList())
|
.collect(ImmutableList.toImmutableList())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ class BushVariant(
|
|||||||
ceiling = data.value.ceiling,
|
ceiling = data.value.ceiling,
|
||||||
descriptions = data.value.descriptions.fixDescription("${data.key} with $modName").toMap(),
|
descriptions = data.value.descriptions.fixDescription("${data.key} with $modName").toMap(),
|
||||||
ephemeral = data.value.ephemeral,
|
ephemeral = data.value.ephemeral,
|
||||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.bushDamage).copy(totalHealth = data.value.health),
|
tileDamageParameters = (data.value.damageTable?.value?.get() ?: Globals.bushDamage).copy(totalHealth = data.value.health),
|
||||||
modName = modName,
|
modName = modName,
|
||||||
shapes = data.value.shapes.stream().map { Shape(it.base, it.mods[modName] ?: ImmutableList.of()) }.collect(ImmutableList.toImmutableList())
|
shapes = data.value.shapes.stream().map { Shape(it.base, it.mods[modName] ?: ImmutableList.of()) }.collect(ImmutableList.toImmutableList())
|
||||||
)
|
)
|
||||||
|
@ -53,7 +53,7 @@ data class GrassVariant(
|
|||||||
ephemeral = data.value.ephemeral,
|
ephemeral = data.value.ephemeral,
|
||||||
hueShift = hueShift,
|
hueShift = hueShift,
|
||||||
descriptions = data.value.descriptions.fixDescription(data.value.name).toMap(),
|
descriptions = data.value.descriptions.fixDescription(data.value.name).toMap(),
|
||||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.grassDamage).copy(totalHealth = data.value.health)
|
tileDamageParameters = (data.value.damageTable?.value?.get() ?: Globals.grassDamage).copy(totalHealth = data.value.health)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -587,7 +587,7 @@ class TerrestrialWorldParameters : VisitableWorldParameters() {
|
|||||||
parameters.beamUpRule = params.beamUpRule
|
parameters.beamUpRule = params.beamUpRule
|
||||||
parameters.disableDeathDrops = params.disableDeathDrops
|
parameters.disableDeathDrops = params.disableDeathDrops
|
||||||
parameters.worldEdgeForceRegions = params.worldEdgeForceRegions
|
parameters.worldEdgeForceRegions = params.worldEdgeForceRegions
|
||||||
parameters.weatherPool = primaryBiome.value.weather.stream().binnedChoice(threadLevel).get().random(random).value ?: throw NullPointerException("No weather pool")
|
parameters.weatherPool = primaryBiome.value.weather.stream().binnedChoice(threadLevel).get().random(random).value.get() ?: throw NullPointerException("No weather pool")
|
||||||
parameters.primaryBiome = primaryBiome.key
|
parameters.primaryBiome = primaryBiome.key
|
||||||
parameters.sizeName = sizeName
|
parameters.sizeName = sizeName
|
||||||
parameters.hueShift = primaryBiome.value.hueShift(random)
|
parameters.hueShift = primaryBiome.value.hueShift(random)
|
||||||
|
@ -80,7 +80,7 @@ data class TreeVariant(
|
|||||||
stemDropConfig = data.value.dropConfig.deepCopy(),
|
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||||
descriptions = data.value.descriptions.fixDescription(data.key).toJsonObject(),
|
descriptions = data.value.descriptions.fixDescription(data.key).toJsonObject(),
|
||||||
ephemeral = data.value.ephemeral,
|
ephemeral = data.value.ephemeral,
|
||||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
tileDamageParameters = (data.value.damageTable?.value?.get() ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
||||||
|
|
||||||
foliageSettings = JsonObject(),
|
foliageSettings = JsonObject(),
|
||||||
foliageDropConfig = JsonObject(),
|
foliageDropConfig = JsonObject(),
|
||||||
@ -107,7 +107,7 @@ data class TreeVariant(
|
|||||||
stemDropConfig = data.value.dropConfig.deepCopy(),
|
stemDropConfig = data.value.dropConfig.deepCopy(),
|
||||||
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}").toJsonObject(),
|
descriptions = data.value.descriptions.fixDescription("${data.key} with ${fdata.key}").toJsonObject(),
|
||||||
ephemeral = data.value.ephemeral,
|
ephemeral = data.value.ephemeral,
|
||||||
tileDamageParameters = (data.value.damageTable?.value ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
tileDamageParameters = (data.value.damageTable?.value?.get() ?: Globals.treeDamage).copy(totalHealth = data.value.health),
|
||||||
|
|
||||||
foliageSettings = fdata.json.asJsonObject.deepCopy(),
|
foliageSettings = fdata.json.asJsonObject.deepCopy(),
|
||||||
foliageDropConfig = fdata.value.dropConfig.deepCopy(),
|
foliageDropConfig = fdata.value.dropConfig.deepCopy(),
|
||||||
|
@ -7,9 +7,12 @@ import ru.dbotthepony.kommons.io.readBinaryString
|
|||||||
import ru.dbotthepony.kommons.io.readVarInt
|
import ru.dbotthepony.kommons.io.readVarInt
|
||||||
import ru.dbotthepony.kommons.io.readVarLong
|
import ru.dbotthepony.kommons.io.readVarLong
|
||||||
import ru.dbotthepony.kstarbound.IStarboundFile
|
import ru.dbotthepony.kstarbound.IStarboundFile
|
||||||
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.getValue
|
import ru.dbotthepony.kstarbound.getValue
|
||||||
import ru.dbotthepony.kstarbound.json.readJsonObject
|
import ru.dbotthepony.kstarbound.json.readJsonObject
|
||||||
|
import ru.dbotthepony.kstarbound.util.CarriedExecutor
|
||||||
import ru.dbotthepony.kstarbound.util.sbIntern
|
import ru.dbotthepony.kstarbound.util.sbIntern
|
||||||
|
import ru.dbotthepony.kstarbound.util.supplyAsync
|
||||||
import java.io.BufferedInputStream
|
import java.io.BufferedInputStream
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
@ -19,6 +22,11 @@ import java.io.InputStream
|
|||||||
import java.io.RandomAccessFile
|
import java.io.RandomAccessFile
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
|
import java.util.function.Supplier
|
||||||
|
import kotlin.concurrent.withLock
|
||||||
|
|
||||||
private fun readHeader(reader: RandomAccessFile, required: Int) {
|
private fun readHeader(reader: RandomAccessFile, required: Int) {
|
||||||
val read = reader.read()
|
val read = reader.read()
|
||||||
@ -80,6 +88,10 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
|
|||||||
throw IllegalStateException("${computeFullPath()} is a directory")
|
throw IllegalStateException("${computeFullPath()} is a directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun asyncRead(): CompletableFuture<ByteArray> {
|
||||||
|
throw IllegalStateException("${computeFullPath()} is a directory")
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "SBDirectory[${computeFullPath()} @ ${metadata.get("friendlyName", "")} $path]"
|
return "SBDirectory[${computeFullPath()} @ ${metadata.get("friendlyName", "")} $path]"
|
||||||
}
|
}
|
||||||
@ -113,6 +125,18 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
|
|||||||
return hash
|
return hash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun asyncRead(): CompletableFuture<ByteArray> {
|
||||||
|
if (length > Int.MAX_VALUE.toLong())
|
||||||
|
throw RuntimeException("File is too big to be read in async way: $length bytes to read!")
|
||||||
|
|
||||||
|
return scheduler.schedule(offset, Supplier {
|
||||||
|
reader.seek(offset)
|
||||||
|
val bytes = ByteArray(length.toInt())
|
||||||
|
reader.readFully(bytes)
|
||||||
|
bytes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
override fun open(): InputStream {
|
override fun open(): InputStream {
|
||||||
return object : InputStream() {
|
return object : InputStream() {
|
||||||
private var innerOffset = 0L
|
private var innerOffset = 0L
|
||||||
@ -171,6 +195,57 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun interface ReadScheduler {
|
||||||
|
fun schedule(offset: Long, action: Supplier<ByteArray>): CompletableFuture<ByteArray>
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSDs
|
||||||
|
private object DirectScheduler : ReadScheduler {
|
||||||
|
override fun schedule(offset: Long, action: Supplier<ByteArray>): CompletableFuture<ByteArray> {
|
||||||
|
return Starbound.IO_EXECUTOR.supplyAsync(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HDDs
|
||||||
|
private class PriorityScheduler : ReadScheduler {
|
||||||
|
private val counter = AtomicInteger()
|
||||||
|
|
||||||
|
private data class Action(val offset: Long, val action: Supplier<ByteArray>, val id: Int, val future: CompletableFuture<ByteArray>) : Runnable, Comparable<Action> {
|
||||||
|
override fun compareTo(other: Action): Int {
|
||||||
|
var cmp = offset.compareTo(other.offset) // read files closer to beginning first
|
||||||
|
if (cmp == 0) cmp = id.compareTo(other.id) // else fulfil requests as FIFO
|
||||||
|
return cmp
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
future.complete(action.get())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val lock = ReentrantLock()
|
||||||
|
private val queue = PriorityQueue<Action>()
|
||||||
|
private val carrier = CarriedExecutor(Starbound.IO_EXECUTOR)
|
||||||
|
|
||||||
|
override fun schedule(offset: Long, action: Supplier<ByteArray>): CompletableFuture<ByteArray> {
|
||||||
|
val future = CompletableFuture<ByteArray>()
|
||||||
|
val task = Action(offset, action, counter.getAndIncrement(), future)
|
||||||
|
|
||||||
|
lock.withLock {
|
||||||
|
queue.add(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
carrier.execute {
|
||||||
|
lock.withLock { queue.remove() }.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: we need to determine whenever we are on SSD, or on HDD.
|
||||||
|
// if we are on SSD, assuming it is an HDD won't hurt performance much
|
||||||
|
private val scheduler: ReadScheduler = PriorityScheduler()
|
||||||
|
|
||||||
private val reader by object : ThreadLocal<RandomAccessFile>() {
|
private val reader by object : ThreadLocal<RandomAccessFile>() {
|
||||||
override fun initialValue(): RandomAccessFile {
|
override fun initialValue(): RandomAccessFile {
|
||||||
return RandomAccessFile(path, "r")
|
return RandomAccessFile(path, "r")
|
||||||
|
@ -32,7 +32,7 @@ class ActiveItemStack(entry: ItemRegistry.Entry, config: JsonObject, parameters:
|
|||||||
val animator: Animator
|
val animator: Animator
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var animationConfig = Starbound.loadJsonAsset(lookupProperty("animation"), entry.directory) ?: JsonNull.INSTANCE
|
var animationConfig = Starbound.loadJsonAsset(lookupProperty("animation"), entry.directory).get() ?: JsonNull.INSTANCE
|
||||||
val animationCustom = lookupProperty("animationCustom")
|
val animationCustom = lookupProperty("animationCustom")
|
||||||
|
|
||||||
if (!animationCustom.isJsonNull) {
|
if (!animationCustom.isJsonNull) {
|
||||||
|
@ -4,6 +4,10 @@ import com.google.common.collect.ImmutableSet
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonPrimitive
|
import com.google.gson.JsonPrimitive
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.future.asCompletableFuture
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.gson.contains
|
import ru.dbotthepony.kommons.gson.contains
|
||||||
import ru.dbotthepony.kommons.gson.get
|
import ru.dbotthepony.kommons.gson.get
|
||||||
@ -140,9 +144,9 @@ object ItemRegistry {
|
|||||||
val files = fileTree[type.extension ?: continue] ?: continue
|
val files = fileTree[type.extension ?: continue] ?: continue
|
||||||
|
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
futures.add(Starbound.EXECUTOR.submit {
|
futures.add(Starbound.GLOBAL_SCOPE.launch {
|
||||||
try {
|
try {
|
||||||
val read = JsonPatch.apply(Starbound.ELEMENTS_ADAPTER.read(file.jsonReader()), patches[file.computeFullPath()]).asJsonObject
|
val read = JsonPatch.applyAsync(Starbound.ELEMENTS_ADAPTER.read(file.asyncJsonReader().await()), patches[file.computeFullPath()]).asJsonObject
|
||||||
val readData = data.fromJsonTree(read)
|
val readData = data.fromJsonTree(read)
|
||||||
|
|
||||||
tasks.add {
|
tasks.add {
|
||||||
@ -164,7 +168,7 @@ object ItemRegistry {
|
|||||||
} catch (err: Throwable) {
|
} catch (err: Throwable) {
|
||||||
LOGGER.error("Reading item definition $file", err)
|
LOGGER.error("Reading item definition $file", err)
|
||||||
}
|
}
|
||||||
})
|
}.asCompletableFuture())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import com.google.gson.JsonElement
|
|||||||
import com.google.gson.JsonNull
|
import com.google.gson.JsonNull
|
||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import com.google.gson.JsonSyntaxException
|
import com.google.gson.JsonSyntaxException
|
||||||
|
import kotlinx.coroutines.future.await
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kommons.gson.get
|
import ru.dbotthepony.kommons.gson.get
|
||||||
import ru.dbotthepony.kstarbound.IStarboundFile
|
import ru.dbotthepony.kstarbound.IStarboundFile
|
||||||
@ -146,5 +147,23 @@ enum class JsonPatch(val key: String) {
|
|||||||
|
|
||||||
return base
|
return base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("NAME_SHADOWING")
|
||||||
|
suspend fun applyAsync(base: JsonElement, source: Collection<IStarboundFile>?): JsonElement {
|
||||||
|
source ?: return base
|
||||||
|
var base = base
|
||||||
|
|
||||||
|
for (patch in source) {
|
||||||
|
val read = Starbound.ELEMENTS_ADAPTER.read(patch.asyncJsonReader().await())
|
||||||
|
|
||||||
|
if (read !is JsonArray) {
|
||||||
|
LOGGER.error("$patch root element is not an array")
|
||||||
|
} else {
|
||||||
|
base = apply(base, read, patch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.json.factory
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import com.google.gson.TypeAdapter
|
||||||
|
import com.google.gson.TypeAdapterFactory
|
||||||
|
import com.google.gson.reflect.TypeToken
|
||||||
|
import com.google.gson.stream.JsonReader
|
||||||
|
import com.google.gson.stream.JsonWriter
|
||||||
|
import ru.dbotthepony.kommons.gson.consumeNull
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
object CompletableFutureAdapter : TypeAdapterFactory {
|
||||||
|
override fun <T : Any?> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T>? {
|
||||||
|
if (type.rawType == CompletableFuture::class.java) {
|
||||||
|
val type = type.type as? ParameterizedType ?: return null
|
||||||
|
val parent = gson.getAdapter(TypeToken.get(type.actualTypeArguments[0])) as TypeAdapter<Any?>
|
||||||
|
|
||||||
|
return object : TypeAdapter<CompletableFuture<Any?>>() {
|
||||||
|
override fun write(out: JsonWriter, value: CompletableFuture<Any?>?) {
|
||||||
|
parent.write(out, value?.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun read(`in`: JsonReader): CompletableFuture<Any?>? {
|
||||||
|
return CompletableFuture.completedFuture(parent.read(`in`))
|
||||||
|
}
|
||||||
|
} as TypeAdapter<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
@ -348,7 +348,7 @@ fun provideRootBindings(lua: LuaEnvironment) {
|
|||||||
lua.globals["root"] = table
|
lua.globals["root"] = table
|
||||||
|
|
||||||
table["assetJson"] = luaFunction { path: ByteString ->
|
table["assetJson"] = luaFunction { path: ByteString ->
|
||||||
returnBuffer.setTo(from(Starbound.loadJsonAsset(path.decode())))
|
returnBuffer.setTo(from(Starbound.loadJsonAsset(path.decode()).get()))
|
||||||
}
|
}
|
||||||
|
|
||||||
table["makeCurrentVersionedJson"] = luaStub("makeCurrentVersionedJson")
|
table["makeCurrentVersionedJson"] = luaStub("makeCurrentVersionedJson")
|
||||||
|
@ -38,13 +38,11 @@ import ru.dbotthepony.kstarbound.util.random.random
|
|||||||
import ru.dbotthepony.kstarbound.util.toStarboundString
|
import ru.dbotthepony.kstarbound.util.toStarboundString
|
||||||
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
import ru.dbotthepony.kstarbound.util.uuidFromStarboundString
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.ref.Cleaner
|
|
||||||
import java.sql.DriverManager
|
import java.sql.DriverManager
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread") {
|
sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread") {
|
||||||
private fun makedir(file: File) {
|
private fun makedir(file: File) {
|
||||||
@ -65,7 +63,7 @@ sealed class StarboundServer(val root: File) : BlockableEventLoop("Server thread
|
|||||||
private val worlds = HashMap<WorldID, CompletableFuture<ServerWorld>>()
|
private val worlds = HashMap<WorldID, CompletableFuture<ServerWorld>>()
|
||||||
val universe = ServerUniverse(universeFolder)
|
val universe = ServerUniverse(universeFolder)
|
||||||
val chat = ChatHandler(this)
|
val chat = ChatHandler(this)
|
||||||
val globalScope = CoroutineScope(Starbound.COROUTINE_EXECUTOR + SupervisorJob())
|
val globalScope = CoroutineScope(Starbound.COROUTINES + SupervisorJob())
|
||||||
|
|
||||||
private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
|
private val database = DriverManager.getConnection("jdbc:sqlite:${File(universeFolder, "universe.db").absolutePath.replace('\\', '/')}")
|
||||||
private val databaseCleanable = Starbound.CLEANER.register(this, database::close)
|
private val databaseCleanable = Starbound.CLEANER.register(this, database::close)
|
||||||
|
@ -88,6 +88,10 @@ open class BlockableEventLoop(name: String) : Thread(name), ScheduledExecutorSer
|
|||||||
val coroutines = asCoroutineDispatcher()
|
val coroutines = asCoroutineDispatcher()
|
||||||
val scope = CoroutineScope(coroutines + SupervisorJob())
|
val scope = CoroutineScope(coroutines + SupervisorJob())
|
||||||
|
|
||||||
|
init {
|
||||||
|
priority = 7
|
||||||
|
}
|
||||||
|
|
||||||
private fun nextDeadline(): Long {
|
private fun nextDeadline(): Long {
|
||||||
if (isShutdown || eventQueue.isNotEmpty())
|
if (isShutdown || eventQueue.isNotEmpty())
|
||||||
return 0L
|
return 0L
|
||||||
|
@ -406,10 +406,10 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun anyCellSatisfies(x: Int, y: Int, distance: Int, predicate: CellPredicate): Boolean {
|
fun anyCellSatisfies(x: Int, y: Int, distance: Int, predicate: CellPredicate): Boolean {
|
||||||
for (tx in x - distance .. x + distance) {
|
for (tx in (x - distance) .. (x + distance)) {
|
||||||
for (ty in y - distance .. y + distance) {
|
for (ty in (y - distance) .. (y + distance)) {
|
||||||
val ix = geometry.x.cell(tx)
|
val ix = geometry.x.cell(tx)
|
||||||
val iy = geometry.x.cell(ty)
|
val iy = geometry.y.cell(ty)
|
||||||
|
|
||||||
if (predicate.test(ix, iy, chunkMap.getCellDirect(ix, iy))) {
|
if (predicate.test(ix, iy, chunkMap.getCellDirect(ix, iy))) {
|
||||||
return true
|
return true
|
||||||
@ -587,8 +587,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
* of world generation, everything else is kinda okay to be performed
|
* of world generation, everything else is kinda okay to be performed
|
||||||
* on main world thread, so concurrent access is not needed for now.
|
* on main world thread, so concurrent access is not needed for now.
|
||||||
*
|
*
|
||||||
* ArrayChunkMap does ~not need~ needs synchronization too, unless we use CopyOnWriteArrayList
|
* ArrayChunkMap does ~not~ need synchronization, because 2D array is thread-safe to be read
|
||||||
* for "existing" chunks list.
|
* by multiple thread while one is writing to it (but it might leave to race condition if
|
||||||
|
* we try to read chunks which are currently being initialized).
|
||||||
*/
|
*/
|
||||||
private const val CONCURRENT_SPARSE_CHUNK_MAP = false
|
private const val CONCURRENT_SPARSE_CHUNK_MAP = false
|
||||||
}
|
}
|
||||||
|
@ -986,7 +986,7 @@ class Animator() {
|
|||||||
companion object {
|
companion object {
|
||||||
// lame
|
// lame
|
||||||
fun load(path: String): Animator {
|
fun load(path: String): Animator {
|
||||||
val json = Starbound.loadJsonAsset(path)
|
val json = Starbound.loadJsonAsset(path).get()
|
||||||
|
|
||||||
if (json == null) {
|
if (json == null) {
|
||||||
if (missing.add(path)) {
|
if (missing.add(path)) {
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world.entities
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.defs.EntityType
|
||||||
|
import ru.dbotthepony.kstarbound.math.AABB
|
||||||
|
import java.io.DataOutputStream
|
||||||
|
|
||||||
|
class ProjectileEntity() : DynamicEntity() {
|
||||||
|
override val type: EntityType
|
||||||
|
get() = EntityType.PROJECTILE
|
||||||
|
|
||||||
|
override fun writeNetwork(stream: DataOutputStream, isLegacy: Boolean) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override val metaBoundingBox: AABB
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
override val movement: MovementController
|
||||||
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
|
private fun setup() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,7 @@ class PlayerEntity() : HumanoidActorEntity() {
|
|||||||
|
|
||||||
val inventory = PlayerInventory()
|
val inventory = PlayerInventory()
|
||||||
val songbook = Songbook(this)
|
val songbook = Songbook(this)
|
||||||
val effectAnimator = if (Globals.player.effectsAnimator.value == null) Animator() else Animator(Globals.player.effectsAnimator.value!!)
|
val effectAnimator = if (Globals.player.effectsAnimator.value.get() == null) Animator() else Animator(Globals.player.effectsAnimator.value.get()!!)
|
||||||
override val statusController = StatusController(this, Globals.player.statusControllerSettings)
|
override val statusController = StatusController(this, Globals.player.statusControllerSettings)
|
||||||
val techController = TechController(this)
|
val techController = TechController(this)
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
}
|
}
|
||||||
|
|
||||||
val orientation: ObjectOrientation? get() {
|
val orientation: ObjectOrientation? get() {
|
||||||
return config.value.orientations.getOrNull(orientationIndex.toInt())
|
return config.value.orientations.getOrNull(orientationIndex.toInt())?.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val mergedJson = ManualLazy {
|
protected val mergedJson = ManualLazy {
|
||||||
@ -328,7 +328,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
val orientation = orientation
|
val orientation = orientation
|
||||||
|
|
||||||
if (orientation != null) {
|
if (orientation != null) {
|
||||||
val touchDamageConfig = mergeJson(config.value.touchDamage.deepCopy(), orientation.touchDamage)
|
val touchDamageConfig = mergeJson(config.value.touchDamage.get().deepCopy(), orientation.touchDamage)
|
||||||
|
|
||||||
if (!touchDamageConfig.isJsonNull) {
|
if (!touchDamageConfig.isJsonNull) {
|
||||||
sources.add(Starbound.gson.fromJson(touchDamageConfig, DamageSource::class.java).copy(sourceEntityId = entityID, team = team.get()))
|
sources.add(Starbound.gson.fromJson(touchDamageConfig, DamageSource::class.java).copy(sourceEntityId = entityID, team = team.get()))
|
||||||
@ -352,11 +352,11 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
val animator: Animator
|
val animator: Animator
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (config.value.animation?.value != null) {
|
if (config.value.animation?.value?.get() != null) {
|
||||||
if (config.value.animationCustom.size() > 0 && config.value.animation!!.json != null) {
|
if (config.value.animationCustom.size() > 0 && config.value.animation!!.json.get() != null) {
|
||||||
animator = Animator(Starbound.gson.fromJson(mergeJson(config.value.animation!!.json!!, config.value.animationCustom), AnimationDefinition::class.java))
|
animator = Animator(Starbound.gson.fromJson(mergeJson(config.value.animation!!.json.get()!!.deepCopy(), config.value.animationCustom), AnimationDefinition::class.java))
|
||||||
} else {
|
} else {
|
||||||
animator = Animator(config.value.animation!!.value!!)
|
animator = Animator(config.value.animation!!.value.get()!!)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
animator = Animator()
|
animator = Animator()
|
||||||
@ -566,7 +566,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
flickerPeriod?.update(delta, world.random)
|
flickerPeriod?.update(delta, world.random)
|
||||||
|
|
||||||
if (!isRemote) {
|
if (!isRemote) {
|
||||||
tileHealth.tick(config.value.damageConfig, delta)
|
tileHealth.tick(config.value.damageConfig.get(), delta)
|
||||||
|
|
||||||
if (tileHealth.isHealthy) {
|
if (tileHealth.isHealthy) {
|
||||||
lastClosestSpaceToDamageSource = null
|
lastClosestSpaceToDamageSource = null
|
||||||
@ -744,7 +744,7 @@ open class WorldObject(val config: Registry.Entry<ObjectDefinition>) : TileEntit
|
|||||||
if (unbreakable)
|
if (unbreakable)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
tileHealth.damage(config.value.damageConfig, source, damage)
|
tileHealth.damage(config.value.damageConfig.get(), source, damage)
|
||||||
|
|
||||||
if (damageSpaces.isNotEmpty()) {
|
if (damageSpaces.isNotEmpty()) {
|
||||||
lastClosestSpaceToDamageSource = damageSpaces.minBy { it.toDoubleVector().distanceSquared(source) }
|
lastClosestSpaceToDamageSource = damageSpaces.minBy { it.toDoubleVector().distanceSquared(source) }
|
||||||
|
Loading…
Reference in New Issue
Block a user