From 278f66d89209316fa9143a7b400fbb6a26be6e63 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 2 Feb 2024 17:53:10 +0700 Subject: [PATCH] Finally, client threads --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 87 +----------- .../kstarbound/client/StarboundClient.kt | 132 ++++++++++++++---- .../kstarbound/server/StarboundServer.kt | 12 +- .../server/network/ServerConnection.kt | 2 +- 4 files changed, 112 insertions(+), 121 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index ab600d1b..57b7b308 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -48,7 +48,7 @@ fun main() { // println(VersionedJson(meta)) val server = IntegratedStarboundServer(File("./")) - val client = StarboundClient() + val client = StarboundClient.create().get() val world = ServerWorld(server, 0L, WorldGeometry(Vector2i(3000, 2000), true, false)) world.addChunkSource(LegacyChunkSource(db)) world.startThread() @@ -74,54 +74,6 @@ fun main() { //ply!!.position = Vector2d(225.0, 680.0) //ply!!.spawn() - //for (chunkX in 17 .. 18) { - //for (chunkX in 14 .. 24) { - for (chunkX in 0 .. 100) { - //for (chunkX in 0 .. 17) { - // for (chunkY in 21 .. 21) { - for (chunkY in 18 .. 24) { - //val data = db.read(byteArrayOf(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())) - //val data2 = db.read(byteArrayOf(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())) - - /*if (data != null) { - var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater()))) - reader.skipBytes(3) - - val chunk = world.chunkMap.compute(chunkX, chunkY) - - if (chunk != null) { - for (y in 0 .. 31) { - for (x in 0 .. 31) { - check(chunk.setCell(x, y, MutableCell().read(reader))) - } - } - } - }*/ - - /*if (data2 != null) { - val reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data2), Inflater()))) - val i = reader.readVarInt() - - for (i2 in 0 until i) { - val obj = VersionedJson(reader) - - if (obj.identifier == "ObjectEntity") { - try { - WorldObject(world, obj.content.asJsonObject).spawn() - //println(obj.content) - //println(created) - } catch (err: Throwable) { - - } - } - } - - //val read = BinaryJsonReader.readElement(reader) - //println(read) - }*/ - } - } - //client.world!!.parallax = Starbound.parallaxAccess["garden"] val rand = Random() @@ -137,47 +89,12 @@ fun main() { //item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0)) } - client.connectToLocalServer(client, server.channels.createLocalChannel(), UUID.randomUUID()) + client.connectToLocalServer(server.channels.createLocalChannel(), UUID.randomUUID()) } //ent.position += Vector2d(y = 14.0, x = -10.0) client.camera.pos = Vector2d(238.0, 685.0) //client.camera.pos = Vector2f(0f, 0f) - client.onDrawGUI { - client.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) - client.font.render("Cursor: ${client.mouseCoordinates} -> ${client.screenToWorld(client.mouseCoordinates)}", y = 160f, scale = 0.25f) - client.font.render("World chunk: ${client.world?.chunkFromCell(client.camera.pos)}", y = 180f, scale = 0.25f) - } - //ent.spawn() - - client.input.addScrollCallback { _, x, y -> - if (y > 0.0) { - client.settings.zoom *= y.toFloat() * 2f - } else if (y < 0.0) { - client.settings.zoom /= -y.toFloat() * 2f - } - } - - while (client.renderFrame()) { - val ply = client.activeConnection?.character - - if (ply != null) { - client.camera.pos = ply.position - - ply.movement.controlMove = if (client.input.KEY_A_DOWN) Direction.LEFT else if (client.input.KEY_D_DOWN) Direction.RIGHT else null - ply.movement.controlJump = client.input.KEY_SPACE_DOWN - ply.movement.controlRun = !client.input.KEY_LEFT_SHIFT_DOWN - } else { - client.camera.pos += Vector2d( - (if (client.input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0), - (if (client.input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) + (if (client.input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / client.settings.zoom else 0.0) - ) - } - - if (client.input.KEY_ESCAPE_PRESSED) { - glfwSetWindowShouldClose(client.window, true) - } - } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 122d5176..3dea8e15 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -47,7 +47,6 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.VertexBuilder import ru.dbotthepony.kstarbound.client.input.UserInput import ru.dbotthepony.kstarbound.client.network.ClientConnection import ru.dbotthepony.kstarbound.server.network.packets.TrackedPositionPacket -import ru.dbotthepony.kstarbound.server.network.packets.TrackedSizePacket import ru.dbotthepony.kstarbound.client.render.Camera import ru.dbotthepony.kstarbound.client.render.Font import ru.dbotthepony.kstarbound.client.render.LayeredRenderer @@ -58,6 +57,7 @@ import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.util.forEachValid import ru.dbotthepony.kstarbound.util.formatBytesShort +import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.AbstractCell @@ -79,6 +79,7 @@ import java.nio.ByteBuffer import java.nio.ByteOrder import java.time.Duration import java.util.* +import java.util.concurrent.CompletableFuture import java.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinWorkerThread import java.util.concurrent.atomic.AtomicInteger @@ -92,19 +93,20 @@ import kotlin.math.absoluteValue import kotlin.math.roundToInt import kotlin.properties.Delegates -class StarboundClient : Closeable { +class StarboundClient private constructor(val clientID: Int) : Closeable { val window: Long val camera = Camera(this) val input = UserInput() val thread: Thread = Thread.currentThread() private val threadCounter = AtomicInteger() + // client specific executor which will accept tasks which involve probable // callback to foreground executor to initialize thread-unsafe data // In above case too many threads will introduce big congestion for resources, stalling entire workload; wasting cpu resources val executor = ForkJoinPool(Runtime.getRuntime().availableProcessors().coerceAtMost(4), { object : ForkJoinWorkerThread(it) { init { - name = "Background Executor for '${thread.name}'-${threadCounter.incrementAndGet()}" + name = "Starbound Client $clientID executor ${threadCounter.incrementAndGet()}" } override fun onTermination(exception: Throwable?) { @@ -150,7 +152,7 @@ class StarboundClient : Closeable { var preciseWait = false - var clientTerminated = false + var shouldTerminate = false private set var viewportMatrixScreen: Matrix3f @@ -172,14 +174,14 @@ class StarboundClient : Closeable { activeConnection = ClientConnection.connectToLocalServer(client, address, uuid) } - fun connectToLocalServer(client: StarboundClient, address: Channel, uuid: UUID) { + fun connectToLocalServer(address: Channel, uuid: UUID) { check(activeConnection == null) { "Already having active connection to server: $activeConnection" } - activeConnection = ClientConnection.connectToLocalServer(client, address, uuid) + activeConnection = ClientConnection.connectToLocalServer(this, address, uuid) } - fun connectToRemoteServer(client: StarboundClient, address: SocketAddress, uuid: UUID) { + fun connectToRemoteServer(address: SocketAddress, uuid: UUID) { check(activeConnection == null) { "Already having active connection to server: $activeConnection" } - activeConnection = ClientConnection.connectToRemoteServer(client, address, uuid) + activeConnection = ClientConnection.connectToRemoteServer(this, address, uuid) } private val scissorStack = LinkedList() @@ -934,7 +936,7 @@ class StarboundClient : Closeable { } } - fun renderFrame(): Boolean { + private fun renderFrame(): Boolean { ensureSameThread() var diff = nextRender - System.nanoTime() @@ -1012,6 +1014,12 @@ class StarboundClient : Closeable { fn.invoke() } + if (world != null) { + font.render("Camera: ${camera.pos} ${settings.zoom}", y = 140f, scale = 0.25f) + font.render("Cursor: $mouseCoordinates -> ${screenToWorld(mouseCoordinates)}", y = 160f, scale = 0.25f) + font.render("World chunk: ${world.chunkFromCell(camera.pos)}", y = 180f, scale = 0.25f) + } + drawPerformanceBasic(false) GLFW.glfwSwapBuffers(window) @@ -1032,39 +1040,101 @@ class StarboundClient : Closeable { } } + private fun spin() { + try { + while (!shouldTerminate && renderFrame()) { + val ply = activeConnection?.character + + if (ply != null) { + camera.pos = ply.position + + ply.movement.controlMove = if (input.KEY_A_DOWN) Direction.LEFT else if (input.KEY_D_DOWN) Direction.RIGHT else null + ply.movement.controlJump = input.KEY_SPACE_DOWN + ply.movement.controlRun = !input.KEY_LEFT_SHIFT_DOWN + } else { + camera.pos += Vector2d( + (if (input.KEY_A_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0) + (if (input.KEY_D_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0), + (if (input.KEY_W_DOWN) Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0) + (if (input.KEY_S_DOWN) -Starbound.TICK_TIME_ADVANCE * 32f / settings.zoom else 0.0) + ) + } + + if (input.KEY_ESCAPE_PRESSED) { + GLFW.glfwSetWindowShouldClose(window, true) + } + } + } catch (err: Throwable) { + LOGGER.fatal("Exception in client loop", err) + } finally { + executor.shutdown() + + lock.lock() + + try { + if (window != MemoryUtil.NULL) { + Callbacks.glfwFreeCallbacks(window) + GLFW.glfwDestroyWindow(window) + } + + if (--clients == 0) { + GLFW.glfwTerminate() + GLFW.glfwSetErrorCallback(null)?.free() + glfwInitialized = false + } + + shouldTerminate = true + + for (callback in terminateCallbacks) { + callback.invoke() + } + } catch (err: Throwable) { + LOGGER.fatal("Exception while destroying client", err) + } finally { + lock.unlock() + } + } + } + fun onTermination(lambda: () -> Unit) { terminateCallbacks.add(lambda) } override fun close() { - if (clientTerminated) - return + shouldTerminate = true + } - lock.lock() - - try { - if (window != MemoryUtil.NULL) { - Callbacks.glfwFreeCallbacks(window) - GLFW.glfwDestroyWindow(window) + init { + input.addScrollCallback { _, x, y -> + if (y > 0.0) { + settings.zoom *= y.toFloat() * 2f + } else if (y < 0.0) { + settings.zoom /= -y.toFloat() * 2f } - - if (--clients == 0) { - GLFW.glfwTerminate() - GLFW.glfwSetErrorCallback(null)?.free() - glfwInitialized = false - } - - clientTerminated = true - - for (callback in terminateCallbacks) { - callback.invoke() - } - } finally { - lock.unlock() } } companion object { + fun create(): CompletableFuture { + val future = CompletableFuture() + val clientID = COUNTER.getAndIncrement() + + val thread = Thread(Runnable { + val client = try { + StarboundClient(clientID) + } catch (err: Throwable) { + future.completeExceptionally(err) + throw err + } + + future.complete(client) + client.spin() + }, "Starbound Client $clientID") + + thread.start() + + return future + } + + private val COUNTER = AtomicInteger() private val LOGGER = LogManager.getLogger(StarboundClient::class.java) private val CLIENTS = ThreadLocal() private val WHITE = ByteBuffer.allocateDirect(4).also { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt index f8c59049..2d12f53c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt @@ -24,7 +24,8 @@ abstract class StarboundServer(val root: File) : Closeable { val worlds: MutableList = Collections.synchronizedList(ArrayList()) - val thread = Thread(::runThread, "Starbound Server ${threadCounter.incrementAndGet()}") + val serverID = threadCounter.getAndIncrement() + val thread = Thread(::runThread, "Starbound Server $serverID") val mailbox = MailboxExecutorService(thread) val settings = ServerSettings() @@ -42,9 +43,12 @@ abstract class StarboundServer(val root: File) : Closeable { fun playerInGame(player: ServerConnection) { val world = worlds.first() - player.world = world - world.players.add(player) - player.send(JoinWorldPacket(world)) + + world.mailbox.execute { + player.world = world + world.players.add(player) + player.send(JoinWorldPacket(world)) + } } protected abstract fun close0() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt index 7897e5d7..11c4f8a4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt @@ -25,7 +25,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn needsToRecomputeTrackedChunks = true } - var trackedPosition: Vector2d = Vector2d(238.0, 685.0) + var trackedPosition: Vector2d = Vector2d.ZERO set(value) { if (field != value) { field = value