Try to prevent recursive energy transfer on cables

This commit is contained in:
DBotThePony 2025-03-29 20:22:44 +07:00
parent 3e5f47c9a3
commit 69e4747363
Signed by: DBot
GPG Key ID: DCC23B5715498507
2 changed files with 91 additions and 79 deletions

View File

@ -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<Pair<Node, RelativeSide>>()
override fun onNeighbour(link: Link) {
if (link is DirectionLink) {
updateBlockState(link.direction, true)

View File

@ -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<EnergyCableBlockEntity.Node, EnergyCableG
val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>()
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)
}
}