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.stream.Stream
import kotlin.reflect.KProperty
import kotlin.reflect.full.createType
inline fun <reified T> GsonBuilder.registerTypeAdapter(adapter: TypeAdapter<T>): GsonBuilder {
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)
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T? {
operator fun <T> ThreadLocal<T>.getValue(thisRef: Any, property: KProperty<*>): T {
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)
}
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)

View File

@ -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()))

View File

@ -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<String> = Interner.newWeakInterner()
// 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 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<ForkJoinTask<*>>()
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.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<Runnable>()
private val futureQueue = ConcurrentLinkedQueue<FutureTask<*>>()
private val openglCleanQueue = ReferenceQueue<Any>()
private var openglCleanQueueHead: CleanRef? = null
@ -160,118 +153,6 @@ class StarboundClient : Closeable, ExecutorService {
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
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?

View File

@ -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

View File

@ -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()
}
}

View File

@ -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) {

View File

@ -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<String, Optional<List<DataSprite>>>()
private val imageCache = ConcurrentHashMap<String, Optional<Image>>()
private val logger = LogManager.getLogger()
private val cleaner = Cleaner.create { Thread(it, "STB Image Cleaner") }
private val dataCache: Cache<String, ByteBuffer> = Caffeine.newBuilder()
.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.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<RandomAccessFile>() {
override fun initialValue(): RandomAccessFile {
return RandomAccessFile(path, "r")
}
}
init {
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.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()))
}
/**

View File

@ -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<T : Any> private constructor(
companion object {
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 properties = kclass.declaredMembers.filterIsInstance<KProperty1<T, *>>()

View File

@ -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<String> = Starbound.strings) : Closeable {
constructor(stringInterner: Interner<String> = 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<String> = Starbound.STRINGS) : Closeable {
constructor(stringInterner: Interner<String> = 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<Long>()
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)
}
}

View File

@ -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 {

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")
}
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
}
}

View File

@ -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? {