Responsive player tracking
This commit is contained in:
parent
fd583cf2b9
commit
ab516d91a9
@ -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
|
||||||
|
|
||||||
|
@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user