diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 00abadb01..3cab6a3d4 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -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); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt index 6b90d5f04..69e79dc4c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/BatteryBankBlockEntity.kt @@ -301,8 +301,6 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte } fun tick() { - synchronizeToPlayers() - if (isBlockedByRedstone) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt index 5ef596f5f..fb10ad8ab 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/EnergyCounterBlockEntity.kt @@ -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() { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt index a27fa6efe..e08e04396 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryWorkerBlockEntity.kt @@ -353,7 +353,6 @@ abstract class MatteryWorkerBlockEntity( fun basicTicker() { batteryChargeLoop() workerLoop() - synchronizeToPlayers() } companion object { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt index b56befaeb..2deeae9db 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/SynchronizedBlockEntity.kt @@ -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> = LinkedList(), + val blockEntities: LinkedList> = LinkedList(), val players: LinkedList = LinkedList(), val level: WeakReference, 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>() + private val playerMap = WeakHashMap>() 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) + } + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt index 17d630b87..e89c87820 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/blackhole/BlackHoleBlockEntity.kt @@ -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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt index 1bd56c5a6..7ceaff35d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/matter/MatterCapacitorBankBlock.kt @@ -27,14 +27,6 @@ class MatterCapacitorBankBlock : RotatableMatteryBlock(), EntityBlock { return MatterCapacitorBankBlockEntity(blockPos, blockState) } - override fun getTicker( - p_153212_: Level, - p_153213_: BlockState, - p_153214_: BlockEntityType - ): BlockEntityTicker? { - 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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt index 3ba010266..1251c302e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt @@ -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>() private val observers = LinkedList>() @@ -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 {