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 0ec06fe94..1be70138b 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 @@ -31,11 +31,31 @@ class EnergyCableGraph : GraphNodeList, ArrayList>() + private class CacheEntry { + val paths = ArrayList(1) + var saturated = false + } - private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable { - val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.segment.availableThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble()) + // TODO: LRU cache? + private val pathCache = HashMap, CacheEntry>() + + private class SearchNode( + val node: EnergyCableBlockEntity.Node, + target: EnergyCableBlockEntity.Node, + var parent: SearchNode? = null, + previouslySeen: Boolean + ) : Comparable { + val heuristics: Double + + init { + var heuristics = node.position.distSqr(target.position) * 0.0001 + + if (!previouslySeen) { + heuristics -= ln(node.segment.availableThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble()) + } + + this.heuristics = heuristics + } override fun compareTo(other: SearchNode): Int { return heuristics.compareTo(other.heuristics) @@ -51,6 +71,10 @@ class EnergyCableGraph : GraphNodeList): Decimal { if (!amount.isPositive || shortCircuit) { return Decimal.ZERO @@ -79,6 +103,10 @@ class EnergyCableGraph : GraphNodeList) { + segments.forEach { it.refund(amount, simulate, instantSnapshot) } + } + fun remove() { segments.toTypedArray().forEach { it.remove(this) } } @@ -93,6 +121,10 @@ class EnergyCableGraph : GraphNodeList() private val paths = HashSet() + operator fun contains(node: EnergyCableBlockEntity.Node): Boolean { + return node in nodes + } + var throughput = Decimal.ZERO private set @@ -180,6 +212,16 @@ class EnergyCableGraph : GraphNodeList) { + if (simulate) { + if (this in instantSnapshot) { + instantSnapshot[this] = maxOf(instantSnapshot[this]!! - amount, Decimal.ZERO) + } + } else { + transferredLastTick = maxOf(transferredLastTick, Decimal.ZERO) + } + } + fun remove(node: EnergyCableBlockEntity.Node) { check(nodes.remove(node)) { "Tried to remove node $node from segment $this, but that node does not belong to this segment" } @@ -241,23 +283,18 @@ class EnergyCableGraph : GraphNodeList, threshold: Decimal): SegmentPath? { + val seenTop = existing.any { a in it } + + if (seenTop && a.energyThroughput <= threshold) { return null + } - val list = pathCache.computeIfAbsent(a to b) { ArrayList(1) } - - if (list.isNotEmpty()) - return list.first - - // no free paths available, try to find extra one - // while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search), - // on small cable networks simple flooding will do just fine, if we consider overloaded cables as closed flood gates val openNodes = PriorityQueue() val seenNodes = HashSet() - openNodes.add(SearchNode(a, b)) + openNodes.add(SearchNode(a, b, null, seenTop)) while (openNodes.isNotEmpty()) { val first = openNodes.remove() @@ -280,21 +317,46 @@ class EnergyCableGraph : GraphNodeList