Responsive player tracking

This commit is contained in:
DBotThePony 2024-04-22 22:40:49 +07:00
parent fd583cf2b9
commit ab516d91a9
Signed by: DBot
GPG Key ID: DCC23B5715498507
3 changed files with 61 additions and 23 deletions

View File

@ -23,6 +23,7 @@ import ru.dbotthepony.kstarbound.defs.WarpAction
import ru.dbotthepony.kstarbound.defs.WorldID import ru.dbotthepony.kstarbound.defs.WorldID
import ru.dbotthepony.kstarbound.defs.tile.TileDamage import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.world.FlyingType import ru.dbotthepony.kstarbound.defs.world.FlyingType
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.network.IPacket import ru.dbotthepony.kstarbound.network.IPacket
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket import ru.dbotthepony.kstarbound.network.packets.EntityDestroyPacket
@ -45,9 +46,11 @@ import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.entities.AbstractEntity import ru.dbotthepony.kstarbound.world.entities.AbstractEntity
import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity import ru.dbotthepony.kstarbound.world.entities.player.PlayerEntity
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.HashMap import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import kotlin.collections.ArrayList
// couples ServerWorld and ServerConnection together, // couples ServerWorld and ServerConnection together,
// allowing ServerConnection client to track ServerWorld state // allowing ServerConnection client to track ServerWorld state
@ -60,7 +63,10 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
private val isRemoved = AtomicBoolean() private val isRemoved = AtomicBoolean()
private var isActuallyRemoved = false private var isActuallyRemoved = false
private val tickets = HashMap<ChunkPos, Ticket>() private val tickets = HashMap<ChunkPos, Ticket>()
private val tasks = ConcurrentLinkedQueue<ServerWorld.() -> Unit>()
private val tasksQueue = ConcurrentLinkedQueue<CompletableFuture<*>>()
private val tasks = ArrayList<CompletableFuture<*>>()
private val entityVersions = Int2LongOpenHashMap() private val entityVersions = Int2LongOpenHashMap()
private val scope = CoroutineScope(world.eventLoop.coroutines + SupervisorJob()) private val scope = CoroutineScope(world.eventLoop.coroutines + SupervisorJob())
@ -87,6 +93,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
world.damageTiles(positions, isBackground, sourcePosition, damage, source, tileModificationBudget) world.damageTiles(positions, isBackground, sourcePosition, damage, source, tileModificationBudget)
} catch (err: Throwable) { } catch (err: Throwable) {
LOGGER.error("Exception in player damage tiles loop", err) LOGGER.error("Exception in player damage tiles loop", err)
client.disconnect("Exception in player damage tiles loop: $err")
} }
} }
} }
@ -110,6 +117,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
} catch (err: Throwable) { } catch (err: Throwable) {
client.send(TileModificationFailurePacket(modifications)) client.send(TileModificationFailurePacket(modifications))
LOGGER.error("Exception in player modify tiles loop", err) LOGGER.error("Exception in player modify tiles loop", err)
client.disconnect("Exception in player modify tiles loop: $err")
} }
} }
} }
@ -128,8 +136,18 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
// packets which interact with world must be // packets which interact with world must be
// executed on world's thread // executed on world's thread
fun enqueue(task: ServerWorld.() -> Unit) { fun enqueue(task: ServerWorld.() -> Unit) {
if (!isRemoved.get()) if (!isRemoved.get()) {
tasks.add(task) tasksQueue.add(world.eventLoop.supplyAsync {
if (!isRemoved.get()) {
try {
task(world)
} catch (err: Throwable) {
LOGGER.error("Exception executing queued player task", err)
client.disconnect("Exception executing queued player task: $err")
}
}
})
}
} }
private inner class Ticket(val ticket: ServerChunk.ITicket, val pos: ChunkPos) : IChunkListener { private inner class Ticket(val ticket: ServerChunk.ITicket, val pos: ChunkPos) : IChunkListener {
@ -186,6 +204,32 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
} }
private var lastSeenFlyingType = FlyingType.NONE private var lastSeenFlyingType = FlyingType.NONE
private var currentlyTrackingRegions: List<AABBi> = listOf()
private fun networkEntityForFirstTime(entity: AbstractEntity) {
val initial = FastByteArrayOutputStream()
entity.writeNetwork(DataOutputStream(initial), client.isLegacy)
val (data, version) = entity.networkGroup.write(isLegacy = client.isLegacy)
entityVersions.put(entity.entityID, version)
send(EntityCreatePacket(
entity.type,
ByteArrayList.wrap(initial.array, initial.length),
data,
entity.entityID
))
}
// called when entities join world, to allow instant tracking
fun evaluateShouldTrack(entity: AbstractEntity) {
if (entity.connectionID == client.connectionID)
return
if (currentlyTrackingRegions.any { it.toDoubleAABB().intersect(entity.metaBoundingBox) }) {
networkEntityForFirstTime(entity)
}
}
fun tick() { fun tick() {
if (!client.worldStartAcknowledged) if (!client.worldStartAcknowledged)
@ -214,17 +258,19 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
} }
run { run {
var next = tasks.poll() var next = tasksQueue.poll()
while (next != null && !isRemoved.get()) { while (next != null) {
next.invoke(world) tasks.add(next)
next = tasks.poll() next = tasksQueue.poll()
} }
} }
tasks.removeIf { it.isDone }
client.playerEntity = world.entities[client.playerID] as? PlayerEntity client.playerEntity = world.entities[client.playerID] as? PlayerEntity
val trackingRegions = client.trackingTileRegions() val trackingRegions = client.trackingTileRegions()
this.currentlyTrackingRegions = trackingRegions
run { run {
val newTrackedChunks = ArrayList<ChunkPos>() val newTrackedChunks = ArrayList<ChunkPos>()
@ -278,18 +324,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
if (entityVersions.get(id) == -1L) { if (entityVersions.get(id) == -1L) {
// never networked // never networked
val initial = FastByteArrayOutputStream() networkEntityForFirstTime(entity)
entity.writeNetwork(DataOutputStream(initial), client.isLegacy)
val (data, version) = entity.networkGroup.write(isLegacy = client.isLegacy)
entityVersions.put(id, version)
send(EntityCreatePacket(
entity.type,
ByteArrayList.wrap(initial.array, initial.length),
data,
entity.entityID
))
} else if (entity.networkGroup.upstream.hasChangedSince(entityVersions.get(id))) { } else if (entity.networkGroup.upstream.hasChangedSince(entityVersions.get(id))) {
val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = client.isLegacy) val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = client.isLegacy)
entityVersions.put(id, version) entityVersions.put(id, version)
@ -338,8 +373,10 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
fun remove(reason: String = "ServerWorldTracker got removed", nullifyVariables: Boolean = true, setReturnWarp: Boolean = true) { fun remove(reason: String = "ServerWorldTracker got removed", nullifyVariables: Boolean = true, setReturnWarp: Boolean = true) {
if (isRemoved.compareAndSet(false, true)) { if (isRemoved.compareAndSet(false, true)) {
// erase all tasks just to be sure tasks.forEach { it.cancel(false) }
tasksQueue.forEach { it.cancel(false) }
tasks.clear() tasks.clear()
tasksQueue.clear()
val playerEntity = client.playerEntity val playerEntity = client.playerEntity

View File

@ -171,6 +171,8 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
val connection = world.client.activeConnection val connection = world.client.activeConnection
// TODO: incomplete // TODO: incomplete
connection?.send(EntityCreatePacket(type, writeNetwork(connection.isLegacy), networkGroup.write(0L, connection.isLegacy).first, entityID)) connection?.send(EntityCreatePacket(type, writeNetwork(connection.isLegacy), networkGroup.write(0L, connection.isLegacy).first, entityID))
} else if (world is ServerWorld) {
world.clients.forEach { it.evaluateShouldTrack(this) }
} }
} }

View File

@ -714,10 +714,9 @@ class PlantEntity() : TileEntity() {
if (fallsWhenDead) { if (fallsWhenDead) {
health.reset() health.reset()
breakAtPosition(baseDamagePosition, source) breakAtPosition(baseDamagePosition, source)
} else {
remove(RemovalReason.DYING)
} }
remove(RemovalReason.DYING)
return true return true
} }