More networking workings
This commit is contained in:
parent
3e2872ea5e
commit
666d746936
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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<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(chunk: Chunk<*, *>) : this(chunk.pos, ArrayList<ImmutableCell>(CHUNK_SIZE * CHUNK_SIZE).also {
|
||||
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) {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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<ServerConnection>()
|
||||
private val internalPlayers = ArrayList<ServerConnection>()
|
||||
val players: List<ServerConnection> = 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<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
|
||||
|
||||
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()
|
||||
|
@ -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()
|
||||
|
@ -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()))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user