From 425494e104cb4a29de5d5279c18d0b3b127a47b5 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 3 Oct 2023 14:05:27 +0700 Subject: [PATCH] Smarter multithreading --- .../kotlin/ru/dbotthepony/kstarbound/Ext.kt | 7 +- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 2 +- .../ru/dbotthepony/kstarbound/Starbound.kt | 65 +++----- .../kstarbound/client/StarboundClient.kt | 150 ++--------------- .../client/gl/vertex/StreamVertexBuilder.kt | 6 +- .../kstarbound/client/render/TileRenderer.kt | 4 +- .../kstarbound/client/world/ClientWorld.kt | 2 +- .../kstarbound/defs/image/Image.kt | 3 +- .../dbotthepony/kstarbound/io/StarboundPak.kt | 29 ++-- .../kstarbound/io/json/BinaryJsonReader.kt | 3 +- .../io/json/builder/FactoryAdapter.kt | 5 +- .../ru/dbotthepony/kstarbound/lua/LuaState.kt | 14 +- .../kstarbound/util/AssetPathStack.kt | 2 +- .../kstarbound/util/ManualExecutorService.kt | 151 ++++++++++++++++++ .../dbotthepony/kstarbound/util/SBPattern.kt | 2 +- .../ru/dbotthepony/kstarbound/util/Utils.kt | 4 +- 16 files changed, 220 insertions(+), 229 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualExecutorService.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt index 36458331..83dfb253 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Ext.kt @@ -15,7 +15,6 @@ import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask import java.util.stream.Stream import kotlin.reflect.KProperty -import kotlin.reflect.full.createType inline fun GsonBuilder.registerTypeAdapter(adapter: TypeAdapter): GsonBuilder { return registerTypeAdapter(T::class.java, adapter) @@ -37,17 +36,17 @@ inline fun GsonBuilder.registerTypeAdapter(noinline factory: (Gson) fun Array.stream(): Stream = Arrays.stream(this) -operator fun ThreadLocal.getValue(thisRef: Any, property: KProperty<*>): T? { +operator fun ThreadLocal.getValue(thisRef: Any, property: KProperty<*>): T { return get() } -operator fun ThreadLocal.setValue(thisRef: Any, property: KProperty<*>, value: T?) { +operator fun ThreadLocal.setValue(thisRef: Any, property: KProperty<*>, value: T) { set(value) } operator fun ImmutableMap.Builder.set(key: K, value: V): ImmutableMap.Builder = put(key, value) -fun String.sintern(): String = Starbound.strings.intern(this) +fun String.sintern(): String = Starbound.STRINGS.intern(this) inline fun Gson.fromJson(reader: JsonReader): T? = fromJson(reader, T::class.java) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 928019a1..487b4c5f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -75,7 +75,7 @@ fun main() { for (chunkX in 0 .. 100) { //for (chunkX in 0 .. 17) { // 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 data2 = db.read(byteArrayOf(2, 0, chunkX.toByte(), 0, chunkY.toByte())) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 30c82a8b..f87c74e1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -2,8 +2,6 @@ package ru.dbotthepony.kstarbound import com.github.benmanes.caffeine.cache.Interner 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.Object2ObjectFunction 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.image.Image 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.IItemDefinition import ru.dbotthepony.kstarbound.defs.item.InventoryIcon 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`.ObjectOrientation 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.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.MaterialModifier -import ru.dbotthepony.kstarbound.defs.tile.TileDefinition import ru.dbotthepony.kstarbound.util.JsonArrayCollector import ru.dbotthepony.kstarbound.io.* 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.traverseJsonPath import java.io.* +import java.lang.ref.Cleaner import java.text.DateFormat -import java.util.concurrent.ExecutorService -import java.util.concurrent.Executors import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinTask -import java.util.concurrent.Future import java.util.function.BiConsumer import java.util.function.BinaryOperator import java.util.function.Function @@ -104,15 +78,22 @@ object Starbound : ISBFileLocator { const val TICK_TIME_ADVANCE = 0.01666666666666664 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 // Hrm. // val strings: Interner = Interner.newWeakInterner() // val strings: Interner = Interner { it } - val strings: Interner = HashTableInterner(5) + val STRINGS: Interner = HashTableInterner(5) private val polyfill by lazy { loadInternalScript("polyfill") } - private val logger = LogManager.getLogger() + private val LOGGER = LogManager.getLogger() val gson: Gson = with(GsonBuilder()) { serializeNulls() @@ -120,9 +101,9 @@ object Starbound : ISBFileLocator { setFieldNamingPolicy(FieldNamingPolicy.IDENTITY) setPrettyPrinting() - registerTypeAdapter(InternedStringAdapter(strings)) + registerTypeAdapter(InternedStringAdapter(STRINGS)) - InternedJsonElementAdapter(strings).also { + InternedJsonElementAdapter(STRINGS).also { registerTypeAdapter(it) registerTypeAdapter(it.arrays) registerTypeAdapter(it.objects) @@ -134,10 +115,10 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(JsonImplementationTypeFactory) // ImmutableList, ImmutableSet, ImmutableMap - registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(strings)) + registerTypeAdapterFactory(ImmutableCollectionAdapterFactory(STRINGS)) // fastutil collections - registerTypeAdapterFactory(FastutilTypeAdapterFactory(strings)) + registerTypeAdapterFactory(FastutilTypeAdapterFactory(STRINGS)) // ArrayList registerTypeAdapterFactory(ArrayListAdapterFactory) @@ -146,10 +127,10 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(EnumAdapter.Companion) // @JsonBuilder - registerTypeAdapterFactory(BuilderAdapter.Factory(strings)) + registerTypeAdapterFactory(BuilderAdapter.Factory(STRINGS)) // @JsonFactory - registerTypeAdapterFactory(FactoryAdapter.Factory(strings)) + registerTypeAdapterFactory(FactoryAdapter.Factory(STRINGS)) // Either<> registerTypeAdapterFactory(EitherTypeAdapter) @@ -192,7 +173,7 @@ object Starbound : ISBFileLocator { registerTypeAdapterFactory(Json2Function.Companion) // Общее - registerTypeAdapterFactory(ThingDescription.Factory(strings)) + registerTypeAdapterFactory(ThingDescription.Factory(STRINGS)) registerTypeAdapter(EnumAdapter(DamageType::class, default = DamageType.NORMAL)) @@ -206,7 +187,7 @@ object Starbound : ISBFileLocator { registerTypeAdapter(ItemStack.Adapter(this@Starbound)) - registerTypeAdapterFactory(ItemReference.Factory(strings)) + registerTypeAdapterFactory(ItemReference.Factory(STRINGS)) registerTypeAdapterFactory(TreasurePoolDefinition.Companion) registerTypeAdapterFactory(with(RegistryReferenceFactory()) { @@ -846,10 +827,10 @@ object Starbound : ISBFileLocator { var time = System.currentTimeMillis() 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) { - 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 -> 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() - log.line("Building file index...".also(logger::info)) + log.line("Building file index...".also(LOGGER::info)) val ext2files = fileSystems.parallelStream() .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>() val pool = if (parallel) ForkJoinPool.commonPool() else ForkJoinPool(1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 70785780..19b6f43a 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -13,6 +13,7 @@ import org.lwjgl.opengl.GLCapabilities import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil import ru.dbotthepony.kstarbound.LoadingLog +import ru.dbotthepony.kstarbound.util.ManualExecutorService import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf 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.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity -import ru.dbotthepony.kstarbound.util.Either import ru.dbotthepony.kstarbound.util.forEachValid import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.world.LightCalculator @@ -73,29 +73,24 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import java.time.Duration import java.util.* -import java.util.concurrent.Callable -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.ForkJoinPool import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.ReentrantLock -import java.util.concurrent.locks.ReentrantReadWriteLock import java.util.function.IntConsumer -import kotlin.NoSuchElementException import kotlin.collections.ArrayList import kotlin.math.absoluteValue import kotlin.math.roundToInt -class StarboundClient : Closeable, ExecutorService { +class StarboundClient : Closeable { val window: Long val camera = Camera(this) val input = UserInput() 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 var viewportX: Int = 0 @@ -150,8 +145,6 @@ class StarboundClient : Closeable, ExecutorService { val loadingLog = LoadingLog() - private val executeQueue = ConcurrentLinkedQueue() - private val futureQueue = ConcurrentLinkedQueue>() private val openglCleanQueue = ReferenceQueue() private var openglCleanQueueHead: CleanRef? = null @@ -160,118 +153,6 @@ class StarboundClient : Closeable, ExecutorService { var prev: CleanRef? = null } - private data class InPlaceFuture(private val value: V) : Future { - 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 { - 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 submit(task: Callable): Future { - if (isSameThread()) return InPlaceFuture(task.call()) - return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) } - } - - override fun submit(task: Runnable, result: T): Future { - 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 invokeAll(tasks: Collection>): List> { - if (isSameThread()) { - return tasks.map { InPlaceFuture(it.call()) } - } else { - return tasks.map { submit(it) }.onEach { it.get() } - } - } - - override fun invokeAll( - tasks: Collection>, - timeout: Long, - unit: TimeUnit - ): List> { - if (isSameThread()) { - return tasks.map { InPlaceFuture(it.call()) } - } else { - return tasks.map { submit(it) }.onEach { it.get(timeout, unit) } - } - } - - override fun invokeAny(tasks: Collection>): 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 invokeAny(tasks: Collection>, 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 private set @@ -421,20 +302,7 @@ class StarboundClient : Closeable, ExecutorService { } private fun executeQueuedTasks() { - 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() - } + foregroundExecutor.executeQueuedTasks() var next3 = openglCleanQueue.poll() as CleanRef? diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt index e3019f4f..df8689ca 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/gl/vertex/StreamVertexBuilder.kt @@ -18,9 +18,9 @@ class StreamVertexBuilder( ) { val builder = VertexBuilder(attributes, type, initialCapacity) - private val vao = client.submit(Callable { VertexArrayObject() }) - private val vbo = client.submit(Callable { BufferObject.VBO() }) - private val ebo = client.submit(Callable { BufferObject.EBO() }) + private val vao = client.foregroundExecutor.submit(Callable { VertexArrayObject() }) + private val vbo = client.foregroundExecutor.submit(Callable { BufferObject.VBO() }) + private val ebo = client.foregroundExecutor.submit(Callable { BufferObject.EBO() }) private var initialized = false diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index d9432081..1e6c8285 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -52,14 +52,14 @@ class TileRenderers(val client: StarboundClient) { fun getMaterialRenderer(defName: String): TileRenderer { return matCache.get(defName) { 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 { return modCache.get(defName) { val def = Registries.tileModifiers[defName] // TODO: Пустой рендерер - client.submit(Callable { TileRenderer(this, def!!.value) }).get() + client.foregroundExecutor.submit(Callable { TileRenderer(this, def!!.value) }).get() } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt index 71c83c8f..f6a512ac 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/world/ClientWorld.kt @@ -110,7 +110,7 @@ class ClientWorld( isDirty = false - currentBakeTask = ForkJoinPool.commonPool().submit(Callable { + currentBakeTask = client.backgroundExecutor.submit(Callable { val meshes = LayeredRenderer(client) for (x in 0 until renderRegionWidth) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt index b8c30d66..83bc874c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/image/Image.kt @@ -94,7 +94,7 @@ class Image private constructor( ) ?: throw IllegalArgumentException("File $source is not an image or it is corrupted") 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) { "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>>() private val imageCache = ConcurrentHashMap>() private val logger = LogManager.getLogger() - private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") } private val dataCache: Cache = Caffeine.newBuilder() .expireAfterAccess(Duration.ofMinutes(1)) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt index c44e5ac7..5c0fca3c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/StarboundPak.kt @@ -2,7 +2,9 @@ package ru.dbotthepony.kstarbound.io import it.unimi.dsi.fastutil.objects.Object2ObjectFunction import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap +import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.api.IStarboundFile +import ru.dbotthepony.kstarbound.getValue import ru.dbotthepony.kstarbound.io.json.BinaryJsonReader import java.io.BufferedInputStream import java.io.Closeable @@ -12,6 +14,7 @@ import java.io.IOError import java.io.IOException import java.io.InputStream import java.io.RandomAccessFile +import java.lang.ref.Cleaner import java.nio.channels.Channels import java.util.* @@ -97,11 +100,9 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) return -1 } - synchronized(reader) { - reader.seek(innerOffset + offset) - innerOffset++ - return reader.read() - } + reader.seek(innerOffset + offset) + innerOffset++ + return reader.read() } override fun readNBytes(len: Int): ByteArray { @@ -112,13 +113,11 @@ class StarboundPak(val path: File, callback: (finished: Boolean, status: String) if (readMax == 0) return ByteArray(0) - synchronized(reader) { - val b = ByteArray(readMax) - reader.seek(innerOffset + offset) - reader.readFully(b) - innerOffset += readMax - return b - } + val b = ByteArray(readMax) + reader.seek(innerOffset + offset) + reader.readFully(b) + innerOffset += readMax + return b } 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() { + override fun initialValue(): RandomAccessFile { + return RandomAccessFile(path, "r") + } + } init { readHeader(reader, 0x53) // S diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BinaryJsonReader.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BinaryJsonReader.kt index 5d093857..a9c7febf 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BinaryJsonReader.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/BinaryJsonReader.kt @@ -14,7 +14,6 @@ import ru.dbotthepony.kstarbound.io.readString import ru.dbotthepony.kstarbound.io.readVarInt import ru.dbotthepony.kstarbound.io.readVarLong import java.io.DataInputStream -import java.io.DataOutputStream import java.io.EOFException import java.io.InputStream import java.io.Reader @@ -357,7 +356,7 @@ class BinaryJsonReader(private val stream: DataInputStream) : JsonReader(unreada } fun readString(reader: DataInputStream): String { - return Starbound.strings.intern(reader.readString(reader.readVarInt())) + return Starbound.STRINGS.intern(reader.readString(reader.readVarInt())) } /** diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt index be6feea9..465360e2 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/io/json/builder/FactoryAdapter.kt @@ -13,15 +13,12 @@ import com.google.gson.JsonSyntaxException import com.google.gson.TypeAdapter import com.google.gson.TypeAdapterFactory import com.google.gson.internal.bind.JsonTreeReader -import com.google.gson.internal.bind.TypeAdapters import com.google.gson.reflect.TypeToken import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonToken import com.google.gson.stream.JsonWriter import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap 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.ObjectArraySet import org.apache.logging.log4j.LogManager @@ -512,7 +509,7 @@ class FactoryAdapter private constructor( companion object { private val LOGGER = LogManager.getLogger() - fun createFor(kclass: KClass, config: JsonFactory, gson: Gson, stringInterner: Interner = Starbound.strings): TypeAdapter { + fun createFor(kclass: KClass, config: JsonFactory, gson: Gson, stringInterner: Interner = Starbound.STRINGS): TypeAdapter { val builder = Builder(kclass) val properties = kclass.declaredMembers.filterIsInstance>() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt index 3e6f0cb3..5a55188d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/lua/LuaState.kt @@ -32,8 +32,8 @@ import java.nio.ByteOrder import kotlin.system.exitProcess @Suppress("unused") -class LuaState private constructor(private val pointer: Pointer, val stringInterner: Interner = Starbound.strings) : Closeable { - constructor(stringInterner: Interner = Starbound.strings) : this(LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState"), stringInterner) { +class LuaState private constructor(private val pointer: Pointer, val stringInterner: Interner = Starbound.STRINGS) : Closeable { + constructor(stringInterner: Interner = Starbound.STRINGS) : this(LuaJNR.INSTANCE.luaL_newstate() ?: throw OutOfMemoryError("Unable to allocate new LuaState"), stringInterner) { val pointer = this.pointer 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) ) - this.cleanable = CLEANER.register(this) { + this.cleanable = Starbound.CLEANER.register(this) { LuaJNR.INSTANCE.lua_close(pointer) panic.dispose() } @@ -1078,12 +1078,6 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter companion object { 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() private val sharedStringBufferPtr: Long get() { @@ -1099,7 +1093,7 @@ class LuaState private constructor(private val pointer: Pointer, val stringInter sharedBuffers.set(p) val p2 = p - CLEANER.register(Thread.currentThread()) { + Starbound.CLEANER.register(Thread.currentThread()) { MemoryIO.getInstance().freeMemory(p2) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt index 786bee7a..656fbae1 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/AssetPathStack.kt @@ -39,7 +39,7 @@ object AssetPathStack { if (b[0] == '/') return b - return Starbound.strings.intern("$a/$b") + return Starbound.STRINGS.intern("$a/$b") } fun remap(path: String): String { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualExecutorService.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualExecutorService.kt new file mode 100644 index 00000000..abc25490 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ManualExecutorService.kt @@ -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() + private val futureQueue = ConcurrentLinkedQueue>() + + private data class InPlaceFuture(private val value: V) : Future { + 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 { + 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 submit(task: Callable): Future { + if (isSameThread()) return InPlaceFuture(task.call()) + return FutureTask(task).also { futureQueue.add(it); LockSupport.unpark(thread) } + } + + override fun submit(task: Runnable, result: T): Future { + 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 invokeAll(tasks: Collection>): List> { + if (isSameThread()) { + return tasks.map { InPlaceFuture(it.call()) } + } else { + return tasks.map { submit(it) }.onEach { it.get() } + } + } + + override fun invokeAll( + tasks: Collection>, + timeout: Long, + unit: TimeUnit + ): List> { + if (isSameThread()) { + return tasks.map { InPlaceFuture(it.call()) } + } else { + return tasks.map { submit(it) }.onEach { it.get(timeout, unit) } + } + } + + override fun invokeAny(tasks: Collection>): 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 invokeAny(tasks: Collection>, 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) + } + } + +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt index b853f854..1275b5e7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/SBPattern.kt @@ -204,7 +204,7 @@ class SBPattern private constructor( 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 } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt index 4dad612a..2c4aab7d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Utils.kt @@ -12,11 +12,11 @@ import java.util.function.Consumer import java.util.stream.Stream fun String.sbIntern(): String { - return Starbound.strings.intern(this) + return Starbound.STRINGS.intern(this) } fun String.sbIntern2(): String { - return Starbound.strings.intern(this.intern()) + return Starbound.STRINGS.intern(this.intern()) } fun traverseJsonPath(path: String?, element: JsonElement?): JsonElement? {