Synchronized block entity no longer need polling

Fixes #106
This commit is contained in:
DBotThePony 2022-10-03 09:55:50 +07:00
parent 1b0fc4bf2e
commit 8f6af0061a
Signed by: DBot
GPG Key ID: DCC23B5715498507
8 changed files with 114 additions and 54 deletions

View File

@ -141,7 +141,6 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStarting);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onWatch);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onForget);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerDisconnected);

View File

@ -301,8 +301,6 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
}
fun tick() {
synchronizeToPlayers()
if (isBlockedByRedstone)
return

View File

@ -394,7 +394,6 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
lastTick = history[historyTick]
historyTick = (historyTick + 1) % history.size
history[historyTick] = ImpreciseFraction.ZERO
synchronizeToPlayers()
}
fun clientTick() {

View File

@ -353,7 +353,6 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
fun basicTicker() {
batteryChargeLoop()
workerLoop()
synchronizeToPlayers()
}
companion object {

View File

@ -19,6 +19,8 @@ import net.minecraftforge.event.level.ChunkWatchEvent
import net.minecraftforge.event.server.ServerStartingEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.core.component1
import ru.dbotthepony.mc.otm.core.component2
import ru.dbotthepony.mc.otm.core.component3
@ -26,12 +28,13 @@ import ru.dbotthepony.mc.otm.core.position
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.WorldNetworkChannel
import ru.dbotthepony.mc.otm.onceServer
import java.lang.ref.WeakReference
import java.util.LinkedList
import java.util.WeakHashMap
private data class Subscribers(
val blockEntities: LinkedList<WeakReference<BlockEntity>> = LinkedList(),
val blockEntities: LinkedList<WeakReference<SynchronizedBlockEntity>> = LinkedList(),
val players: LinkedList<ServerPlayer> = LinkedList(),
val level: WeakReference<Level>,
val chunkPos: Long,
@ -47,18 +50,28 @@ private data class Subscribers(
}
}
fun subscribe(blockEntity: BlockEntity) {
for (value in blockEntities) {
if (value.get() === blockEntity) {
fun subscribe(blockEntity: SynchronizedBlockEntity) {
val iterator = blockEntities.listIterator()
for (value in iterator) {
val ref = value.get()
if (ref === blockEntity) {
return
} else if (ref == null) {
iterator.remove()
}
}
blockEntities.add(WeakReference(blockEntity))
changeset++
onceServer {
blockEntity.synchronizeToPlayers()
}
}
fun unsubscribe(blockEntity: BlockEntity): Boolean {
fun unsubscribe(blockEntity: SynchronizedBlockEntity): Boolean {
val listIterator = blockEntities.listIterator()
for (value in listIterator) {
@ -88,18 +101,39 @@ private data class Subscribers(
}
abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_) {
val synchronizer = FieldSynchronizer()
val synchronizer = FieldSynchronizer {
if (!isRemoved && level?.isClientSide == false) {
onceServer {
synchronizeToPlayers(true)
}
}
}
init {
synchronizer.defaultEndpoint.markUnused()
}
open val synchronizationRadius: Double = 32.0
override fun setLevel(p_155231_: Level) {
super.setLevel(p_155231_)
unsubscribe()
_subCache = null
if (!p_155231_.isClientSide) {
subscribe()
}
}
override fun setRemoved() {
super.setRemoved()
unsubscribe()
}
override fun clearRemoved() {
super.clearRemoved()
if (level?.isClientSide == false) {
subscribe()
}
}
private var _subCache: Subscribers? = null
@ -119,6 +153,8 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
}
private fun subscribe(): Subscribers {
val level = level
check(level is ServerLevel) { "Invalid realm" }
unsubscribe()
val subs = playerMap.computeIfAbsent(level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) })
subs.subscribe(this)
@ -130,7 +166,12 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
private var lastSubscriptionChangeset = -1
fun synchronizeToPlayers() {
if (synchronizationRadius <= 0.0 || synchronizer.isEmpty) {
synchronizeToPlayers(false)
}
protected fun synchronizeToPlayers(callbedBySynchronizer: Boolean) {
if (synchronizer.isEmpty || isRemoved) {
if (callbedBySynchronizer) synchronizer.markClean()
return
}
@ -141,35 +182,21 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
val subscription = subscription
if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) {
if (callbedBySynchronizer) synchronizer.markClean()
return
}
lastSubscriptionChangeset = subscription.changeset
val (xi, yi, zi) = blockPos
val bx = xi + 0.5
val by = yi + 0.5
val bz = zi + 0.5
val double = synchronizationRadius * synchronizationRadius
subscription.players.stream().filter {
if (!it.isAlive) {
return@filter false
}
val (x, y, z) = it.position
(x - bx) * (x - bx) +
(y - by) * (y - by) +
(z - bz) * (z - bz) <= double
}.forEach {
val payload = synchronizer.computeEndpointFor(it).collectNetworkPayload()
for (player in subscription.players) {
val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload()
if (payload != null) {
WorldNetworkChannel.send(it, BlockEntitySyncPacket(blockPos, payload.array, payload.length))
WorldNetworkChannel.send(player, BlockEntitySyncPacket(blockPos, payload.array, payload.length))
}
}
synchronizer.markClean()
}
/**
@ -187,16 +214,12 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
* [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want
*/
companion object {
private val playerMap = WeakHashMap<Level, Long2ObjectAVLTreeMap<Subscribers>>()
private val playerMap = WeakHashMap<ServerLevel, Long2ObjectAVLTreeMap<Subscribers>>()
fun onServerStopping(event: ServerStoppingEvent) {
playerMap.clear()
}
fun onServerStarting(event: ServerStartingEvent) {
playerMap.clear()
}
fun onWatch(event: ChunkWatchEvent.Watch) {
playerMap.computeIfAbsent(event.level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) }).let {
val (blocks, players) = it
@ -204,6 +227,22 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
if (event.player !in players) {
players.add(event.player)
it.changeset++
onceServer(20) {
if (!event.player.hasDisconnected()) {
val iterator = blocks.listIterator()
for (block in iterator) {
val deref = block.get()
if (deref == null) {
iterator.remove()
} else {
deref.synchronizeToPlayers(false)
}
}
}
}
}
}
}
@ -216,6 +255,18 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
playerMap.get(event.level)?.remove(event.pos.toLong())
} else {
subs.changeset++
val iterator = subs.blockEntities.listIterator()
for (block in iterator) {
val deref = block.get()
if (deref == null) {
iterator.remove()
} else {
deref.synchronizer.removeEndpointFor(event.player)
}
}
}
}
}

View File

@ -167,8 +167,6 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro
nbt["spin_direction"] = spinDirection
}
override val synchronizationRadius: Double = 256.0
override fun load(tag: CompoundTag) {
super.load(tag)
mass = tag.mapIf("mass", ImpreciseFraction::deserializeNBT) ?: BASELINE_MASS
@ -223,8 +221,6 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro
}
fun tick() {
synchronizeToPlayers()
sleepTicks--
if (sleepTicks > 0) return
val level = level as? ServerLevel ?: return

View File

@ -27,14 +27,6 @@ class MatterCapacitorBankBlock : RotatableMatteryBlock(), EntityBlock {
return MatterCapacitorBankBlockEntity(blockPos, blockState)
}
override fun <T : BlockEntity> getTicker(
p_153212_: Level,
p_153213_: BlockState,
p_153214_: BlockEntityType<T>
): BlockEntityTicker<T>? {
return SynchronizedBlockEntity::synchronizeToPlayers.blockServerTicker(p_153212_, MBlockEntities.MATTER_CAPACITOR_BANK, p_153214_)
}
override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
var state = super.getStateForPlacement(context) ?: return null

View File

@ -69,7 +69,10 @@ enum class MapAction {
CLEAR, ADD, REMOVE
}
class FieldSynchronizer {
class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCallback: Boolean) {
constructor() : this(Runnable {}, false)
constructor(callback: Runnable) : this(callback, false)
private val fields = ArrayList<IField<*>>()
private val observers = LinkedList<IField<*>>()
@ -77,7 +80,23 @@ class FieldSynchronizer {
val isNotEmpty: Boolean get() = fields.isNotEmpty()
var hasChanges: Boolean = false
private set
private set(value) {
if (value != field) {
field = value
if (value && !alwaysCallCallback) {
callback.run()
}
}
if (alwaysCallCallback && value) {
callback.run()
}
}
fun markClean() {
hasChanges = false
}
fun byte(
value: Byte = 0,
@ -345,6 +364,10 @@ class FieldSynchronizer {
return boundEndpoints.computeIfAbsent(obj) { Endpoint() }
}
fun removeEndpointFor(obj: Any): Endpoint? {
return boundEndpoints.remove(obj)
}
fun endpointFor(obj: Any): Endpoint? {
return boundEndpoints[obj]
}
@ -820,8 +843,11 @@ class FieldSynchronizer {
* [defaultEndpoint]#collectNetworkPayload
*/
fun collectNetworkPayload(): FastByteArrayOutputStream? {
check(!defaultEndpoint.unused) { "Default endpoint is not used" }
observe()
return defaultEndpoint.collectNetworkPayload()
val values = defaultEndpoint.collectNetworkPayload()
markClean()
return values
}
fun applyNetworkPayload(stream: DataInputStream): Int {