Handle recursive lively nodes removal gracefully

This commit is contained in:
DBotThePony 2025-03-29 20:42:14 +07:00
parent 03dfef00c4
commit e85bc72ef2
Signed by: DBot
GPG Key ID: DCC23B5715498507

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.block.entity.cable package ru.dbotthepony.mc.otm.block.entity.cable
import it.unimi.dsi.fastutil.ints.IntRBTreeSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ReferenceArraySet import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
@ -20,7 +21,6 @@ import ru.dbotthepony.mc.otm.graph.GraphNodeList
import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.onceServer
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import kotlin.ConcurrentModificationException
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.collections.LinkedHashSet import kotlin.collections.LinkedHashSet
@ -85,9 +85,6 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
ContainsStatus.ABOUT_TO_BE_REMOVED, ContainsStatus.ABOUT_TO_BE_ADDED -> { } // do nothing ContainsStatus.ABOUT_TO_BE_REMOVED, ContainsStatus.ABOUT_TO_BE_ADDED -> { } // do nothing
ContainsStatus.DOES_NOT_BELONG -> throw IllegalArgumentException("$node does not belong to $this") ContainsStatus.DOES_NOT_BELONG -> throw IllegalArgumentException("$node does not belong to $this")
ContainsStatus.CONTAINS -> { ContainsStatus.CONTAINS -> {
if (isIteratingNodes)
throw ConcurrentModificationException("Bug trap: currently iterating energy receivers!")
for (dir in RelativeSide.entries) { for (dir in RelativeSide.entries) {
val pair = node to dir val pair = node to dir
@ -594,9 +591,6 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) { override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
if (isIteratingNodes)
throw ConcurrentModificationException("Bug trap: currently iterating energy receivers!")
for (dir in RelativeSide.entries) { for (dir in RelativeSide.entries) {
val pair = node to dir val pair = node to dir
@ -625,9 +619,6 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) { override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
if (isIteratingNodes)
throw ConcurrentModificationException("Bug trap: currently iterating energy receivers!")
for (dir in RelativeSide.entries) { for (dir in RelativeSide.entries) {
val pair = node to dir val pair = node to dir
check(livelyNodes.add(pair)) check(livelyNodes.add(pair))
@ -637,19 +628,25 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
notifyThroughputsChanged() notifyThroughputsChanged()
} }
private var isIteratingNodes = false private val indicesToRemove = IntRBTreeSet()
private var recursionLevel = 0
fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal { fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal {
livelyNodesList.shuffle(fromNode.blockEntity.level!!.otmRandom) val thisLevel = ++recursionLevel
val isMaster = thisLevel == 1
if (isMaster)
livelyNodesList.shuffle(fromNode.blockEntity.level!!.otmRandom)
var i = -1
val itr = livelyNodesList.iterator() val itr = livelyNodesList.iterator()
var received = Decimal.ZERO var received = Decimal.ZERO
var residue = howMuch.coerceAtMost(fromNode.energyThroughput) var residue = howMuch.coerceAtMost(fromNode.energyThroughput)
val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>() val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>()
isIteratingNodes = true
for (pair in itr) { for (pair in itr) {
i++
// recursion prevention // recursion prevention
if (!fromNode.currentlyTransferringTo.add(pair)) if (!fromNode.currentlyTransferringTo.add(pair))
continue continue
@ -659,8 +656,12 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
val side = node.sides[relSide]!! val side = node.sides[relSide]!!
if (!side.isEnabled) { if (!side.isEnabled) {
itr.remove() if (isMaster)
check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } itr.remove()
else
indicesToRemove.add(i)
livelyNodes.remove(pair)
continue continue
} else if (fromNode === node && side.side === fromSide) { } else if (fromNode === node && side.side === fromSide) {
continue continue
@ -669,8 +670,12 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
val it = side.neighbour.get() val it = side.neighbour.get()
if (it == null || it is EnergyCableBlockEntity.CableSide) { if (it == null || it is EnergyCableBlockEntity.CableSide) {
itr.remove() if (isMaster)
check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } itr.remove()
else
indicesToRemove.add(i)
livelyNodes.remove(pair)
continue continue
} }
@ -751,7 +756,17 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
} }
isIteratingNodes = false recursionLevel--
if (isMaster && indicesToRemove.isNotEmpty()) {
val indices = indicesToRemove.iterator(indicesToRemove.lastInt())
while (indices.hasPrevious()) {
livelyNodesList.removeAt(indices.previousInt())
}
indicesToRemove.clear()
}
return received return received
} }