More networking workings

This commit is contained in:
DBotThePony 2024-02-04 14:26:34 +07:00
parent 3e2872ea5e
commit 666d746936
Signed by: DBot
GPG Key ID: DCC23B5715498507
15 changed files with 219 additions and 221 deletions

View File

@ -42,7 +42,7 @@ fun main() {
val client = StarboundClient.create().get() val client = StarboundClient.create().get()
val world = ServerWorld(server, 0L, WorldGeometry(Vector2i(3000, 2000), true, false)) val world = ServerWorld(server, 0L, WorldGeometry(Vector2i(3000, 2000), true, false))
world.addChunkSource(LegacyChunkSource(db)) world.addChunkSource(LegacyChunkSource(db))
world.startThread() world.thread.start()
//Starbound.addFilePath(File("./unpacked_assets/")) //Starbound.addFilePath(File("./unpacked_assets/"))
Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak")) Starbound.addPakPath(File("J:\\Steam\\steamapps\\common\\Starbound\\assets\\packed.pak"))

View File

@ -64,7 +64,7 @@ object Starbound : ISBFileLocator {
const val ENGINE_VERSION = "0.0.1" const val ENGINE_VERSION = "0.0.1"
const val PROTOCOL_VERSION = 1 const val PROTOCOL_VERSION = 1
const val TICK_TIME_ADVANCE = 1.0 / 60.0 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 // compile flags. uuuugh
const val DEDUP_CELL_STATES = true const val DEDUP_CELL_STATES = true

View File

@ -64,6 +64,7 @@ import ru.dbotthepony.kstarbound.client.world.ClientWorld
import ru.dbotthepony.kstarbound.defs.image.Image import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
import ru.dbotthepony.kstarbound.util.formatBytesShort import ru.dbotthepony.kstarbound.util.formatBytesShort
import ru.dbotthepony.kstarbound.world.Direction import ru.dbotthepony.kstarbound.world.Direction
import ru.dbotthepony.kstarbound.world.LightCalculator import ru.dbotthepony.kstarbound.world.LightCalculator
@ -150,8 +151,6 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
var fullbright = true var fullbright = true
var preciseWait = false
var shouldTerminate = false var shouldTerminate = false
private set private set
@ -682,43 +681,7 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA blendFunc = BlendFunc.MULTIPLY_WITH_ALPHA
} }
// nanoseconds val spinner = ExecutionSpinner(mailbox, ::renderFrame, Starbound.TICK_TIME_ADVANCE_NANOS, true)
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 settings = ClientSettings() val settings = ClientSettings()
val viewportCells: ICellAccess = object : ICellAccess { val viewportCells: ICellAccess = object : ICellAccess {
@ -790,8 +753,8 @@ class StarboundClient private constructor(val clientID: Int) : Closeable {
private fun drawPerformanceBasic(onlyMemory: Boolean) { private fun drawPerformanceBasic(onlyMemory: Boolean) {
val runtime = Runtime.getRuntime() val runtime = Runtime.getRuntime()
if (!onlyMemory) font.render("Latency: ${(averageRenderWait * 1_00000.0).toInt() / 100f}ms", scale = 0.4f) if (!onlyMemory) font.render("Latency: ${(spinner.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("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) 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) 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 { private fun renderFrame(): Boolean {
ensureSameThread() if (GLFW.glfwWindowShouldClose(window)) {
close()
var diff = nextRender - System.nanoTime() return false
// 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)
}
} }
val mark = System.nanoTime() val world = world
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)
if (!isRenderingGame) {
executeQueuedTasks() executeQueuedTasks()
GLFW.glfwPollEvents()
if (world != null && Starbound.initialized)
world.think()
return true 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() { private fun spin() {
try { try {
while (!shouldTerminate && renderFrame()) { while (!shouldTerminate && spinner.spin()) {
val ply = activeConnection?.character val ply = activeConnection?.character
if (ply != null) { 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_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_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) { if (input.KEY_ESCAPE_PRESSED) {

View File

@ -17,7 +17,6 @@ import ru.dbotthepony.kstarbound.network.packets.HelloListener
import ru.dbotthepony.kstarbound.network.packets.HelloPacket import ru.dbotthepony.kstarbound.network.packets.HelloPacket
import java.net.SocketAddress import java.net.SocketAddress
import java.util.* import java.util.*
import kotlin.properties.Delegates
// client -> server // client -> server
class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: UUID) : Connection(ConnectionSide.CLIENT, type, uuid) { class ClientConnection(val client: StarboundClient, type: ConnectionType, uuid: UUID) : Connection(ConnectionSide.CLIENT, type, uuid) {

View File

@ -14,7 +14,7 @@ import ru.dbotthepony.kstarbound.world.api.MutableCell
import java.io.DataInputStream import java.io.DataInputStream
import java.io.DataOutputStream import java.io.DataOutputStream
class InitialChunkDataPacket(val pos: ChunkPos, val data: List<ImmutableCell>) : IClientPacket { class ChunkCellsPacket(val pos: ChunkPos, val data: List<ImmutableCell>) : IClientPacket {
constructor(stream: DataInputStream) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() }) constructor(stream: DataInputStream) : this(stream.readChunkPos(), stream.readCollection { MutableCell().read(stream).immutable() })
constructor(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList<ImmutableCell>(CHUNK_SIZE * CHUNK_SIZE).also { constructor(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList<ImmutableCell>(CHUNK_SIZE * CHUNK_SIZE).also {
for (x in 0 until CHUNK_SIZE) { for (x in 0 until CHUNK_SIZE) {
@ -30,12 +30,14 @@ class InitialChunkDataPacket(val pos: ChunkPos, val data: List<ImmutableCell>) :
} }
override fun play(connection: ClientConnection) { override fun play(connection: ClientConnection) {
val chunk = connection.client.world?.chunkMap?.compute(pos.x, pos.y) ?: return connection.client.mailbox.execute {
val itr = data.iterator() 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 (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) { for (y in 0 until CHUNK_SIZE) {
chunk.setCell(x, y, itr.next()) chunk.setCell(x, y, itr.next())
}
} }
} }
} }

View File

@ -17,9 +17,8 @@ class ForgetChunkPacket(val pos: ChunkPos) : IClientPacket {
} }
override fun play(connection: ClientConnection) { override fun play(connection: ClientConnection) {
val world = connection.client.world ?: return connection.client.mailbox.execute {
val world = connection.client.world ?: return@execute
world.lock.withLock {
world.chunkMap.remove(pos) world.chunkMap.remove(pos)
world.forEachRenderRegion(pos) { world.forEachRenderRegion(pos) {

View File

@ -22,6 +22,8 @@ data class JoinWorldPacket(val uuid: UUID, val seed: Long, val geometry: WorldGe
} }
override fun play(connection: ClientConnection) { 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)
}
} }
} }

View File

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

View File

@ -17,11 +17,10 @@ class SpawnWorldObjectPacket(val data: JsonObject) : IClientPacket {
} }
override fun play(connection: ClientConnection) { override fun play(connection: ClientConnection) {
val world = connection.client.world ?: return connection.client.mailbox.execute {
val obj = WorldObject.fromJson(data) val world = connection.client.world ?: return@execute
val obj = WorldObject.fromJson(data)
world.mailbox.submit { val chunk = world.chunkMap[world.geometry.chunkFromCell(obj.pos)] ?: return@execute
val chunk = world.chunkMap[world.geometry.chunkFromCell(obj.pos)] ?: return@submit
chunk.addObject(obj) chunk.addObject(obj)
} }
} }

View File

@ -10,7 +10,7 @@ import io.netty.channel.ChannelPromise
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket 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.JoinWorldPacket
import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket import ru.dbotthepony.kstarbound.client.network.packets.SpawnWorldObjectPacket
import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket import ru.dbotthepony.kstarbound.network.packets.DisconnectPacket
@ -127,7 +127,7 @@ object PacketRegistry {
init { init {
add(::DisconnectPacket) add(::DisconnectPacket)
add(::JoinWorldPacket) add(::JoinWorldPacket)
add(::InitialChunkDataPacket) add(::ChunkCellsPacket)
add(::ForgetChunkPacket) add(::ForgetChunkPacket)
add(::TrackedPositionPacket) add(::TrackedPositionPacket)
add(::TrackedSizePacket) add(::TrackedSizePacket)

View File

@ -43,12 +43,7 @@ abstract class StarboundServer(val root: File) : Closeable {
fun playerInGame(player: ServerConnection) { fun playerInGame(player: ServerConnection) {
val world = worlds.first() val world = worlds.first()
world.acceptPlayer(player)
world.mailbox.execute {
player.world = world
world.players.add(player)
player.send(JoinWorldPacket(world))
}
} }
protected abstract fun close0() protected abstract fun close0()

View File

@ -6,7 +6,7 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.vector.Vector2d import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.client.network.packets.ForgetChunkPacket 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.client.network.packets.SpawnWorldObjectPacket
import ru.dbotthepony.kstarbound.network.Connection import ru.dbotthepony.kstarbound.network.Connection
import ru.dbotthepony.kstarbound.network.ConnectionSide 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)) { class ServerConnection(val server: StarboundServer, type: ConnectionType) : Connection(ConnectionSide.SERVER, type, UUID(0L, 0L)) {
var world: ServerWorld? = null var world: ServerWorld? = null
set(value) {
field = value
needsToRecomputeTrackedChunks = true
}
var trackedPosition: Vector2d = Vector2d.ZERO var trackedPosition: Vector2d = Vector2d.ZERO
set(value) { set(value) {
@ -57,6 +53,12 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
private var needsToRecomputeTrackedChunks = true private var needsToRecomputeTrackedChunks = true
fun onLeaveWorld() {
tickets.values.forEach { it.cancel() }
tickets.clear()
sentChunks.clear()
}
private fun recomputeTrackedChunks() { private fun recomputeTrackedChunks() {
val world = world ?: return val world = world ?: return
val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition) val trackedPositionChunk = world.geometry.chunkFromCell(trackedPosition)
@ -91,9 +93,7 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
val world = world val world = world
if (world == null) { if (world == null) {
tickets.values.forEach { it.cancel() } onLeaveWorld()
tickets.clear()
sentChunks.clear()
return return
} }
@ -102,19 +102,19 @@ class ServerConnection(val server: StarboundServer, type: ConnectionType) : Conn
} }
for (pos in tickets.keys) { for (pos in tickets.keys) {
val chunk = world.chunkMap[pos] ?: continue
if (pos !in sentChunks) { if (pos !in sentChunks) {
val chunk = world.chunkMap[pos] send(ChunkCellsPacket(chunk))
if (chunk != null) { chunk.objects.forEach {
send(InitialChunkDataPacket(chunk)) send(SpawnWorldObjectPacket(it.serialize()))
chunk.objects.forEach {
send(SpawnWorldObjectPacket(it.serialize()))
}
sentChunks.add(pos)
} }
sentChunks.add(pos)
} }
} }
val itr = sentChunks.iterator() val itr = sentChunks.iterator()

View File

@ -3,19 +3,23 @@ package ru.dbotthepony.kstarbound.server.world
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
import it.unimi.dsi.fastutil.objects.ObjectArraySet
import ru.dbotthepony.kommons.collect.chainOptionalFutures import ru.dbotthepony.kommons.collect.chainOptionalFutures
import ru.dbotthepony.kommons.core.KOptional import ru.dbotthepony.kommons.core.KOptional
import ru.dbotthepony.kstarbound.Starbound import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.client.network.packets.JoinWorldPacket
import ru.dbotthepony.kstarbound.server.StarboundServer import ru.dbotthepony.kstarbound.server.StarboundServer
import ru.dbotthepony.kstarbound.server.network.ServerConnection import ru.dbotthepony.kstarbound.server.network.ServerConnection
import ru.dbotthepony.kstarbound.util.ExecutionSpinner
import ru.dbotthepony.kstarbound.world.ChunkPos import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.World import ru.dbotthepony.kstarbound.world.World
import ru.dbotthepony.kstarbound.world.WorldGeometry import ru.dbotthepony.kstarbound.world.WorldGeometry
import java.util.Collections
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.RejectedExecutionException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.function.Consumer import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
class ServerWorld( class ServerWorld(
@ -27,10 +31,46 @@ class ServerWorld(
server.worlds.add(this) server.worlds.add(this)
} }
val players = ObjectArraySet<ServerConnection>() private val internalPlayers = ArrayList<ServerConnection>()
val players: List<ServerConnection> = Collections.unmodifiableList(internalPlayers)
val thread = Thread(::runThread, "Starbound Server World $seed") private fun doAcceptPlayer(player: ServerConnection): Boolean {
var isStopped: Boolean = false 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<Boolean> {
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<Boolean> {
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 private set
init { init {
@ -44,38 +84,30 @@ class ServerWorld(
chunkProviders.add(source) chunkProviders.add(source)
} }
@Volatile
private var nextThink = 0L
override fun close() { override fun close() {
super.close() if (!isClosed) {
isStopped = true super.close()
} isClosed = true
fun startThread() { lock.withLock {
nextThink = System.nanoTime() internalPlayers.forEach {
thread.start() it.world = null
} }
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
} }
nextThink = System.nanoTime() LockSupport.unpark(thread)
}
}
try { private fun spin(): Boolean {
think() if (isClosed) return false
} catch (err: Throwable) {
close() try {
throw err think()
} return true
} catch (err: Throwable) {
close()
return false
} }
} }
@ -84,7 +116,7 @@ class ServerWorld(
override fun thinkInner() { override fun thinkInner() {
lock.withLock { lock.withLock {
players.forEach { it.tick() } internalPlayers.forEach { it.tick() }
ticketLists.removeIf { ticketLists.removeIf {
val valid = it.tick() val valid = it.tick()

View File

@ -5,7 +5,7 @@ import ru.dbotthepony.kstarbound.Starbound
import java.util.concurrent.locks.LockSupport import java.util.concurrent.locks.LockSupport
import java.util.function.BooleanSupplier 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 lastRender = System.nanoTime()
private var frameRenderTime = 0L private var frameRenderTime = 0L
private val frameRenderTimes = LongArray(60) { 1L } private val frameRenderTimes = LongArray(60) { 1L }
@ -46,16 +46,23 @@ class ExecutionSpinner(private val executor: MailboxExecutorService, private val
fun spin(): Boolean { fun spin(): Boolean {
var diff = timeUntilNextFrame() var diff = timeUntilNextFrame()
while (diff > 1_000_000L) { if (precise) {
executor.executeQueuedTasks() while (diff > 1_500_000L) {
diff = timeUntilNextFrame() executor.executeQueuedTasks()
if (diff > 1_000_000L) LockSupport.parkNanos(diff - 400_000L) diff = timeUntilNextFrame()
} if (diff > 1_500_000L) LockSupport.parkNanos(diff - 400_000L)
}
while (diff > 0L) { while (diff > 0L) {
executor.executeQueuedTasks() executor.executeQueuedTasks()
diff = timeUntilNextFrame() diff = timeUntilNextFrame()
if (diff > 0L) Thread.yield() }
} else {
while (diff > 0L) {
executor.executeQueuedTasks()
diff = timeUntilNextFrame()
if (diff > 400_000L) LockSupport.parkNanos(diff)
}
} }
val mark = System.nanoTime() val mark = System.nanoTime()

View File

@ -3,6 +3,7 @@ package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kommons.core.IStruct2d import ru.dbotthepony.kommons.core.IStruct2d
import ru.dbotthepony.kommons.core.IStruct2f import ru.dbotthepony.kommons.core.IStruct2f
import ru.dbotthepony.kommons.core.IStruct2i import ru.dbotthepony.kommons.core.IStruct2i
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.io.readVec2i import ru.dbotthepony.kstarbound.io.readVec2i
import ru.dbotthepony.kstarbound.io.writeVec2i 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) 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 { fun chunkFromCell(pos: IStruct2i): ChunkPos {
return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2())) return ChunkPos(x.chunkFromCell(pos.component1()), y.chunkFromCell(pos.component2()))
} }