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( 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..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 @@ -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 @@ -19,6 +20,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 @@ -626,108 +628,138 @@ class EnergyCableGraph : GraphNodeList() for (pair in itr) { - val (node, relSide) = pair - val side = node.sides[relSide]!! + i++ - 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) { + indicesToRemove.add(i) + livelyNodes.remove(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) { + indicesToRemove.add(i) + livelyNodes.remove(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) } } + recursionLevel-- + + if (isMaster && indicesToRemove.isNotEmpty()) { + val indices = indicesToRemove.iterator(indicesToRemove.lastInt()) + + while (indices.hasPrevious()) { + livelyNodesList.removeAt(indices.previousInt()) + } + + indicesToRemove.clear() + } + return received } 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 {