diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 65d44044..bea1e1c8 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -42,7 +42,7 @@ fun main() { val client = StarboundClient.create().get() val world = ServerWorld(server, 0L, WorldGeometry(Vector2i(3000, 2000), true, false)) world.addChunkSource(LegacyChunkSource(db)) - world.startThread() + world.thread.start() //Starbound.addFilePath(File("./unpacked_assets/")) Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt index 5146762f..7b7cb195 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Starbound.kt @@ -64,7 +64,7 @@ object Starbound : ISBFileLocator { const val ENGINE_VERSION = "0.0.1" const val PROTOCOL_VERSION = 1 const val TICK_TIME_ADVANCE = 1.0 / 60.0 - const val TICK_TIME_ADVANCE_NANOS = 16_666_666L + const val TICK_TIME_ADVANCE_NANOS = (TICK_TIME_ADVANCE * 1_000_000_000L).toLong() // compile flags. uuuugh const val DEDUP_CELL_STATES = true diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 3cad58dc..f1584272 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -64,6 +64,7 @@ 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.ExecutionSpinner import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.LightCalculator @@ -150,8 +151,6 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { var fullbright = true - var preciseWait = false - var shouldTerminate = false private set @@ -682,43 +681,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA } - // nanoseconds - var frameRenderTime = 0L - private set - - private var nextRender = System.nanoTime() - private val frameRenderTimes = LongArray(60) { 1L } - private var frameRenderIndex = 0 - private val renderWaitTimes = LongArray(60) { 1L } - private var renderWaitIndex = 0 - private var lastRender = System.nanoTime() - - val averageRenderWait: Double get() { - var sum = 0.0 - - for (value in renderWaitTimes) - sum += value - - if (sum == 0.0) - return 0.0 - - sum /= 1_000_000_000.0 - return sum / renderWaitTimes.size - } - - val averageRenderTime: Double get() { - var sum = 0.0 - - for (value in frameRenderTimes) - sum += value - - if (sum == 0.0) - return 0.0 - - sum /= 1_000_000_000.0 - return sum / frameRenderTimes.size - } - + val spinner = ExecutionSpinner(mailbox, ::renderFrame, Starbound.TICK_TIME_ADVANCE_NANOS, true) val settings = ClientSettings() val viewportCells: ICellAccess = object : ICellAccess { @@ -790,8 +753,8 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { private fun drawPerformanceBasic(onlyMemory: Boolean) { val runtime = Runtime.getRuntime() - if (!onlyMemory) font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) - if (!onlyMemory) font.render("Frame: ${(averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) + if (!onlyMemory) font.render("Latency: ${(spinner.averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) + if (!onlyMemory) font.render("Frame: ${(spinner.averageRenderTime * 1_00000.0).toInt() / 100f}ms", y = font.lineHeight * 0.6f, scale = 0.4f) font.render("JVM Heap: ${formatBytesShort(runtime.totalMemory() - runtime.freeMemory())}", y = font.lineHeight * 1.2f, scale = 0.4f) if (!onlyMemory) font.render("OGL C: $openglObjectsCreated D: $openglObjectsCleaned A: ${openglObjectsCreated - openglObjectsCleaned}", y = font.lineHeight * 1.8f, scale = 0.4f) } @@ -937,112 +900,84 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { } private fun renderFrame(): Boolean { - ensureSameThread() - - var diff = nextRender - System.nanoTime() - - // try to sleep until next frame as precise as possible - while (diff > 0L) { - executeQueuedTasks() - diff = nextRender - System.nanoTime() - - if (preciseWait) { - if (diff >= 1_500_000L) { - LockSupport.parkNanos(1_000_000L) - } else { - Thread.yield() - } - } else { - LockSupport.parkNanos(diff) - } + if (GLFW.glfwWindowShouldClose(window)) { + close() + return false } - val mark = System.nanoTime() - - try { - if (GLFW.glfwWindowShouldClose(window)) { - close() - return false - } - - val world = world - - if (!isRenderingGame) { - executeQueuedTasks() - GLFW.glfwPollEvents() - - if (world != null && Starbound.initialized) - world.think() - - return true - } - - if (!Starbound.initialized || !fontInitialized) { - renderLoadingScreen() - return true - } - - layers.clear() - - uberShaderPrograms.forValidRefs { - if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) { - it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat()) - } - } - - clearColor = RGBAColor.SLATE_GRAY - glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) - - if (world != null) { - renderWorld(world) - } - - layers.render() - - val activeConnection = activeConnection - - if (activeConnection != null) { - activeConnection.send(TrackedPositionPacket(camera.pos)) - } - - uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } - fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } - - stack.clear(Matrix3f.identity()) - - for (fn in onDrawGUI) { - 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) - GLFW.glfwPollEvents() - input.think() - - camera.think(Starbound.TICK_TIME_ADVANCE) + val world = world + if (!isRenderingGame) { executeQueuedTasks() + GLFW.glfwPollEvents() + + if (world != null && Starbound.initialized) + world.think() return true - } finally { - frameRenderTime = System.nanoTime() - mark - frameRenderTimes[++frameRenderIndex % frameRenderTimes.size] = frameRenderTime - renderWaitTimes[++renderWaitIndex % renderWaitTimes.size] = System.nanoTime() - lastRender - lastRender = System.nanoTime() - nextRender = mark + Starbound.TICK_TIME_ADVANCE_NANOS } + + if (!Starbound.initialized || !fontInitialized) { + executeQueuedTasks() + renderLoadingScreen() + return true + } + + input.think() + camera.think(Starbound.TICK_TIME_ADVANCE) + executeQueuedTasks() + + layers.clear() + + uberShaderPrograms.forValidRefs { + if (it.flags.contains(UberShader.Flag.NEEDS_SCREEN_SIZE)) { + it.screenSize = Vector2f(viewportWidth.toFloat(), viewportHeight.toFloat()) + } + } + + clearColor = RGBAColor.SLATE_GRAY + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) + + if (world != null) { + renderWorld(world) + } + + layers.render() + + val activeConnection = activeConnection + + if (activeConnection != null) { + activeConnection.send(TrackedPositionPacket(camera.pos)) + } + + uberShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } + fontShaderPrograms.forValidRefs { it.viewMatrix = viewportMatrixScreen } + + stack.clear(Matrix3f.identity()) + + for (fn in onDrawGUI) { + 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) + GLFW.glfwPollEvents() + + executeQueuedTasks() + + return true } private fun spin() { try { - while (!shouldTerminate && renderFrame()) { + while (!shouldTerminate && spinner.spin()) { val ply = activeConnection?.character if (ply != null) { @@ -1056,6 +991,8 @@ class StarboundClient private constructor(val clientID: Int) : Closeable { (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) ) + + camera.pos = world?.geometry?.wrap(camera.pos) ?: camera.pos } if (input.KEY_ESCAPE_PRESSED) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt index f50e2809..bdbdea82 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/ClientConnection.kt @@ -17,7 +17,6 @@ import ru.dbotthepony.kstarbound.network.packets.HelloListener import ru.dbotthepony.kstarbound.network.packets.HelloPacket import java.net.SocketAddress import java.util.* -import kotlin.properties.Delegates // client -> server class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: UUID) : Connection(ConnectionSide.CLIENT, type, uuid) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt similarity index 77% rename from src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt rename to src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt index 40081361..b42be8f6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/InitialChunkDataPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ChunkCellsPacket.kt @@ -14,7 +14,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableCell import java.io.DataInputStream import java.io.DataOutputStream -class InitialChunkDataPacket(val pos: ChunkPos, val data: List) : IClientPacket { +class ChunkCellsPacket(val pos: ChunkPos, val data: List) : IClientPacket { constructor(stream: DataInputStream) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() }) constructor(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList(CHUNK_SIZE * CHUNK_SIZE).also { for (x in 0 until CHUNK_SIZE) { @@ -30,12 +30,14 @@ class InitialChunkDataPacket(val pos: ChunkPos, val data: List) : } override fun play(connection: ClientConnection) { - val chunk = connection.client.world?.chunkMap?.compute(pos.x, pos.y) ?: return - val itr = data.iterator() + connection.client.mailbox.execute { + val chunk = connection.client.world?.chunkMap?.compute(pos.x, pos.y) ?: return@execute + val itr = data.iterator() - for (x in 0 until CHUNK_SIZE) { - for (y in 0 until CHUNK_SIZE) { - chunk.setCell(x, y, itr.next()) + for (x in 0 until CHUNK_SIZE) { + for (y in 0 until CHUNK_SIZE) { + chunk.setCell(x, y, itr.next()) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt index 9bdd9394..4fd13137 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/ForgetChunkPacket.kt @@ -17,9 +17,8 @@ class ForgetChunkPacket(val pos: ChunkPos) : IClientPacket { } override fun play(connection: ClientConnection) { - val world = connection.client.world ?: return - - world.lock.withLock { + connection.client.mailbox.execute { + val world = connection.client.world ?: return@execute world.chunkMap.remove(pos) world.forEachRenderRegion(pos) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt index fea3b803..12953ef9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/JoinWorldPacket.kt @@ -22,6 +22,8 @@ data class JoinWorldPacket(val uuid: UUID, val seed: Long, val geometry: WorldGe } override fun play(connection: ClientConnection) { - connection.client.world = ClientWorld(connection.client, seed, geometry) + connection.client.mailbox.execute { + connection.client.world = ClientWorld(connection.client, seed, geometry) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt new file mode 100644 index 00000000..860b1591 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/LeaveWorldPacket.kt @@ -0,0 +1,17 @@ +package ru.dbotthepony.kstarbound.client.network.packets + +import ru.dbotthepony.kstarbound.client.network.ClientConnection +import ru.dbotthepony.kstarbound.network.IClientPacket +import java.io.DataOutputStream + +object LeaveWorldPacket : IClientPacket { + override fun write(stream: DataOutputStream) { + + } + + override fun play(connection: ClientConnection) { + connection.client.mailbox.execute { + connection.client.world = null + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt index 58f76406..ef553dae 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/network/packets/SpawnWorldObjectPacket.kt @@ -17,11 +17,10 @@ class SpawnWorldObjectPacket(val data: JsonObject) : IClientPacket { } override fun play(connection: ClientConnection) { - val world = connection.client.world ?: return - val obj = WorldObject.fromJson(data) - - world.mailbox.submit { - val chunk = world.chunkMap[world.geometry.chunkFromCell(obj.pos)] ?: return@submit + connection.client.mailbox.execute { + val world = connection.client.world ?: return@execute + val obj = WorldObject.fromJson(data) + val chunk = world.chunkMap[world.geometry.chunkFromCell(obj.pos)] ?: return@execute chunk.addObject(obj) } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt index 023bba6f..94a1a7f0 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/network/PacketRegistry.kt @@ -10,7 +10,7 @@ import io.netty.channel.ChannelPromise import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import org.apache.logging.log4j.LogManager import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket -import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket +import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket @@ -127,7 +127,7 @@ object PacketRegistry { init { add(::DisconnectPacket) add(::JoinWorldPacket) - add(::InitialChunkDataPacket) + add(::ChunkCellsPacket) add(::ForgetChunkPacket) add(::TrackedPositionPacket) add(::TrackedSizePacket) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt index 6c8dcc1d..16946fa3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/StarboundServer.kt @@ -43,12 +43,7 @@ abstract class StarboundServer(val root: File) : Closeable { fun playerInGame(player: ServerConnection) { val world = worlds.first() - - world.mailbox.execute { - player.world = world - world.players.add(player) - player.send(JoinWorldPacket(world)) - } + world.acceptPlayer(player) } 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 7a029349..543d2a8b 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/network/ServerConnection.kt @@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import org.apache.logging.log4j.LogManager import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket -import ru.dbotthepony.kstarbound.client.network.packets.InitialChunkDataPacket +import ru.dbotthepony.kstarbound.client.network.packets.ChunkCellsPacket import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.ConnectionSide @@ -20,10 +20,6 @@ import java.util.* class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type, UUID(0L, 0L)) { var world: ServerWorld? = null - set(value) { - field = value - needsToRecomputeTrackedChunks = true - } var trackedPosition: Vector2d = Vector2d.ZERO set(value) { @@ -57,6 +53,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn private var needsToRecomputeTrackedChunks = true + fun onLeaveWorld() { + tickets.values.forEach { it.cancel() } + tickets.clear() + sentChunks.clear() + } + private fun recomputeTrackedChunks() { val world = world ?: return val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition) @@ -91,9 +93,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn val world = world if (world == null) { - tickets.values.forEach { it.cancel() } - tickets.clear() - sentChunks.clear() + onLeaveWorld() return } @@ -102,19 +102,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn } for (pos in tickets.keys) { + val chunk = world.chunkMap[pos] ?: continue + if (pos !in sentChunks) { - val chunk = world.chunkMap[pos] + send(ChunkCellsPacket(chunk)) - if (chunk != null) { - send(InitialChunkDataPacket(chunk)) - - chunk.objects.forEach { - send(SpawnWorldObjectPacket(it.serialize())) - } - - sentChunks.add(pos) + chunk.objects.forEach { + send(SpawnWorldObjectPacket(it.serialize())) } + + sentChunks.add(pos) } + + } val itr = sentChunks.iterator() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt index 9904836d..40db2330 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/server/world/ServerWorld.kt @@ -3,19 +3,23 @@ package ru.dbotthepony.kstarbound.server.world import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet -import it.unimi.dsi.fastutil.objects.ObjectArraySet import ru.dbotthepony.kommons.collect.chainOptionalFutures import ru.dbotthepony.kommons.core.KOptional import ru.dbotthepony.kstarbound.Starbound +import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket import ru.dbotthepony.kstarbound.server.StarboundServer import ru.dbotthepony.kstarbound.server.network.ServerConnection +import ru.dbotthepony.kstarbound.util.ExecutionSpinner import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.WorldGeometry +import java.util.Collections import java.util.concurrent.CompletableFuture +import java.util.concurrent.RejectedExecutionException import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.LockSupport import java.util.function.Consumer +import java.util.function.Supplier import kotlin.concurrent.withLock class ServerWorld( @@ -27,10 +31,46 @@ class ServerWorld( server.worlds.add(this) } - val players = ObjectArraySet() + private val internalPlayers = ArrayList() + val players: List = Collections.unmodifiableList(internalPlayers) - val thread = Thread(::runThread, "Starbound Server World $seed") - var isStopped: Boolean = false + private fun doAcceptPlayer(player: ServerConnection): Boolean { + if (player !in internalPlayers) { + internalPlayers.add(player) + player.world?.removePlayer(player) + player.world = this + player.send(JoinWorldPacket(this)) + return true + } + + return false + } + + fun acceptPlayer(player: ServerConnection): CompletableFuture { + try { + return CompletableFuture.supplyAsync(Supplier { doAcceptPlayer(player) }, mailbox) + } catch (err: RejectedExecutionException) { + return CompletableFuture.completedFuture(false) + } + } + + private fun doRemovePlayer(player: ServerConnection): Boolean { + return internalPlayers.remove(player) + } + + fun removePlayer(player: ServerConnection): CompletableFuture { + try { + return CompletableFuture.supplyAsync(Supplier { doRemovePlayer(player) }, mailbox) + } catch (err: RejectedExecutionException) { + return CompletableFuture.completedFuture(false) + } + } + + val spinner = ExecutionSpinner(mailbox, ::spin, Starbound.TICK_TIME_ADVANCE_NANOS) + val thread = Thread(spinner, "Starbound Server World $seed") + + @Volatile + var isClosed: Boolean = false private set init { @@ -44,38 +84,30 @@ class ServerWorld( chunkProviders.add(source) } - @Volatile - private var nextThink = 0L - override fun close() { - super.close() - isStopped = true - } + if (!isClosed) { + super.close() + isClosed = true - fun startThread() { - nextThink = System.nanoTime() - thread.start() - } - - private fun runThread() { - while (!isStopped) { - var diff = System.nanoTime() - nextThink - - while (diff < Starbound.TICK_TIME_ADVANCE_NANOS) { - mailbox.executeQueuedTasks() - diff = System.nanoTime() - nextThink - LockSupport.parkNanos(diff) - diff = System.nanoTime() - nextThink + lock.withLock { + internalPlayers.forEach { + it.world = null + } } - nextThink = System.nanoTime() + LockSupport.unpark(thread) + } + } - try { - think() - } catch (err: Throwable) { - close() - throw err - } + private fun spin(): Boolean { + if (isClosed) return false + + try { + think() + return true + } catch (err: Throwable) { + close() + return false } } @@ -84,7 +116,7 @@ class ServerWorld( override fun thinkInner() { lock.withLock { - players.forEach { it.tick() } + internalPlayers.forEach { it.tick() } ticketLists.removeIf { val valid = it.tick() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ExecutionSpinner.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ExecutionSpinner.kt index 6550a84a..9eb31759 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/util/ExecutionSpinner.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/ExecutionSpinner.kt @@ -5,7 +5,7 @@ import ru.dbotthepony.kstarbound.Starbound import java.util.concurrent.locks.LockSupport import java.util.function.BooleanSupplier -class ExecutionSpinner(private val executor: MailboxExecutorService, private val spinner: BooleanSupplier, private val timeBetweenFrames: Long) : Runnable { +class ExecutionSpinner(private val executor: MailboxExecutorService, private val spinner: BooleanSupplier, private val timeBetweenFrames: Long, val precise: Boolean = false) : Runnable { private var lastRender = System.nanoTime() private var frameRenderTime = 0L private val frameRenderTimes = LongArray(60) { 1L } @@ -46,16 +46,23 @@ class ExecutionSpinner(private val executor: MailboxExecutorService, private val fun spin(): Boolean { var diff = timeUntilNextFrame() - while (diff > 1_000_000L) { - executor.executeQueuedTasks() - diff = timeUntilNextFrame() - if (diff > 1_000_000L) LockSupport.parkNanos(diff - 400_000L) - } + if (precise) { + while (diff > 1_500_000L) { + executor.executeQueuedTasks() + diff = timeUntilNextFrame() + if (diff > 1_500_000L) LockSupport.parkNanos(diff - 400_000L) + } - while (diff > 0L) { - executor.executeQueuedTasks() - diff = timeUntilNextFrame() - if (diff > 0L) Thread.yield() + while (diff > 0L) { + executor.executeQueuedTasks() + diff = timeUntilNextFrame() + } + } else { + while (diff > 0L) { + executor.executeQueuedTasks() + diff = timeUntilNextFrame() + if (diff > 400_000L) LockSupport.parkNanos(diff) + } } val mark = System.nanoTime() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt index 06037e62..ab2fb30c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/WorldGeometry.kt @@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.world import ru.dbotthepony.kommons.core.IStruct2d import ru.dbotthepony.kommons.core.IStruct2f import ru.dbotthepony.kommons.core.IStruct2i +import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kstarbound.io.readVec2i import ru.dbotthepony.kstarbound.io.writeVec2i @@ -21,6 +22,14 @@ data class WorldGeometry(val size: Vector2i, val loopX: Boolean, val loopY: Bool buff.writeBoolean(loopY) } + fun wrap(pos: IStruct2i): Vector2i { + return Vector2i(x.cell(pos.component1()), y.cell(pos.component2())) + } + + fun wrap(pos: IStruct2d): Vector2d { + return Vector2d(x.cell(pos.component1()), y.cell(pos.component2())) + } + fun chunkFromCell(pos: IStruct2i): ChunkPos { return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2())) }