Smarter multithreading

This commit is contained in:
DBotThePony 2023-10-03 14:05:27 +07:00
parent 6ae8066ebc
commit 425494e104
Signed by: DBot
GPG Key ID: DCC23B5715498507
16 changed files with 220 additions and 229 deletions

View File

@ -15,7 +15,6 @@ import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ForkJoinTask import java.util.concurrent.ForkJoinTask
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.full.createType
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder { inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
return registerTypeAdapter(T::class.java, adapter) return registerTypeAdapter(T::class.java, adapter)
@ -37,17 +36,17 @@ inline fun <reified T> GsonBuilder.registerTypeAdapter(noinline factory: (Gson)
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this) fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T? { operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T {
return get() return get()
} }
operator fun <T> ThreadLocal<T>.setValue(thisRef: Any, property: KProperty<*>, value: T?) { operator fun <T> ThreadLocal<T>.setValue(thisRef: Any, property: KProperty<*>, value: T) {
set(value) set(value)
} }
operator fun <K, V> ImmutableMap.Builder<K, V>.set(key: K, value: V): ImmutableMap.Builder<K, V> = put(key, value) operator fun <K, V> ImmutableMap.Builder<K, V>.set(key: K, value: V): ImmutableMap.Builder<K, V> = put(key, value)
fun String.sintern(): String = Starbound.strings.intern(this) fun String.sintern(): String = Starbound.STRINGS.intern(this)
inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java) inline fun <reified T> Gson.fromJson(reader: JsonReader): T? = fromJson<T>(reader, T::class.java)

View File

@ -75,7 +75,7 @@ fun main() {
for (chunkX in 0 .. 100) { for (chunkX in 0 .. 100) {
//for (chunkX in 0 .. 17) { //for (chunkX in 0 .. 17) {
// for (chunkY in 21 .. 21) { // for (chunkY in 21 .. 21) {
for (chunkY in 18 .. 24) { for (chunkY in 0 .. 24) {
val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte())) val data = db.read(byteArrayOf(1, 0, chunkX.toByte(), 0, chunkY.toByte()))
val data2 = db.read(byteArrayOf(2, 0, chunkX.toByte(), 0, chunkY.toByte())) val data2 = db.read(byteArrayOf(2, 0, chunkX.toByte(), 0, chunkY.toByte()))

View File

@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound
import com.github.benmanes.caffeine.cache.Interner import com.github.benmanes.caffeine.cache.Interner
import com.google.gson.* import com.google.gson.*
import com.google.gson.internal.bind.JsonTreeReader
import com.google.gson.stream.JsonReader
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
@ -15,37 +13,15 @@ import ru.dbotthepony.kstarbound.api.PhysicalFile
import ru.dbotthepony.kstarbound.defs.* import ru.dbotthepony.kstarbound.defs.*
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.defs.image.SpriteReference import ru.dbotthepony.kstarbound.defs.image.SpriteReference
import ru.dbotthepony.kstarbound.defs.item.impl.BackArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.ChestArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.CurrencyItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.FlashlightDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.HarvestingToolPrototype
import ru.dbotthepony.kstarbound.defs.item.impl.HeadArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition import ru.dbotthepony.kstarbound.defs.item.api.IArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.api.IItemDefinition
import ru.dbotthepony.kstarbound.defs.item.InventoryIcon import ru.dbotthepony.kstarbound.defs.item.InventoryIcon
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.ItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.LegsArmorItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.LiquidItemDefinition
import ru.dbotthepony.kstarbound.defs.item.impl.MaterialItemDefinition
import ru.dbotthepony.kstarbound.defs.monster.MonsterSkillDefinition
import ru.dbotthepony.kstarbound.defs.monster.MonsterTypeDefinition
import ru.dbotthepony.kstarbound.defs.npc.NpcTypeDefinition
import ru.dbotthepony.kstarbound.defs.npc.TenantDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition import ru.dbotthepony.kstarbound.defs.`object`.ObjectDefinition
import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation import ru.dbotthepony.kstarbound.defs.`object`.ObjectOrientation
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.particle.ParticleDefinition
import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList import ru.dbotthepony.kstarbound.defs.player.BlueprintLearnList
import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition import ru.dbotthepony.kstarbound.defs.player.PlayerDefinition
import ru.dbotthepony.kstarbound.defs.player.RecipeDefinition
import ru.dbotthepony.kstarbound.defs.player.TechDefinition
import ru.dbotthepony.kstarbound.defs.projectile.ProjectileDefinition
import ru.dbotthepony.kstarbound.defs.quest.QuestTemplate
import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials import ru.dbotthepony.kstarbound.defs.tile.BuiltinMetaMaterials
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.util.JsonArrayCollector import ru.dbotthepony.kstarbound.util.JsonArrayCollector
import ru.dbotthepony.kstarbound.io.* import ru.dbotthepony.kstarbound.io.*
import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter import ru.dbotthepony.kstarbound.io.json.AABBTypeAdapter
@ -85,12 +61,10 @@ import ru.dbotthepony.kstarbound.util.filterNotNull
import ru.dbotthepony.kstarbound.util.set import ru.dbotthepony.kstarbound.util.set
import ru.dbotthepony.kstarbound.util.traverseJsonPath import ru.dbotthepony.kstarbound.util.traverseJsonPath
import java.io.* import java.io.*
import java.lang.ref.Cleaner
import java.text.DateFormat import java.text.DateFormat
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
import java.util.concurrent.ForkJoinTask import java.util.concurrent.ForkJoinTask
import java.util.concurrent.Future
import java.util.function.BiConsumer import java.util.function.BiConsumer
import java.util.function.BinaryOperator import java.util.function.BinaryOperator
import java.util.function.Function import java.util.function.Function
@ -104,15 +78,22 @@ object Starbound : ISBFileLocator {
const val TICK_TIME_ADVANCE = 0.01666666666666664 const val TICK_TIME_ADVANCE = 0.01666666666666664
const val TICK_TIME_ADVANCE_NANOS = 16_666_666L const val TICK_TIME_ADVANCE_NANOS = 16_666_666L
val CLEANER: Cleaner = Cleaner.create {
val t = Thread(it, "Starbound Global Cleaner Thread")
t.isDaemon = true
t.priority = 2
t
}
// currently Caffeine one saves only 4 megabytes of RAM on pretty big modpack // currently Caffeine one saves only 4 megabytes of RAM on pretty big modpack
// Hrm. // Hrm.
// val strings: Interner<String> = Interner.newWeakInterner() // val strings: Interner<String> = Interner.newWeakInterner()
// val strings: Interner<String> = Interner { it } // val strings: Interner<String> = Interner { it }
val strings: Interner<String> = HashTableInterner(5) val STRINGS: Interner<String> = HashTableInterner(5)
private val polyfill by lazy { loadInternalScript("polyfill") } private val polyfill by lazy { loadInternalScript("polyfill") }
private val logger = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
val gson: Gson = with(GsonBuilder()) { val gson: Gson = with(GsonBuilder()) {
serializeNulls() serializeNulls()
@ -120,9 +101,9 @@ object Starbound : ISBFileLocator {
setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) setFieldNamingPolicy(FieldNamingPolicy.IDENTITY)
setPrettyPrinting() setPrettyPrinting()
registerTypeAdapter(InternedStringAdapter(strings)) registerTypeAdapter(InternedStringAdapter(STRINGS))
InternedJsonElementAdapter(strings).also { InternedJsonElementAdapter(STRINGS).also {
registerTypeAdapter(it) registerTypeAdapter(it)
registerTypeAdapter(it.arrays) registerTypeAdapter(it.arrays)
registerTypeAdapter(it.objects) registerTypeAdapter(it.objects)
@ -134,10 +115,10 @@ object Starbound : ISBFileLocator {
registerTypeAdapterFactory(JsonImplementationTypeFactory) registerTypeAdapterFactory(JsonImplementationTypeFactory)
// ImmutableList, ImmutableSet, ImmutableMap // ImmutableList, ImmutableSet, ImmutableMap
registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(strings)) registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS))
// fastutil collections // fastutil collections
registerTypeAdapterFactory(FastutilTypeAdapterFactory(strings)) registerTypeAdapterFactory(FastutilTypeAdapterFactory(STRINGS))
// ArrayList // ArrayList
registerTypeAdapterFactory(ArrayListAdapterFactory) registerTypeAdapterFactory(ArrayListAdapterFactory)
@ -146,10 +127,10 @@ object Starbound : ISBFileLocator {
registerTypeAdapterFactory(EnumAdapter.Companion) registerTypeAdapterFactory(EnumAdapter.Companion)
// @JsonBuilder // @JsonBuilder
registerTypeAdapterFactory(BuilderAdapter.Factory(strings)) registerTypeAdapterFactory(BuilderAdapter.Factory(STRINGS))
// @JsonFactory // @JsonFactory
registerTypeAdapterFactory(FactoryAdapter.Factory(strings)) registerTypeAdapterFactory(FactoryAdapter.Factory(STRINGS))
// Either<> // Either<>
registerTypeAdapterFactory(EitherTypeAdapter) registerTypeAdapterFactory(EitherTypeAdapter)
@ -192,7 +173,7 @@ object Starbound : ISBFileLocator {
registerTypeAdapterFactory(Json2Function.Companion) registerTypeAdapterFactory(Json2Function.Companion)
// Общее // Общее
registerTypeAdapterFactory(ThingDescription.Factory(strings)) registerTypeAdapterFactory(ThingDescription.Factory(STRINGS))
registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL))
@ -206,7 +187,7 @@ object Starbound : ISBFileLocator {
registerTypeAdapter(ItemStack.Adapter(this@Starbound)) registerTypeAdapter(ItemStack.Adapter(this@Starbound))
registerTypeAdapterFactory(ItemReference.Factory(strings)) registerTypeAdapterFactory(ItemReference.Factory(STRINGS))
registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapterFactory(TreasurePoolDefinition.Companion)
registerTypeAdapterFactory(with(RegistryReferenceFactory()) { registerTypeAdapterFactory(with(RegistryReferenceFactory()) {
@ -846,10 +827,10 @@ object Starbound : ISBFileLocator {
var time = System.currentTimeMillis() var time = System.currentTimeMillis()
if (archivePaths.isNotEmpty()) { if (archivePaths.isNotEmpty()) {
log.line("Searching for pak archives...".also(logger::info)) log.line("Searching for pak archives...".also(LOGGER::info))
for (path in archivePaths) { for (path in archivePaths) {
val line = log.line("Reading index of ${path}...".also(logger::info)) val line = log.line("Reading index of ${path}...".also(LOGGER::info))
addPak(StarboundPak(path) { _, status -> addPak(StarboundPak(path) { _, status ->
line.text = ("${path.parent}/${path.name}: $status") line.text = ("${path.parent}/${path.name}: $status")
@ -857,9 +838,9 @@ object Starbound : ISBFileLocator {
} }
} }
log.line("Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(logger::info)) log.line("Finished reading pak archives in ${System.currentTimeMillis() - time}ms".also(LOGGER::info))
time = System.currentTimeMillis() time = System.currentTimeMillis()
log.line("Building file index...".also(logger::info)) log.line("Building file index...".also(LOGGER::info))
val ext2files = fileSystems.parallelStream() val ext2files = fileSystems.parallelStream()
.flatMap { it.explore() } .flatMap { it.explore() }
@ -895,7 +876,7 @@ object Starbound : ISBFileLocator {
} }
}) })
log.line("Finished building file index in ${System.currentTimeMillis() - time}ms".also(logger::info)) log.line("Finished building file index in ${System.currentTimeMillis() - time}ms".also(LOGGER::info))
val tasks = ArrayList<ForkJoinTask<*>>() val tasks = ArrayList<ForkJoinTask<*>>()
val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1) val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1)

View File

@ -13,6 +13,7 @@ import org.lwjgl.opengl.GLCapabilities
import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryStack
import org.lwjgl.system.MemoryUtil import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.LoadingLog import ru.dbotthepony.kstarbound.LoadingLog
import ru.dbotthepony.kstarbound.util.ManualExecutorService
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT
import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
@ -49,7 +50,6 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.Either
import ru.dbotthepony.kstarbound.util.forEachValid import ru.dbotthepony.kstarbound.util.forEachValid
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
@ -73,29 +73,24 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.time.Duration import java.time.Duration
import java.util.* import java.util.*
import java.util.concurrent.Callable import java.util.concurrent.ForkJoinPool
import java.util.concurrent.CancellationException
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ExecutionException
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import java.util.concurrent.FutureTask
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.locks.ReentrantReadWriteLock
import java.util.function.IntConsumer import java.util.function.IntConsumer
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
class StarboundClient : Closeable, ExecutorService { class StarboundClient : Closeable {
val window: Long val window: Long
val camera = Camera(this) val camera = Camera(this)
val input = UserInput() val input = UserInput()
val thread: Thread = Thread.currentThread() val thread: Thread = Thread.currentThread()
// client specific executor which will accept tasks which involve probable
// callback to foreground executor to initialize thread-unsafe data
// In above case multiple threads will introduce big congestion for resources
val backgroundExecutor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4))
val foregroundExecutor = ManualExecutorService(thread)
val capabilities: GLCapabilities val capabilities: GLCapabilities
var viewportX: Int = 0 var viewportX: Int = 0
@ -150,8 +145,6 @@ class StarboundClient : Closeable, ExecutorService {
val loadingLog = LoadingLog() val loadingLog = LoadingLog()
private val executeQueue = ConcurrentLinkedQueue<Runnable>()
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
private val openglCleanQueue = ReferenceQueue<Any>() private val openglCleanQueue = ReferenceQueue<Any>()
private var openglCleanQueueHead: CleanRef? = null private var openglCleanQueueHead: CleanRef? = null
@ -160,118 +153,6 @@ class StarboundClient : Closeable, ExecutorService {
var prev: CleanRef? = null var prev: CleanRef? = null
} }
private data class InPlaceFuture<V>(private val value: V) : Future<V> {
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return true
}
override fun get(): V {
return value
}
override fun get(timeout: Long, unit: TimeUnit): V {
return value
}
companion object {
val EMPTY = InPlaceFuture(Unit)
}
}
override fun execute(command: Runnable) {
if (isSameThread()) {
command.run()
} else {
executeQueue.add(command)
LockSupport.unpark(thread)
}
}
override fun shutdown() {
throw UnsupportedOperationException()
}
override fun shutdownNow(): MutableList<Runnable> {
throw UnsupportedOperationException()
}
override fun isShutdown(): Boolean {
return false
}
override fun isTerminated(): Boolean {
return false
}
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
throw UnsupportedOperationException()
}
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
if (isSameThread()) return InPlaceFuture(task.call())
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
if (isSameThread()) { task.run(); return InPlaceFuture(result) }
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun submit(task: Runnable): Future<*> {
if (isSameThread()) { task.run(); return InPlaceFuture.EMPTY }
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
if (isSameThread()) {
return tasks.map { InPlaceFuture(it.call()) }
} else {
return tasks.map { submit(it) }.onEach { it.get() }
}
}
override fun <T : Any?> invokeAll(
tasks: Collection<Callable<T>>,
timeout: Long,
unit: TimeUnit
): List<Future<T>> {
if (isSameThread()) {
return tasks.map { InPlaceFuture(it.call()) }
} else {
return tasks.map { submit(it) }.onEach { it.get(timeout, unit) }
}
}
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>): T {
if (tasks.isEmpty())
throw NoSuchElementException("Provided task list is empty")
if (isSameThread()) {
return tasks.first().call()
} else {
return submit(tasks.first()).get()
}
}
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>, timeout: Long, unit: TimeUnit): T {
if (tasks.isEmpty())
throw NoSuchElementException("Provided task list is empty")
if (isSameThread()) {
return tasks.first().call()
} else {
return submit(tasks.first()).get(timeout, unit)
}
}
var openglObjectsCreated = 0L var openglObjectsCreated = 0L
private set private set
@ -421,20 +302,7 @@ class StarboundClient : Closeable, ExecutorService {
} }
private fun executeQueuedTasks() { private fun executeQueuedTasks() {
var next = executeQueue.poll() foregroundExecutor.executeQueuedTasks()
while (next != null) {
next.run()
next = executeQueue.poll()
}
var next2 = futureQueue.poll()
while (next2 != null) {
next2.run()
Thread.interrupted()
next2 = futureQueue.poll()
}
var next3 = openglCleanQueue.poll() as CleanRef? var next3 = openglCleanQueue.poll() as CleanRef?

View File

@ -18,9 +18,9 @@ class StreamVertexBuilder(
) { ) {
val builder = VertexBuilder(attributes, type, initialCapacity) val builder = VertexBuilder(attributes, type, initialCapacity)
private val vao = client.submit(Callable { VertexArrayObject() }) private val vao = client.foregroundExecutor.submit(Callable { VertexArrayObject() })
private val vbo = client.submit(Callable { BufferObject.VBO() }) private val vbo = client.foregroundExecutor.submit(Callable { BufferObject.VBO() })
private val ebo = client.submit(Callable { BufferObject.EBO() }) private val ebo = client.foregroundExecutor.submit(Callable { BufferObject.EBO() })
private var initialized = false private var initialized = false

View File

@ -52,14 +52,14 @@ class TileRenderers(val client: StarboundClient) {
fun getMaterialRenderer(defName: String): TileRenderer { fun getMaterialRenderer(defName: String): TileRenderer {
return matCache.get(defName) { return matCache.get(defName) {
val def = Registries.tiles[defName] // TODO: Пустой рендерер val def = Registries.tiles[defName] // TODO: Пустой рендерер
client.submit(Callable { TileRenderer(this, def!!.value) }).get() client.foregroundExecutor.submit(Callable { TileRenderer(this, def!!.value) }).get()
} }
} }
fun getModifierRenderer(defName: String): TileRenderer { fun getModifierRenderer(defName: String): TileRenderer {
return modCache.get(defName) { return modCache.get(defName) {
val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер
client.submit(Callable { TileRenderer(this, def!!.value) }).get() client.foregroundExecutor.submit(Callable { TileRenderer(this, def!!.value) }).get()
} }
} }

View File

@ -110,7 +110,7 @@ class ClientWorld(
isDirty = false isDirty = false
currentBakeTask = ForkJoinPool.commonPool().submit(Callable { currentBakeTask = client.backgroundExecutor.submit(Callable {
val meshes = LayeredRenderer(client) val meshes = LayeredRenderer(client)
for (x in 0 until renderRegionWidth) { for (x in 0 until renderRegionWidth) {

View File

@ -94,7 +94,7 @@ class Image private constructor(
) ?: throw IllegalArgumentException("File $source is not an image or it is corrupted") ) ?: throw IllegalArgumentException("File $source is not an image or it is corrupted")
val address = MemoryUtil.memAddress(data) val address = MemoryUtil.memAddress(data)
cleaner.register(data) { STBImage.nstbi_image_free(address) } Starbound.CLEANER.register(data) { STBImage.nstbi_image_free(address) }
check(getWidth[0] == width && getHeight[0] == height && components[0] == amountOfChannels) { check(getWidth[0] == width && getHeight[0] == height && components[0] == amountOfChannels) {
"Actual loaded image differs from constructed (this $width x $height with $amountOfChannels channels; loaded ${getWidth[0]} x ${getHeight[0]} with ${components[0]} channels)" "Actual loaded image differs from constructed (this $width x $height with $amountOfChannels channels; loaded ${getWidth[0]} x ${getHeight[0]} with ${components[0]} channels)"
@ -272,7 +272,6 @@ class Image private constructor(
private val cache = ConcurrentHashMap<String, Optional<List<DataSprite>>>() private val cache = ConcurrentHashMap<String, Optional<List<DataSprite>>>()
private val imageCache = ConcurrentHashMap<String, Optional<Image>>() private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
private val logger = LogManager.getLogger() private val logger = LogManager.getLogger()
private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") }
private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder() private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(1)) .expireAfterAccess(Duration.ofMinutes(1))

View File

@ -2,7 +2,9 @@ package ru.dbotthepony.kstarbound.io
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.api.IStarboundFile import ru.dbotthepony.kstarbound.api.IStarboundFile
import ru.dbotthepony.kstarbound.getValue
import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader
import java.io.BufferedInputStream import java.io.BufferedInputStream
import java.io.Closeable import java.io.Closeable
@ -12,6 +14,7 @@ import java.io.IOError
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.lang.ref.Cleaner
import java.nio.channels.Channels import java.nio.channels.Channels
import java.util.* import java.util.*
@ -97,11 +100,9 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
return -1 return -1
} }
synchronized(reader) { reader.seek(innerOffset + offset)
reader.seek(innerOffset + offset) innerOffset++
innerOffset++ return reader.read()
return reader.read()
}
} }
override fun readNBytes(len: Int): ByteArray { override fun readNBytes(len: Int): ByteArray {
@ -112,13 +113,11 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
if (readMax == 0) if (readMax == 0)
return ByteArray(0) return ByteArray(0)
synchronized(reader) { val b = ByteArray(readMax)
val b = ByteArray(readMax) reader.seek(innerOffset + offset)
reader.seek(innerOffset + offset) reader.readFully(b)
reader.readFully(b) innerOffset += readMax
innerOffset += readMax return b
return b
}
} }
override fun read(b: ByteArray, off: Int, len: Int): Int { override fun read(b: ByteArray, off: Int, len: Int): Int {
@ -152,7 +151,11 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String)
} }
} }
private val reader = RandomAccessFile(path, "r") private val reader by object : ThreadLocal<RandomAccessFile>() {
override fun initialValue(): RandomAccessFile {
return RandomAccessFile(path, "r")
}
}
init { init {
readHeader(reader, 0x53) // S readHeader(reader, 0x53) // S

View File

@ -14,7 +14,6 @@ import ru.dbotthepony.kstarbound.io.readString
import ru.dbotthepony.kstarbound.io.readVarInt import ru.dbotthepony.kstarbound.io.readVarInt
import ru.dbotthepony.kstarbound.io.readVarLong import ru.dbotthepony.kstarbound.io.readVarLong
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.EOFException import java.io.EOFException
import java.io.InputStream import java.io.InputStream
import java.io.Reader import java.io.Reader
@ -357,7 +356,7 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada
} }
fun readString(reader: DataInputStream): String { fun readString(reader: DataInputStream): String {
return Starbound.strings.intern(reader.readString(reader.readVarInt())) return Starbound.STRINGS.intern(reader.readString(reader.readVarInt()))
} }
/** /**

View File

@ -13,15 +13,12 @@ import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter import com.google.gson.TypeAdapter
import com.google.gson.TypeAdapterFactory import com.google.gson.TypeAdapterFactory
import com.google.gson.internal.bind.JsonTreeReader import com.google.gson.internal.bind.JsonTreeReader
import com.google.gson.internal.bind.TypeAdapters
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.JsonToken import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.ints.IntArrayList import it.unimi.dsi.fastutil.ints.IntArrayList
import it.unimi.dsi.fastutil.ints.IntList
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
@ -512,7 +509,7 @@ class FactoryAdapter<T : Any> private constructor(
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory, gson: Gson, stringInterner: Interner<String> = Starbound.strings): TypeAdapter<T> { fun <T : Any> createFor(kclass: KClass<T>, config: JsonFactory, gson: Gson, stringInterner: Interner<String> = Starbound.STRINGS): TypeAdapter<T> {
val builder = Builder(kclass) val builder = Builder(kclass)
val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>() val properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()

View File

@ -32,8 +32,8 @@ import java.nio.ByteOrder
import kotlin.system.exitProcess import kotlin.system.exitProcess
@Suppress("unused") @Suppress("unused")
class LuaState private constructor(private val pointer: Pointer, val stringInterner: Interner<String> = Starbound.strings) : Closeable { class LuaState private constructor(private val pointer: Pointer, val stringInterner: Interner<String> = Starbound.STRINGS) : Closeable {
constructor(stringInterner: Interner<String> = Starbound.strings) : this(LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState"), stringInterner) { constructor(stringInterner: Interner<String> = Starbound.STRINGS) : this(LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState"), stringInterner) {
val pointer = this.pointer val pointer = this.pointer
val panic = ClosureManager.getInstance().newClosure( val panic = ClosureManager.getInstance().newClosure(
{ {
@ -44,7 +44,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false) CallContext.getCallContext(Type.SINT, arrayOf(Type.POINTER), CallingConvention.DEFAULT, false)
) )
this.cleanable = CLEANER.register(this) { this.cleanable = Starbound.CLEANER.register(this) {
LuaJNR.INSTANCE.lua_close(pointer) LuaJNR.INSTANCE.lua_close(pointer)
panic.dispose() panic.dispose()
} }
@ -1078,12 +1078,6 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
companion object { companion object {
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()
private val CLEANER: Cleaner = Cleaner.create {
val thread = Thread(it, "Lua State Cleaner")
thread.priority = 1
thread
}
private val sharedBuffers = ThreadLocal<Long>() private val sharedBuffers = ThreadLocal<Long>()
private val sharedStringBufferPtr: Long get() { private val sharedStringBufferPtr: Long get() {
@ -1099,7 +1093,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter
sharedBuffers.set(p) sharedBuffers.set(p)
val p2 = p val p2 = p
CLEANER.register(Thread.currentThread()) { Starbound.CLEANER.register(Thread.currentThread()) {
MemoryIO.getInstance().freeMemory(p2) MemoryIO.getInstance().freeMemory(p2)
} }
} }

View File

@ -39,7 +39,7 @@ object AssetPathStack {
if (b[0] == '/') if (b[0] == '/')
return b return b
return Starbound.strings.intern("$a/$b") return Starbound.STRINGS.intern("$a/$b")
} }
fun remap(path: String): String { fun remap(path: String): String {

View File

@ -0,0 +1,151 @@
package ru.dbotthepony.kstarbound.util
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ExecutorService
import java.util.concurrent.Future
import java.util.concurrent.FutureTask
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.LockSupport
class ManualExecutorService(val thread: Thread = Thread.currentThread()) : ExecutorService {
private val executeQueue = ConcurrentLinkedQueue<Runnable>()
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
private data class InPlaceFuture<V>(private val value: V) : Future<V> {
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
return false
}
override fun isCancelled(): Boolean {
return false
}
override fun isDone(): Boolean {
return true
}
override fun get(): V {
return value
}
override fun get(timeout: Long, unit: TimeUnit): V {
return value
}
companion object {
val EMPTY = InPlaceFuture(Unit)
}
}
fun isSameThread(): Boolean {
return Thread.currentThread() === thread
}
fun executeQueuedTasks() {
check(isSameThread()) { "Trying to execute queued tasks in thread ${Thread.currentThread()}, while correct thread is $thread" }
var next = executeQueue.poll()
while (next != null) {
next.run()
next = executeQueue.poll()
}
var next2 = futureQueue.poll()
while (next2 != null) {
next2.run()
Thread.interrupted()
next2 = futureQueue.poll()
}
}
override fun execute(command: Runnable) {
if (isSameThread()) {
command.run()
} else {
executeQueue.add(command)
LockSupport.unpark(thread)
}
}
override fun shutdown() {
throw UnsupportedOperationException()
}
override fun shutdownNow(): MutableList<Runnable> {
throw UnsupportedOperationException()
}
override fun isShutdown(): Boolean {
return false
}
override fun isTerminated(): Boolean {
return false
}
override fun awaitTermination(timeout: Long, unit: TimeUnit): Boolean {
throw UnsupportedOperationException()
}
override fun <T : Any?> submit(task: Callable<T>): Future<T> {
if (isSameThread()) return InPlaceFuture(task.call())
return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun <T : Any?> submit(task: Runnable, result: T): Future<T> {
if (isSameThread()) { task.run(); return InPlaceFuture(result) }
return FutureTask { task.run(); result }.also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun submit(task: Runnable): Future<*> {
if (isSameThread()) { task.run(); return InPlaceFuture.EMPTY
}
return FutureTask { task.run() }.also { futureQueue.add(it); LockSupport.unpark(thread) }
}
override fun <T : Any?> invokeAll(tasks: Collection<Callable<T>>): List<Future<T>> {
if (isSameThread()) {
return tasks.map { InPlaceFuture(it.call()) }
} else {
return tasks.map { submit(it) }.onEach { it.get() }
}
}
override fun <T : Any?> invokeAll(
tasks: Collection<Callable<T>>,
timeout: Long,
unit: TimeUnit
): List<Future<T>> {
if (isSameThread()) {
return tasks.map { InPlaceFuture(it.call()) }
} else {
return tasks.map { submit(it) }.onEach { it.get(timeout, unit) }
}
}
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>): T {
if (tasks.isEmpty())
throw NoSuchElementException("Provided task list is empty")
if (isSameThread()) {
return tasks.first().call()
} else {
return submit(tasks.first()).get()
}
}
override fun <T : Any?> invokeAny(tasks: Collection<Callable<T>>, timeout: Long, unit: TimeUnit): T {
if (tasks.isEmpty())
throw NoSuchElementException("Provided task list is empty")
if (isSameThread()) {
return tasks.first().call()
} else {
return submit(tasks.first()).get(timeout, unit)
}
}
}

View File

@ -204,7 +204,7 @@ class SBPattern private constructor(
throw IllegalArgumentException("Malformed pattern string: $raw") throw IllegalArgumentException("Malformed pattern string: $raw")
} }
pieces.add(Piece(name = Starbound.strings.intern(raw.substring(open + 1, closing)))) pieces.add(Piece(name = Starbound.STRINGS.intern(raw.substring(open + 1, closing))))
i = closing + 1 i = closing + 1
} }
} }

View File

@ -12,11 +12,11 @@ import java.util.function.Consumer
import java.util.stream.Stream import java.util.stream.Stream
fun String.sbIntern(): String { fun String.sbIntern(): String {
return Starbound.strings.intern(this) return Starbound.STRINGS.intern(this)
} }
fun String.sbIntern2(): String { fun String.sbIntern2(): String {
return Starbound.strings.intern(this.intern()) return Starbound.STRINGS.intern(this.intern())
} }
fun traverseJsonPath(path: String?, element: JsonElement?): JsonElement? { fun traverseJsonPath(path: String?, element: JsonElement?): JsonElement? {