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.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.world.FlyingType
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.network.IPacket
import ru.dbotthepony.kstarbound.network.packets.EntityCreatePacket
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.player.PlayerEntity
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.atomic.AtomicBoolean
import kotlin.collections.ArrayList
// couples ServerWorld and ServerConnection together,
// 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 var isActuallyRemoved = false
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 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)
} catch (err: Throwable) {
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) {
client.send(TileModificationFailurePacket(modifications))
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
// executed on world's thread
fun enqueue(task: ServerWorld.() -> Unit) {
if (!isRemoved.get())
tasks.add(task)
if (!isRemoved.get()) {
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 {
@ -186,6 +204,32 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
}
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() {
if (!client.worldStartAcknowledged)
@ -214,17 +258,19 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
}
run {
var next = tasks.poll()
var next = tasksQueue.poll()
while (next != null && !isRemoved.get()) {
next.invoke(world)
next = tasks.poll()
while (next != null) {
tasks.add(next)
next = tasksQueue.poll()
}
}
tasks.removeIf { it.isDone }
client.playerEntity = world.entities[client.playerID] as? PlayerEntity
val trackingRegions = client.trackingTileRegions()
this.currentlyTrackingRegions = trackingRegions
run {
val newTrackedChunks = ArrayList<ChunkPos>()
@ -278,18 +324,7 @@ class ServerWorldTracker(val world: ServerWorld, val client: ServerConnection, p
if (entityVersions.get(id) == -1L) {
// never networked
val initial = FastByteArrayOutputStream()
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
))
networkEntityForFirstTime(entity)
} else if (entity.networkGroup.upstream.hasChangedSince(entityVersions.get(id))) {
val (data, version) = entity.networkGroup.write(remoteVersion = entityVersions.get(id), isLegacy = client.isLegacy)
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) {
if (isRemoved.compareAndSet(false, true)) {
// erase all tasks just to be sure
tasks.forEach { it.cancel(false) }
tasksQueue.forEach { it.cancel(false) }
tasks.clear()
tasksQueue.clear()
val playerEntity = client.playerEntity

View File

@ -171,6 +171,8 @@ abstract class AbstractEntity : Comparable<AbstractEntity> {
val connection = world.client.activeConnection
// TODO: incomplete
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) {
health.reset()
breakAtPosition(baseDamagePosition, source)
} else {
remove(RemovalReason.DYING)
}
remove(RemovalReason.DYING)
return true
}