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) } }