More networking workings
This commit is contained in:
parent
3e2872ea5e
commit
666d746936
@ -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"))
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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) {
|
||||||
|
@ -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())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user