From 69e4747363fd268024e7c2b0dbf4ee28247c17f7 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 29 Mar 2025 20:22:44 +0700 Subject: [PATCH 1/6] Try to prevent recursive energy transfer on cables --- .../entity/cable/EnergyCableBlockEntity.kt | 3 + .../block/entity/cable/EnergyCableGraph.kt | 167 +++++++++--------- 2 files changed, 91 insertions(+), 79 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt index 693ec5090..0aabce3b9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableBlockEntity.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity.cable +import it.unimi.dsi.fastutil.objects.ObjectArraySet import net.minecraft.core.BlockPos import net.minecraft.core.Direction import net.minecraft.world.level.Level @@ -114,6 +115,8 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP val sides get() = energySides + val currentlyTransferringTo = ObjectArraySet>() + override fun onNeighbour(link: Link) { if (link is DirectionLink) { updateBlockState(link.direction, true) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt index 8434ce627..aa754d4a7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -19,6 +19,7 @@ import ru.dbotthepony.mc.otm.core.shuffle import ru.dbotthepony.mc.otm.graph.GraphNodeList import ru.dbotthepony.mc.otm.onceServer import java.util.* +import java.util.concurrent.atomic.AtomicLong import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.collections.LinkedHashSet @@ -635,96 +636,104 @@ class EnergyCableGraph : GraphNodeList() for (pair in itr) { - val (node, relSide) = pair - val side = node.sides[relSide]!! - - if (!side.isEnabled) { - itr.remove() - check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } + // recursion prevention + if (!fromNode.currentlyTransferringTo.add(pair)) continue - } else if (fromNode === node && side.side === fromSide) { - continue - } - val it = side.neighbour.get() + try { + val (node, relSide) = pair + val side = node.sides[relSide]!! - if (it == null || it is EnergyCableBlockEntity.CableSide) { - itr.remove() - check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } - continue - } - - val paths = getPath(fromNode, node, it.receiveEnergy(residue, true), !simulate) - - if (paths.size == 1) { - // Single path, fast scenario - val path = paths[0] - val pathTransferred = path.transfer(residue, simulate, snapshot) - if (pathTransferred <= Decimal.ZERO) continue - val thisReceived = it.receiveEnergy(pathTransferred, simulate) - - // If cable transferred more than machine accepted, then "refund" energy - // so cables record actual value transferred through them - if (thisReceived != pathTransferred) { - path.refund(pathTransferred - thisReceived, simulate, snapshot) - - if (!simulate && thisReceived != Decimal.ZERO) { - path.triggerBlockstateUpdates() - } - } else if (!simulate) { - path.triggerBlockstateUpdates() - } - - received += thisReceived - residue -= thisReceived - if (!residue.isPositive) return received - } else if (paths.size >= 2) { - // Multiple paths, a bit more complicated - // Determine how much machine is likely to accept - val potentiallyAccepted = it.receiveEnergy(residue, true) - - // Won't accept anything - if (potentiallyAccepted <= Decimal.ZERO) continue - - // Now determine combined available throughput - // Make a copy of snapshot, so we can freely write into it - val copy = snapshot.clone() - var calcResidue = potentiallyAccepted - - // TODO: Currently, all transfers cause Braess's paradox, because of greedy selection of "fastest" cable - // Need to implement heuristics to better distribute load across different paths/segments - for (path in paths) { - val passed = path.transfer(calcResidue, true, copy) - calcResidue -= passed - if (calcResidue <= Decimal.ZERO) break - } - - if (calcResidue == potentiallyAccepted) { - // мда + if (!side.isEnabled) { + itr.remove() + check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } + continue + } else if (fromNode === node && side.side === fromSide) { continue } - var thisReceived = it.receiveEnergy(potentiallyAccepted - calcResidue, simulate) - received += thisReceived - residue -= thisReceived + val it = side.neighbour.get() - for (path in paths) { - val passed = path.transfer(thisReceived, simulate, snapshot) + if (it == null || it is EnergyCableBlockEntity.CableSide) { + itr.remove() + check(livelyNodes.remove(pair)) { "Lively nodes Set does not contain $pair" } + continue + } - if (!simulate) + val paths = getPath(fromNode, node, it.receiveEnergy(residue, true), !simulate) + + if (paths.size == 1) { + // Single path, fast scenario + val path = paths[0] + val pathTransferred = path.transfer(residue, simulate, snapshot) + if (pathTransferred <= Decimal.ZERO) continue + val thisReceived = it.receiveEnergy(pathTransferred, simulate) + + // If cable transferred more than machine accepted, then "refund" energy + // so cables record actual value transferred through them + if (thisReceived != pathTransferred) { + path.refund(pathTransferred - thisReceived, simulate, snapshot) + + if (!simulate && thisReceived != Decimal.ZERO) { + path.triggerBlockstateUpdates() + } + } else if (!simulate) { path.triggerBlockstateUpdates() + } - thisReceived -= passed - if (thisReceived <= Decimal.ZERO) break - } - - if (!residue.isPositive) return received - //check(thisReceived <= Decimal.ZERO) { "Путом, алло, Путом, какого чёрта Путом? Путом почему ты заблокировал логику, а Путом?" } - - if (thisReceived > Decimal.ZERO) { - LOGGER.warn("Cable path from $fromNode to $node doesn't follow common sense, disabling.") - paths.forEach { it.shortCircuit = true } + received += thisReceived + residue -= thisReceived + if (!residue.isPositive) return received + } else if (paths.size >= 2) { + // Multiple paths, a bit more complicated + // Determine how much machine is likely to accept + val potentiallyAccepted = it.receiveEnergy(residue, true) + + // Won't accept anything + if (potentiallyAccepted <= Decimal.ZERO) continue + + // Now determine combined available throughput + // Make a copy of snapshot, so we can freely write into it + val copy = snapshot.clone() + var calcResidue = potentiallyAccepted + + // TODO: Currently, all transfers cause Braess's paradox, because of greedy selection of "fastest" cable + // Need to implement heuristics to better distribute load across different paths/segments + for (path in paths) { + val passed = path.transfer(calcResidue, true, copy) + calcResidue -= passed + if (calcResidue <= Decimal.ZERO) break + } + + if (calcResidue == potentiallyAccepted) { + // мда + continue + } + + var thisReceived = it.receiveEnergy(potentiallyAccepted - calcResidue, simulate) + received += thisReceived + residue -= thisReceived + + for (path in paths) { + val passed = path.transfer(thisReceived, simulate, snapshot) + + if (!simulate) + path.triggerBlockstateUpdates() + + thisReceived -= passed + if (thisReceived <= Decimal.ZERO) break + } + + if (!residue.isPositive) return received + //check(thisReceived <= Decimal.ZERO) { "Путом, алло, Путом, какого чёрта Путом? Путом почему ты заблокировал логику, а Путом?" } + + if (thisReceived > Decimal.ZERO) { + LOGGER.warn("Cable path from $fromNode to $node doesn't follow common sense, disabling.") + paths.forEach { it.shortCircuit = true } + } } + } finally { + fromNode.currentlyTransferringTo.remove(pair) } } From 03dfef00c43e4e29bcc1a67ea0b2cc6ce82776b6 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 29 Mar 2025 20:30:38 +0700 Subject: [PATCH 2/6] ??? --- .../otm/block/entity/cable/EnergyCableGraph.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt index aa754d4a7..f837fe360 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -20,6 +20,7 @@ import ru.dbotthepony.mc.otm.graph.GraphNodeList import ru.dbotthepony.mc.otm.onceServer import java.util.* import java.util.concurrent.atomic.AtomicLong +import kotlin.ConcurrentModificationException import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.collections.LinkedHashSet @@ -84,6 +85,9 @@ class EnergyCableGraph : GraphNodeList { } // do nothing ContainsStatus.DOES_NOT_BELONG -> throw IllegalArgumentException("$node does not belong to $this") ContainsStatus.CONTAINS -> { + if (isIteratingNodes) + throw ConcurrentModificationException("Bug trap: currently iterating energy receivers!") + for (dir in RelativeSide.entries) { val pair = node to dir @@ -590,6 +594,9 @@ class EnergyCableGraph : GraphNodeList() + isIteratingNodes = true + for (pair in itr) { // recursion prevention if (!fromNode.currentlyTransferringTo.add(pair)) @@ -737,6 +751,8 @@ class EnergyCableGraph : GraphNodeList Date: Sat, 29 Mar 2025 20:42:14 +0700 Subject: [PATCH 3/6] Handle recursive lively nodes removal gracefully --- .../block/entity/cable/EnergyCableGraph.kt | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt index f837fe360..3f0b335e6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -1,5 +1,6 @@ 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.ReferenceArraySet 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 java.util.* import java.util.concurrent.atomic.AtomicLong -import kotlin.ConcurrentModificationException import kotlin.collections.ArrayList import kotlin.collections.HashSet import kotlin.collections.LinkedHashSet @@ -85,9 +85,6 @@ class EnergyCableGraph : GraphNodeList { } // do nothing ContainsStatus.DOES_NOT_BELONG -> throw IllegalArgumentException("$node does not belong to $this") ContainsStatus.CONTAINS -> { - if (isIteratingNodes) - throw ConcurrentModificationException("Bug trap: currently iterating energy receivers!") - for (dir in RelativeSide.entries) { val pair = node to dir @@ -594,9 +591,6 @@ class EnergyCableGraph : GraphNodeList() - isIteratingNodes = true - for (pair in itr) { + i++ + // recursion prevention if (!fromNode.currentlyTransferringTo.add(pair)) continue @@ -659,8 +656,12 @@ class EnergyCableGraph : GraphNodeList Date: Sat, 29 Mar 2025 20:44:56 +0700 Subject: [PATCH 4/6] i'm with stupid --- .../mc/otm/block/entity/cable/EnergyCableGraph.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt index 3f0b335e6..e5cde9bfe 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/cable/EnergyCableGraph.kt @@ -656,11 +656,7 @@ class EnergyCableGraph : GraphNodeList Date: Sat, 29 Mar 2025 21:08:54 +0700 Subject: [PATCH 5/6] okey --- .../dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) 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 4a4d90f37..ac5458346 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 @@ -358,6 +358,9 @@ abstract class MatteryBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state } override fun get(): T? { + if (isRemoved) + return null + return cache?.capability } @@ -371,13 +374,13 @@ abstract class MatteryBlockEntity(type: BlockEntityType<*>, pos: BlockPos, state if (!SERVER_IS_LIVE) return val level = level as? ServerLevel + val creationVersion = ++currentVersion + if (level == null) { cache = null return } - val creationVersion = ++currentVersion - val direction = blockRotation.side2Dir(side) cache = BlockCapabilityCache.create( From 929049f24c9d7eea6bbda27eb72d29f2f2559aea Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 29 Mar 2025 21:19:37 +0700 Subject: [PATCH 6/6] =?UTF-8?q?YuRaNnNzZZ=20=E2=80=94=2021:17=20=D0=B4?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=BA=D0=BB=D0=B8=D0=BA=20=D1=82=D0=B5=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D1=8C=20=D0=BD=D0=B5=20=D1=81=D0=BE=D0=B1=D0=B8?= =?UTF-8?q?=D1=80=D0=B0=D0=B5=D1=82=20=D0=B2=D1=81=D0=B5=20=D0=B8=D1=82?= =?UTF-8?q?=D0=B5=D0=BC=D1=8B=20=D0=B2=20=D1=8D=D0=BA=D0=B7=D0=BE=D0=BF?= =?UTF-8?q?=D0=B0=D0=BA=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt index 64f4bc51e..b79fce98b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/Slots.kt @@ -77,7 +77,7 @@ open class MatteryMenuSlot(container: Container, index: Int, x: Int = 0, y: Int } open fun canTakeItemForPickAll(): Boolean { - return (container.containerSlotOrNull(slotIndex) as? IFilteredContainerSlot)?.filter == null + return (container.containerSlotOrNull(slotIndex) as? IFilteredContainerSlot)?.filter?.allowAll != false } override fun getMaxStackSize(): Int {