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, AndroidResearchManager.INSTANCE::syncEvent);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping); 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::onWatch);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onForget); EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onForget);
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerDisconnected); 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() { fun tick() {
synchronizeToPlayers()
if (isBlockedByRedstone) if (isBlockedByRedstone)
return return

View File

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

View File

@ -353,7 +353,6 @@ abstract class MatteryWorkerBlockEntity<JobType : MatteryWorkerBlockEntity.Job>(
fun basicTicker() { fun basicTicker() {
batteryChargeLoop() batteryChargeLoop()
workerLoop() workerLoop()
synchronizeToPlayers()
} }
companion object { 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.ServerStartingEvent
import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.server.ServerStoppingEvent
import org.apache.logging.log4j.LogManager 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.component1
import ru.dbotthepony.mc.otm.core.component2 import ru.dbotthepony.mc.otm.core.component2
import ru.dbotthepony.mc.otm.core.component3 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.BlockEntitySyncPacket
import ru.dbotthepony.mc.otm.network.FieldSynchronizer import ru.dbotthepony.mc.otm.network.FieldSynchronizer
import ru.dbotthepony.mc.otm.network.WorldNetworkChannel import ru.dbotthepony.mc.otm.network.WorldNetworkChannel
import ru.dbotthepony.mc.otm.onceServer
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.LinkedList import java.util.LinkedList
import java.util.WeakHashMap import java.util.WeakHashMap
private data class Subscribers( private data class Subscribers(
val blockEntities: LinkedList<WeakReference<BlockEntity>> = LinkedList(), val blockEntities: LinkedList<WeakReference<SynchronizedBlockEntity>> = LinkedList(),
val players: LinkedList<ServerPlayer> = LinkedList(), val players: LinkedList<ServerPlayer> = LinkedList(),
val level: WeakReference<Level>, val level: WeakReference<Level>,
val chunkPos: Long, val chunkPos: Long,
@ -47,18 +50,28 @@ private data class Subscribers(
} }
} }
fun subscribe(blockEntity: BlockEntity) { fun subscribe(blockEntity: SynchronizedBlockEntity) {
for (value in blockEntities) { val iterator = blockEntities.listIterator()
if (value.get() === blockEntity) {
for (value in iterator) {
val ref = value.get()
if (ref === blockEntity) {
return return
} else if (ref == null) {
iterator.remove()
} }
} }
blockEntities.add(WeakReference(blockEntity)) blockEntities.add(WeakReference(blockEntity))
changeset++ changeset++
onceServer {
blockEntity.synchronizeToPlayers()
}
} }
fun unsubscribe(blockEntity: BlockEntity): Boolean { fun unsubscribe(blockEntity: SynchronizedBlockEntity): Boolean {
val listIterator = blockEntities.listIterator() val listIterator = blockEntities.listIterator()
for (value in 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_) { 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 { init {
synchronizer.defaultEndpoint.markUnused() synchronizer.defaultEndpoint.markUnused()
} }
open val synchronizationRadius: Double = 32.0
override fun setLevel(p_155231_: Level) { override fun setLevel(p_155231_: Level) {
super.setLevel(p_155231_) super.setLevel(p_155231_)
unsubscribe() unsubscribe()
_subCache = null _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 private var _subCache: Subscribers? = null
@ -119,6 +153,8 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
} }
private fun subscribe(): Subscribers { private fun subscribe(): Subscribers {
val level = level
check(level is ServerLevel) { "Invalid realm" }
unsubscribe() unsubscribe()
val subs = playerMap.computeIfAbsent(level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) }) val subs = playerMap.computeIfAbsent(level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) })
subs.subscribe(this) subs.subscribe(this)
@ -130,7 +166,12 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
private var lastSubscriptionChangeset = -1 private var lastSubscriptionChangeset = -1
fun synchronizeToPlayers() { fun synchronizeToPlayers() {
if (synchronizationRadius <= 0.0 || synchronizer.isEmpty) { synchronizeToPlayers(false)
}
protected fun synchronizeToPlayers(callbedBySynchronizer: Boolean) {
if (synchronizer.isEmpty || isRemoved) {
if (callbedBySynchronizer) synchronizer.markClean()
return return
} }
@ -141,35 +182,21 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
val subscription = subscription val subscription = subscription
if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) { if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) {
if (callbedBySynchronizer) synchronizer.markClean()
return return
} }
lastSubscriptionChangeset = subscription.changeset lastSubscriptionChangeset = subscription.changeset
val (xi, yi, zi) = blockPos for (player in subscription.players) {
val bx = xi + 0.5 val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload()
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()
if (payload != null) { 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 * [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want
*/ */
companion object { companion object {
private val playerMap = WeakHashMap<Level, Long2ObjectAVLTreeMap<Subscribers>>() private val playerMap = WeakHashMap<ServerLevel, Long2ObjectAVLTreeMap<Subscribers>>()
fun onServerStopping(event: ServerStoppingEvent) { fun onServerStopping(event: ServerStoppingEvent) {
playerMap.clear() playerMap.clear()
} }
fun onServerStarting(event: ServerStartingEvent) {
playerMap.clear()
}
fun onWatch(event: ChunkWatchEvent.Watch) { 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 { playerMap.computeIfAbsent(event.level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) }).let {
val (blocks, players) = it val (blocks, players) = it
@ -204,6 +227,22 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
if (event.player !in players) { if (event.player !in players) {
players.add(event.player) players.add(event.player)
it.changeset++ 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()) playerMap.get(event.level)?.remove(event.pos.toLong())
} else { } else {
subs.changeset++ 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 nbt["spin_direction"] = spinDirection
} }
override val synchronizationRadius: Double = 256.0
override fun load(tag: CompoundTag) { override fun load(tag: CompoundTag) {
super.load(tag) super.load(tag)
mass = tag.mapIf("mass", ImpreciseFraction::deserializeNBT) ?: BASELINE_MASS mass = tag.mapIf("mass", ImpreciseFraction::deserializeNBT) ?: BASELINE_MASS
@ -223,8 +221,6 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro
} }
fun tick() { fun tick() {
synchronizeToPlayers()
sleepTicks-- sleepTicks--
if (sleepTicks > 0) return if (sleepTicks > 0) return
val level = level as? ServerLevel ?: return val level = level as? ServerLevel ?: return

View File

@ -27,14 +27,6 @@ class MatterCapacitorBankBlock : RotatableMatteryBlock(), EntityBlock {
return MatterCapacitorBankBlockEntity(blockPos, blockState) 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? { override fun getStateForPlacement(context: BlockPlaceContext): BlockState? {
var state = super.getStateForPlacement(context) ?: return null var state = super.getStateForPlacement(context) ?: return null

View File

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