From 95a9aa72aa04a3684f44f465dea00525ade43159 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 14 Jan 2023 22:06:45 +0700 Subject: [PATCH] Move everything to BESubscribeList (and eliminate old, bugged code) --- .../mc/otm/block/BatteryBankBlock.kt | 5 +- .../block/entity/BatteryBankBlockEntity.kt | 35 +--- .../block/entity/EnergyCounterBlockEntity.kt | 86 +++----- .../mc/otm/block/entity/MatteryBlockEntity.kt | 23 --- .../block/entity/storage/StorageInterfaces.kt | 66 ++++--- .../mc/otm/core/util/BESubscribeList.kt | 186 +++++++++++++++--- 6 files changed, 244 insertions(+), 157 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt index bb2a49852..7d6051719 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/BatteryBankBlock.kt @@ -19,6 +19,7 @@ import net.minecraft.world.level.block.Block import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.VoxelShape import ru.dbotthepony.mc.otm.once +import ru.dbotthepony.mc.otm.oncePre import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.shapes.BlockShapes @@ -77,7 +78,7 @@ class BatteryBankBlock : RotatableMatteryBlock(), EntityBlock { ) { super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) val blockEntity = level.getBlockEntity(pos) as? BatteryBankBlockEntity ?: return - level.once { blockEntity.checkSurroundings(level) } + level.oncePre { blockEntity.checkSurroundings() } } companion object { @@ -111,4 +112,4 @@ class BatteryBankBlock : RotatableMatteryBlock(), EntityBlock { ) } } -} \ No newline at end of file +} 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 33924d262..da1a32af7 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 @@ -31,6 +31,7 @@ import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.unaryMinus import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.BESubscribeList import ru.dbotthepony.mc.otm.menu.BatteryBankMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities @@ -263,7 +264,7 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte override fun setLevel(p_155231_: Level) { super.setLevel(p_155231_) - tickOnceServer(this::checkSurroundings) + checkSurroundings() } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { @@ -294,41 +295,23 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte return super.getCapability(cap,side) } - private var consumingCapability = LazyOptional.empty() - - fun checkSurroundings(level: Level) { - if (isRemoved) return - - val tile = level.getBlockEntity(blockPos.offset(blockState.getValue(RotatableMatteryBlock.FACING).normal)) - - if (tile == null) { - consumingCapability = LazyOptional.empty() - return - } - - consumingCapability = getAndBind( - old = consumingCapability, - provider = tile, - capability = ForgeCapabilities.ENERGY, - side = -blockState.getValue(RotatableMatteryBlock.FACING) - ) { - @Suppress("name_shadowing") - val level = this.level - - if (level is ServerLevel && !SERVER_IS_LIVE) - checkSurroundings(level) + private val consumers = object : BESubscribeList(this@BatteryBankBlockEntity, ForgeCapabilities.ENERGY) { + override fun test(t: Direction): Boolean { + return -blockState.getValue(RotatableMatteryBlock.FACING) == t } } + fun checkSurroundings() = consumers.update((-blockState.getValue(RotatableMatteryBlock.FACING))::equals) + fun tick() { if (isBlockedByRedstone) return - consumingCapability.ifPresentK { + for (it in consumers) { val (_, maxThroughput) = energy.getDistribution(false) if (maxThroughput.isZero) - return@ifPresentK + continue val diff = it.receiveEnergy(maxThroughput, true) 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 58bbf5a95..19cbaf27d 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 @@ -29,6 +29,7 @@ import ru.dbotthepony.mc.otm.core.nbt.getByteArrayList import ru.dbotthepony.mc.otm.core.nbt.ifHas import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.BESubscribeList import ru.dbotthepony.mc.otm.menu.EnergyCounterMenu import ru.dbotthepony.mc.otm.registry.MBlockEntities import java.lang.ref.WeakReference @@ -125,8 +126,17 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat private val energyInput = EnergyCounterCap(true) private val energyOutput = EnergyCounterCap(false) - private var inputCapability: LazyOptional = LazyOptional.empty() - private var outputCapability: LazyOptional = LazyOptional.empty() + private val inputCapability = object : BESubscribeList(this@EnergyCounterBlockEntity, ForgeCapabilities.ENERGY) { + override fun test(t: Direction): Boolean { + return t == blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) + } + } + + private val outputCapability = object : BESubscribeList(this@EnergyCounterBlockEntity, ForgeCapabilities.ENERGY) { + override fun test(t: Direction): Boolean { + return t == -blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) + } + } override fun setLevel(p_155231_: Level) { super.setLevel(p_155231_) @@ -137,9 +147,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat override val energyFlow = FlowDirection.input(isInput) override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() + val it = inputCapability.first + if (it != null) { val diff: Decimal val ioLimit = ioLimit @@ -162,9 +172,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat } override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() + val it = outputCapability.first + if (it != null) { val diff: Decimal val ioLimit = ioLimit @@ -192,9 +202,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat override var batteryLevel: Decimal get() { if (energyFlow.input) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() + val it = outputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.batteryLevel } @@ -202,9 +212,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat return Decimal(it.energyStored) } } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() + val it = inputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.batteryLevel } @@ -222,9 +232,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat override val maxBatteryLevel: Decimal get() { if (energyFlow.input) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() + val it = outputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.maxBatteryLevel } @@ -232,9 +242,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat return Decimal(it.maxEnergyStored) } } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() + val it = inputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.maxBatteryLevel } @@ -249,9 +259,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat override val missingPower: Decimal get() { if (energyFlow.input) { - if (outputCapability.isPresent) { - val it = outputCapability.resolve().get() + val it = outputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.missingPower } @@ -259,9 +269,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) } } else { - if (inputCapability.isPresent) { - val it = inputCapability.resolve().get() + val it = inputCapability.first + if (it != null) { if (it is IMatteryEnergyStorage) { return it.missingPower } @@ -326,45 +336,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat } } - private fun getAndBind( - level: Level, - old: LazyOptional, - side: Direction - ): LazyOptional { - val ent = level.getBlockEntity(blockPos.offset(side.normal)) ?: return LazyOptional.empty() - val resolve = ent.getEnergySided(-side) - - if (resolve !== old) { - if (resolve.isPresent) { - val weak = WeakReference(this) - - resolve.addListener { - if (SERVER_IS_LIVE) - weak.get()?.checkSurroundings() - } - } - - return resolve - } - - return old - } - fun checkSurroundings() { - val level = level - if (isRemoved || level !is ServerLevel) return - - inputCapability = getAndBind( - level, - inputCapability, - blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - ) - - outputCapability = getAndBind( - level, - outputCapability, - -blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION) - ) + inputCapability.update((blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION))::equals) + outputCapability.update((-blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION))::equals) } override fun getCapability(cap: Capability, side: Direction?): LazyOptional { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index c2ed4d53b..33f761487 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -98,29 +98,6 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc level.oncePre { if (!isRemoved) func.invoke(level) } } - protected fun getAndBind( - old: LazyOptional, - provider: ICapabilityProvider?, - capability: Capability, - side: Direction, - invalidate: Runnable - ): LazyOptional { - val get = provider?.getCapability(capability, side) ?: LazyOptional.empty() - - if (old !== get) { - if (get.isPresent) { - val ref = WeakReference(invalidate) - get.addListener { - ref.get()?.run() - } - } - - return get.cast() - } - - return get.cast() - } - override fun getDisplayName(): Component { return customDisplayName ?: defaultDisplayName } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index acf76caf0..0e5f177a0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -27,11 +27,11 @@ import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.toIntSafe import ru.dbotthepony.mc.otm.core.math.unaryMinus import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.set +import ru.dbotthepony.mc.otm.core.util.BESubscribeList import ru.dbotthepony.mc.otm.graph.Graph6Node import ru.dbotthepony.mc.otm.graph.GraphNodeListener import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode @@ -81,12 +81,14 @@ abstract class AbstractStorageImportExport( override fun invalidateCaps() { super.invalidateCaps() cell.invalidate() + target.invalidate() valid = false } override fun reviveCaps() { super.reviveCaps() cell.revive() + target.revive() valid = true } @@ -101,21 +103,22 @@ abstract class AbstractStorageImportExport( if (p_155231_ is ServerLevel) { StorageNetworkGraph.discoverFull(this, cell.storageNode) + tickOnceServer(this::checkSurroundings) } - - tickOnceServer(this::checkSurroundings) } - protected var target: LazyOptional = LazyOptional.empty() protected abstract val targetCapability: Capability + protected val target by lazy { + object : BESubscribeList(this@AbstractStorageImportExport, targetCapability) { + override fun test(t: Direction): Boolean { + return t == -this@AbstractStorageImportExport.blockState.getValue(RotatableMatteryBlock.FACING_FULL) + } + } + } + fun checkSurroundings() { - target = getAndBind( - target, - level?.getBlockEntity(blockPos + blockState.getValue(RotatableMatteryBlock.FACING_FULL).normal), - targetCapability, - -blockState.getValue(RotatableMatteryBlock.FACING_FULL), - ) { tickOnceServer(this::checkSurroundings) } + target.update() } abstract val filter: ItemFilter @@ -226,18 +229,18 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) nextTick-- - if (nextTick <= 0 && target.isPresent && enoughEnergy) { + val target = target.firstOrNull() + + if (nextTick <= 0 && target != null && enoughEnergy) { val graph = cell.storageGraph ?: return val items = graph.getVirtualComponent(ITEM_STORAGE) - val resolved = target.orThrow() - - if (lastSlot >= resolved.slots) { + if (lastSlot >= target.slots) { lastSlot = 0 } val maxMove = energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, MAX_MOVE_PER_OPERATION, true) - var extracted = resolved.extractItem(lastSlot, maxMove, true) + var extracted = target.extractItem(lastSlot, maxMove, true) if (extracted.isEmpty || !filter.match(extracted)) { lastSlot++ @@ -245,7 +248,7 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState) val leftOver = items.insertStack(ItemStackWrapper(extracted), true) if (leftOver.count.toInt() != extracted.count) { - extracted = resolved.extractItem(lastSlot, extracted.count - leftOver.count.toInt(), false) + extracted = target.extractItem(lastSlot, extracted.count - leftOver.count.toInt(), false) energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, extracted.count, false) items.insertStack(ItemStackWrapper(extracted), false) } else { @@ -335,43 +338,50 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) : nextTick-- - if (nextTick <= 0 && target.isPresent && enoughEnergy) { + val target = target.firstOrNull() + + if (nextTick <= 0 && target != null && enoughEnergy) { val graph = cell.storageGraph ?: return val items = graph.getVirtualComponent(ITEM_STORAGE) - val resolved = target.orThrow() - - if (lastSlot >= resolved.slots) { + if (lastSlot >= target.slots) { lastSlot = 0 } var hit = false for (stack in exportStacks) { - if (!resolved.isItemValid(lastSlot, stack.second.item)) { + if (!target.isItemValid(lastSlot, stack.second.item)) { continue } - val exportAmountA = items.extractStack(stack.first, stack.second.count.coerceAtMost( - MAX_MOVE_PER_OPERATION - ), true).count + val exportAmountA = items.extractStack( + stack.first, stack.second.count.coerceAtMost( + MAX_MOVE_PER_OPERATION + ), true + ).count if (exportAmountA == BigInteger.ZERO) { continue } - var exportAmount = energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmountA, true).toIntSafe() + var exportAmount = + energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmountA, true).toIntSafe() if (exportAmount == 0) { break } - val leftover = resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, true) + val leftover = target.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, true) if (leftover.count != exportAmount) { hit = true - exportAmount = items.extractStack(stack.first, (exportAmount - leftover.count).toBigInteger(), false).count.toInt() - resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, false) + exportAmount = items.extractStack( + stack.first, + (exportAmount - leftover.count).toBigInteger(), + false + ).count.toInt() + target.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, false) energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmount, false) break } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BESubscribeList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BESubscribeList.kt index 8d33f4aed..0d4fc1517 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BESubscribeList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/BESubscribeList.kt @@ -2,23 +2,84 @@ package ru.dbotthepony.mc.otm.core.util import net.minecraft.core.Direction import net.minecraft.core.SectionPos +import net.minecraft.server.level.ServerLevel import net.minecraft.world.level.block.entity.BlockEntity import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.util.LazyOptional +import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.math.unaryMinus import ru.dbotthepony.mc.otm.core.orNull import ru.dbotthepony.mc.otm.onceServer import java.lang.ref.WeakReference import java.util.EnumMap import java.util.function.Predicate +private inline val Direction.flag: Int get() = 1 shl (ordinal + 1) + +/** + * This class allows block entities to track [capability] per block side. + * + * Allowed sides are governed by [test] method, which is open for override. + * + * **IMPORTANT:** Once rules behind [test] are changed, you MUST call [validate] or [update] right away, or expect hard to debug bugs. + * If rule changes can't be tracked in practical way, then flip on [alwaysValidate], although it will come with performance + * penalty (this will cause [iterator] to invoke [validate] on each its invocation). + * + * Subclasses can also override [subscribe] and [unsubscribe] to more finely track changes in neighbours. + * + * **THIS CLASS IS COMPLETELY THREAD UNSAFE**, it is assumed you only ever invoke it on server thread, + * or, at very least, thread where [ServerLevel] resides. + */ open class BESubscribeList( val block: BlockEntity, val capability: Capability, + val alwaysValidate: Boolean = false ) : Predicate, Iterable { private val knownCaps = WeakHashSet>() private val trackedCaps = EnumMap>>(Direction::class.java) + private var updateQueued = false + private var firstKey: Direction? = null + private var lastDirectionSet = 0 + private var isWaitingOnChunk = false + + /** + * First valid capability, this is faster than invoking [iterator] and getting first value from it. + */ + val first: T? get() { + return firstLO.orNull() + } + + /** + * First valid capability as [LazyOptional], this is faster than invoking [iterator] and getting first value from it. + * + * If required, calls [update] + */ + val firstLO: LazyOptional get() { + if (updateQueued) { + update() + } else if (alwaysValidate) { + validate() + } + + for (i in 0 .. 2) { + if (firstKey == null) { + return LazyOptional.empty() + } + + val get = trackedCaps[firstKey]?.get() + + if (get == null || !get.isPresent) { + update() + continue + } + + return get + } + + return LazyOptional.empty() + } var valid = true set(value) { @@ -48,17 +109,13 @@ open class BESubscribeList( return true } - fun validate() { - val iterator = trackedCaps.iterator() - - for ((k, v) in iterator) { - if (v.get() == null || !test(k)) { - unsubscribe(k, null) - } - } - } - override fun iterator(): Iterator { + if (updateQueued) { + update() + } else if (alwaysValidate) { + validate() + } + return object : Iterator { val iterator = trackedCaps.iterator() var value: T? = null @@ -68,9 +125,13 @@ open class BESubscribeList( val (k, v) = iterator.next() value = v.get()?.orNull() - if (value == null || !test(k)) { + if (value == null) { iterator.remove() unsubscribe(k, null) + } else if (!test(k)) { + iterator.remove() + unsubscribe(k, null) + updateQueued = true } } } @@ -93,29 +154,99 @@ open class BESubscribeList( return update(direction::equals) } - private var updateTick = 0 + private fun calculateDirectionSet(): Int { + var set = 0 + + for (value in VALUES) + if (test(value)) + set = set or value.flag + + return set + } /** - * Updates subscription list, searching for new capabilities or "removing" outdated ones + * Checks for one of these conditions to be true: + * * If [test] was determined to be changed during last time [iterator] was invoked AND tracked capability was filtered out due to [test] returning false + * * Performs a fast bitflag intersection test, by calling [test] on all directions and seeking difference between built set and previous set checked during last [update] + * * Tracked capabilities are checked for validity (if references are still valid) * - * [predicate] predicate narrows sides-to-check list, not replaces list generated by this class' [test] + * Once *any* of conditions above end up true, [update] is called and nothing else down the list is checked further. + */ + fun validate() { + check(valid) { "Subscription list is marked as invalid" } + + if (updateQueued) { + update() + return + } + + val new = calculateDirectionSet() + + if (new != lastDirectionSet) { + val intersect = new and lastDirectionSet + val removed = lastDirectionSet and (intersect.inv()) + val added = new and (intersect.inv()) + + for (dir in VALUES) { + if (dir.flag and removed != 0) { + val value1 = trackedCaps.remove(dir) + val value = value1?.get() + + if (value1 != null) { + unsubscribe(dir, value) + } + } + } + + if (added != 0) { + update { it.flag and added != 0 } + } + + lastDirectionSet = new + return + } + + val iterator = trackedCaps.iterator() + var any = 0 + + for ((k, v) in iterator) { + if (v.get() == null) { + unsubscribe(k, null) + any = any or k.flag + } + } + + if (any != 0) { + update { it.flag and any != 0 } + } + } + + /** + * Updates subscription list, searching for new capabilities and "removing" outdated ones. + * + * [predicate] narrows sides-to-check list, not replaces the set generated by this class' [test] */ @JvmOverloads fun update(predicate: Predicate? = null) { - if (!valid) + if (!valid || block.isRemoved) return - val sorse = block.level?.chunkSource ?: return + val level = block.level as? ServerLevel ?: return // don't run on client + val sorse = level.chunkSource var waitChunkLoad = false - updateTick++ - val updateTick = updateTick - var stream = Direction.stream().filter(this) + var stream = Direction.stream() if (predicate != null) { stream = stream.filter(predicate) + } else { + updateQueued = false + lastDirectionSet = calculateDirectionSet() } + stream = stream.filter(this) + firstKey = null + for (dir in stream) { val pos = block.blockPos + dir val getChunk = sorse.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z)) @@ -123,7 +254,7 @@ open class BESubscribeList( if (getChunk == null) { waitChunkLoad = true } else { - val cap = getChunk.getBlockEntity(pos)?.getCapability(capability) + val cap = getChunk.getBlockEntity(pos)?.getCapability(capability, -dir) val knownCap = trackedCaps[dir]?.get() if (cap != null && cap.isPresent) { @@ -147,6 +278,10 @@ open class BESubscribeList( trackedCaps[dir] = WeakReference(cap) subscribe(dir, cap) } + + if (firstKey == null) { + firstKey = dir + } } else { trackedCaps.remove(dir) unsubscribe(dir, knownCap) @@ -154,8 +289,15 @@ open class BESubscribeList( } } - if (waitChunkLoad) { - onceServer { if (updateTick == this.updateTick) update(predicate) } + if (waitChunkLoad && SERVER_IS_LIVE) { + onceServer { if (isWaitingOnChunk) update() } + isWaitingOnChunk = true + } else if (predicate == null) { + isWaitingOnChunk = false } } + + companion object { + private val VALUES = Direction.values() + } }