Move everything to BESubscribeList (and eliminate old, bugged code)

This commit is contained in:
DBotThePony 2023-01-14 22:06:45 +07:00
parent 3c73f809dc
commit 95a9aa72aa
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 244 additions and 157 deletions

View File

@ -19,6 +19,7 @@ import net.minecraft.world.level.block.Block
import net.minecraft.world.phys.shapes.CollisionContext import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.once import ru.dbotthepony.mc.otm.once
import ru.dbotthepony.mc.otm.oncePre
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.shapes.BlockShapes import ru.dbotthepony.mc.otm.shapes.BlockShapes
@ -77,7 +78,7 @@ class BatteryBankBlock : RotatableMatteryBlock(), EntityBlock {
) { ) {
super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston) super.neighborChanged(state, level, pos, neighbour, neighbourPos, movedByPiston)
val blockEntity = level.getBlockEntity(pos) as? BatteryBankBlockEntity ?: return val blockEntity = level.getBlockEntity(pos) as? BatteryBankBlockEntity ?: return
level.once { blockEntity.checkSurroundings(level) } level.oncePre { blockEntity.checkSurroundings() }
} }
companion object { companion object {

View File

@ -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.Decimal
import ru.dbotthepony.mc.otm.core.math.unaryMinus import ru.dbotthepony.mc.otm.core.math.unaryMinus
import ru.dbotthepony.mc.otm.core.nbt.set 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.menu.BatteryBankMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities 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) { override fun setLevel(p_155231_: Level) {
super.setLevel(p_155231_) super.setLevel(p_155231_)
tickOnceServer(this::checkSurroundings) checkSurroundings()
} }
override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> { override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
@ -294,41 +295,23 @@ class BatteryBankBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matte
return super.getCapability(cap,side) return super.getCapability(cap,side)
} }
private var consumingCapability = LazyOptional.empty<IEnergyStorage>() private val consumers = object : BESubscribeList<IEnergyStorage>(this@BatteryBankBlockEntity, ForgeCapabilities.ENERGY) {
override fun test(t: Direction): Boolean {
fun checkSurroundings(level: Level) { return -blockState.getValue(RotatableMatteryBlock.FACING) == t
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)
} }
} }
fun checkSurroundings() = consumers.update((-blockState.getValue(RotatableMatteryBlock.FACING))::equals)
fun tick() { fun tick() {
if (isBlockedByRedstone) if (isBlockedByRedstone)
return return
consumingCapability.ifPresentK { for (it in consumers) {
val (_, maxThroughput) = energy.getDistribution(false) val (_, maxThroughput) = energy.getDistribution(false)
if (maxThroughput.isZero) if (maxThroughput.isZero)
return@ifPresentK continue
val diff = it.receiveEnergy(maxThroughput, true) val diff = it.receiveEnergy(maxThroughput, true)

View File

@ -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.ifHas
import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set 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.menu.EnergyCounterMenu
import ru.dbotthepony.mc.otm.registry.MBlockEntities import ru.dbotthepony.mc.otm.registry.MBlockEntities
import java.lang.ref.WeakReference 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 energyInput = EnergyCounterCap(true)
private val energyOutput = EnergyCounterCap(false) private val energyOutput = EnergyCounterCap(false)
private var inputCapability: LazyOptional<out IEnergyStorage> = LazyOptional.empty() private val inputCapability = object : BESubscribeList<IEnergyStorage>(this@EnergyCounterBlockEntity, ForgeCapabilities.ENERGY) {
private var outputCapability: LazyOptional<out IEnergyStorage> = LazyOptional.empty() override fun test(t: Direction): Boolean {
return t == blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)
}
}
private val outputCapability = object : BESubscribeList<IEnergyStorage>(this@EnergyCounterBlockEntity, ForgeCapabilities.ENERGY) {
override fun test(t: Direction): Boolean {
return t == -blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)
}
}
override fun setLevel(p_155231_: Level) { override fun setLevel(p_155231_: Level) {
super.setLevel(p_155231_) super.setLevel(p_155231_)
@ -137,9 +147,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
override val energyFlow = FlowDirection.input(isInput) override val energyFlow = FlowDirection.input(isInput)
override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
if (inputCapability.isPresent) { val it = inputCapability.first
val it = inputCapability.resolve().get()
if (it != null) {
val diff: Decimal val diff: Decimal
val ioLimit = ioLimit val ioLimit = ioLimit
@ -162,9 +172,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
} }
override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal { override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
if (outputCapability.isPresent) { val it = outputCapability.first
val it = outputCapability.resolve().get()
if (it != null) {
val diff: Decimal val diff: Decimal
val ioLimit = ioLimit val ioLimit = ioLimit
@ -192,9 +202,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
override var batteryLevel: Decimal override var batteryLevel: Decimal
get() { get() {
if (energyFlow.input) { if (energyFlow.input) {
if (outputCapability.isPresent) { val it = outputCapability.first
val it = outputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.batteryLevel return it.batteryLevel
} }
@ -202,9 +212,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
return Decimal(it.energyStored) return Decimal(it.energyStored)
} }
} else { } else {
if (inputCapability.isPresent) { val it = inputCapability.first
val it = inputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.batteryLevel return it.batteryLevel
} }
@ -222,9 +232,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
override val maxBatteryLevel: Decimal override val maxBatteryLevel: Decimal
get() { get() {
if (energyFlow.input) { if (energyFlow.input) {
if (outputCapability.isPresent) { val it = outputCapability.first
val it = outputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.maxBatteryLevel return it.maxBatteryLevel
} }
@ -232,9 +242,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
return Decimal(it.maxEnergyStored) return Decimal(it.maxEnergyStored)
} }
} else { } else {
if (inputCapability.isPresent) { val it = inputCapability.first
val it = inputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.maxBatteryLevel return it.maxBatteryLevel
} }
@ -249,9 +259,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
override val missingPower: Decimal override val missingPower: Decimal
get() { get() {
if (energyFlow.input) { if (energyFlow.input) {
if (outputCapability.isPresent) { val it = outputCapability.first
val it = outputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.missingPower return it.missingPower
} }
@ -259,9 +269,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0)) return Decimal((it.maxEnergyStored - it.energyStored).coerceAtLeast(0))
} }
} else { } else {
if (inputCapability.isPresent) { val it = inputCapability.first
val it = inputCapability.resolve().get()
if (it != null) {
if (it is IMatteryEnergyStorage) { if (it is IMatteryEnergyStorage) {
return it.missingPower return it.missingPower
} }
@ -326,45 +336,9 @@ class EnergyCounterBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Mat
} }
} }
private fun getAndBind(
level: Level,
old: LazyOptional<out IEnergyStorage>,
side: Direction
): LazyOptional<out IEnergyStorage> {
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() { fun checkSurroundings() {
val level = level inputCapability.update((blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION))::equals)
if (isRemoved || level !is ServerLevel) return outputCapability.update((-blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION))::equals)
inputCapability = getAndBind(
level,
inputCapability,
blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)
)
outputCapability = getAndBind(
level,
outputCapability,
-blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)
)
} }
override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> { override fun <T> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {

View File

@ -98,29 +98,6 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
level.oncePre { if (!isRemoved) func.invoke(level) } level.oncePre { if (!isRemoved) func.invoke(level) }
} }
protected fun <T> getAndBind(
old: LazyOptional<T>,
provider: ICapabilityProvider?,
capability: Capability<T>,
side: Direction,
invalidate: Runnable
): LazyOptional<T> {
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 { override fun getDisplayName(): Component {
return customDisplayName ?: defaultDisplayName return customDisplayName ?: defaultDisplayName
} }

View File

@ -27,11 +27,11 @@ import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
import ru.dbotthepony.mc.otm.container.ItemFilter import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.* import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.math.Decimal 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.toIntSafe
import ru.dbotthepony.mc.otm.core.math.unaryMinus import ru.dbotthepony.mc.otm.core.math.unaryMinus
import ru.dbotthepony.mc.otm.core.nbt.map import ru.dbotthepony.mc.otm.core.nbt.map
import ru.dbotthepony.mc.otm.core.nbt.set 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.Graph6Node
import ru.dbotthepony.mc.otm.graph.GraphNodeListener import ru.dbotthepony.mc.otm.graph.GraphNodeListener
import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode import ru.dbotthepony.mc.otm.graph.storage.BasicStorageGraphNode
@ -81,12 +81,14 @@ abstract class AbstractStorageImportExport<T>(
override fun invalidateCaps() { override fun invalidateCaps() {
super.invalidateCaps() super.invalidateCaps()
cell.invalidate() cell.invalidate()
target.invalidate()
valid = false valid = false
} }
override fun reviveCaps() { override fun reviveCaps() {
super.reviveCaps() super.reviveCaps()
cell.revive() cell.revive()
target.revive()
valid = true valid = true
} }
@ -101,21 +103,22 @@ abstract class AbstractStorageImportExport<T>(
if (p_155231_ is ServerLevel) { if (p_155231_ is ServerLevel) {
StorageNetworkGraph.discoverFull(this, cell.storageNode) StorageNetworkGraph.discoverFull(this, cell.storageNode)
tickOnceServer(this::checkSurroundings)
} }
tickOnceServer(this::checkSurroundings)
} }
protected var target: LazyOptional<T> = LazyOptional.empty()
protected abstract val targetCapability: Capability<T> protected abstract val targetCapability: Capability<T>
protected val target by lazy {
object : BESubscribeList<T>(this@AbstractStorageImportExport, targetCapability) {
override fun test(t: Direction): Boolean {
return t == -this@AbstractStorageImportExport.blockState.getValue(RotatableMatteryBlock.FACING_FULL)
}
}
}
fun checkSurroundings() { fun checkSurroundings() {
target = getAndBind( target.update()
target,
level?.getBlockEntity(blockPos + blockState.getValue(RotatableMatteryBlock.FACING_FULL).normal),
targetCapability,
-blockState.getValue(RotatableMatteryBlock.FACING_FULL),
) { tickOnceServer(this::checkSurroundings) }
} }
abstract val filter: ItemFilter abstract val filter: ItemFilter
@ -226,18 +229,18 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState)
nextTick-- nextTick--
if (nextTick <= 0 && target.isPresent && enoughEnergy) { val target = target.firstOrNull()
if (nextTick <= 0 && target != null && enoughEnergy) {
val graph = cell.storageGraph ?: return val graph = cell.storageGraph ?: return
val items = graph.getVirtualComponent(ITEM_STORAGE) val items = graph.getVirtualComponent(ITEM_STORAGE)
val resolved = target.orThrow() if (lastSlot >= target.slots) {
if (lastSlot >= resolved.slots) {
lastSlot = 0 lastSlot = 0
} }
val maxMove = energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, MAX_MOVE_PER_OPERATION, true) 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)) { if (extracted.isEmpty || !filter.match(extracted)) {
lastSlot++ lastSlot++
@ -245,7 +248,7 @@ class StorageImporterBlockEntity(blockPos: BlockPos, blockState: BlockState)
val leftOver = items.insertStack(ItemStackWrapper(extracted), true) val leftOver = items.insertStack(ItemStackWrapper(extracted), true)
if (leftOver.count.toInt() != extracted.count) { 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) energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, extracted.count, false)
items.insertStack(ItemStackWrapper(extracted), false) items.insertStack(ItemStackWrapper(extracted), false)
} else { } else {
@ -335,43 +338,50 @@ class StorageExporterBlockEntity(blockPos: BlockPos, blockState: BlockState) :
nextTick-- nextTick--
if (nextTick <= 0 && target.isPresent && enoughEnergy) { val target = target.firstOrNull()
if (nextTick <= 0 && target != null && enoughEnergy) {
val graph = cell.storageGraph ?: return val graph = cell.storageGraph ?: return
val items = graph.getVirtualComponent(ITEM_STORAGE) val items = graph.getVirtualComponent(ITEM_STORAGE)
val resolved = target.orThrow() if (lastSlot >= target.slots) {
if (lastSlot >= resolved.slots) {
lastSlot = 0 lastSlot = 0
} }
var hit = false var hit = false
for (stack in exportStacks) { for (stack in exportStacks) {
if (!resolved.isItemValid(lastSlot, stack.second.item)) { if (!target.isItemValid(lastSlot, stack.second.item)) {
continue continue
} }
val exportAmountA = items.extractStack(stack.first, stack.second.count.coerceAtMost( val exportAmountA = items.extractStack(
MAX_MOVE_PER_OPERATION stack.first, stack.second.count.coerceAtMost(
), true).count MAX_MOVE_PER_OPERATION
), true
).count
if (exportAmountA == BigInteger.ZERO) { if (exportAmountA == BigInteger.ZERO) {
continue continue
} }
var exportAmount = energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmountA, true).toIntSafe() var exportAmount =
energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmountA, true).toIntSafe()
if (exportAmount == 0) { if (exportAmount == 0) {
break 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) { if (leftover.count != exportAmount) {
hit = true hit = true
exportAmount = items.extractStack(stack.first, (exportAmount - leftover.count).toBigInteger(), false).count.toInt() exportAmount = items.extractStack(
resolved.insertItem(lastSlot, stack.second.stack.also { it.count = exportAmount }, false) 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) energy.extractEnergyExact(ITEM_STORAGE.energyPerOperation, exportAmount, false)
break break
} }

View File

@ -2,23 +2,84 @@ package ru.dbotthepony.mc.otm.core.util
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.SectionPos import net.minecraft.core.SectionPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraftforge.common.capabilities.Capability import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.util.LazyOptional 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.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.math.plus 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.core.orNull
import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.onceServer
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.EnumMap import java.util.EnumMap
import java.util.function.Predicate 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<T>( open class BESubscribeList<T>(
val block: BlockEntity, val block: BlockEntity,
val capability: Capability<T>, val capability: Capability<T>,
val alwaysValidate: Boolean = false
) : Predicate<Direction>, Iterable<T> { ) : Predicate<Direction>, Iterable<T> {
private val knownCaps = WeakHashSet<LazyOptional<T>>() private val knownCaps = WeakHashSet<LazyOptional<T>>()
private val trackedCaps = EnumMap<Direction, WeakReference<LazyOptional<T>>>(Direction::class.java) private val trackedCaps = EnumMap<Direction, WeakReference<LazyOptional<T>>>(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<T> 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 var valid = true
set(value) { set(value) {
@ -48,17 +109,13 @@ open class BESubscribeList<T>(
return true 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<T> { override fun iterator(): Iterator<T> {
if (updateQueued) {
update()
} else if (alwaysValidate) {
validate()
}
return object : Iterator<T> { return object : Iterator<T> {
val iterator = trackedCaps.iterator() val iterator = trackedCaps.iterator()
var value: T? = null var value: T? = null
@ -68,9 +125,13 @@ open class BESubscribeList<T>(
val (k, v) = iterator.next() val (k, v) = iterator.next()
value = v.get()?.orNull() value = v.get()?.orNull()
if (value == null || !test(k)) { if (value == null) {
iterator.remove() iterator.remove()
unsubscribe(k, null) unsubscribe(k, null)
} else if (!test(k)) {
iterator.remove()
unsubscribe(k, null)
updateQueued = true
} }
} }
} }
@ -93,29 +154,99 @@ open class BESubscribeList<T>(
return update(direction::equals) 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 @JvmOverloads
fun update(predicate: Predicate<Direction>? = null) { fun update(predicate: Predicate<Direction>? = null) {
if (!valid) if (!valid || block.isRemoved)
return 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 var waitChunkLoad = false
updateTick++ var stream = Direction.stream()
val updateTick = updateTick
var stream = Direction.stream().filter(this)
if (predicate != null) { if (predicate != null) {
stream = stream.filter(predicate) stream = stream.filter(predicate)
} else {
updateQueued = false
lastDirectionSet = calculateDirectionSet()
} }
stream = stream.filter(this)
firstKey = null
for (dir in stream) { for (dir in stream) {
val pos = block.blockPos + dir val pos = block.blockPos + dir
val getChunk = sorse.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z)) val getChunk = sorse.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))
@ -123,7 +254,7 @@ open class BESubscribeList<T>(
if (getChunk == null) { if (getChunk == null) {
waitChunkLoad = true waitChunkLoad = true
} else { } else {
val cap = getChunk.getBlockEntity(pos)?.getCapability(capability) val cap = getChunk.getBlockEntity(pos)?.getCapability(capability, -dir)
val knownCap = trackedCaps[dir]?.get() val knownCap = trackedCaps[dir]?.get()
if (cap != null && cap.isPresent) { if (cap != null && cap.isPresent) {
@ -147,6 +278,10 @@ open class BESubscribeList<T>(
trackedCaps[dir] = WeakReference(cap) trackedCaps[dir] = WeakReference(cap)
subscribe(dir, cap) subscribe(dir, cap)
} }
if (firstKey == null) {
firstKey = dir
}
} else { } else {
trackedCaps.remove(dir) trackedCaps.remove(dir)
unsubscribe(dir, knownCap) unsubscribe(dir, knownCap)
@ -154,8 +289,15 @@ open class BESubscribeList<T>(
} }
} }
if (waitChunkLoad) { if (waitChunkLoad && SERVER_IS_LIVE) {
onceServer { if (updateTick == this.updateTick) update(predicate) } onceServer { if (isWaitingOnChunk) update() }
isWaitingOnChunk = true
} else if (predicate == null) {
isWaitingOnChunk = false
} }
} }
companion object {
private val VALUES = Direction.values()
}
} }