Compare commits

...

3 Commits

Author SHA1 Message Date
a9b191eb72
Greatly improve cable network segment slicing and splicing 2025-01-19 22:51:37 +07:00
aa41647c04
Speed up cable Segment splitting 2025-01-19 21:12:59 +07:00
9579bc6a0c
LinkedHashSet instead of ObjectLinkedOpenHashSet for update performance
because LinkedHashSet uses linked list, while ObjectLinkedOpenHashSet use sequential array of keys
2025-01-19 20:56:32 +07:00

View File

@ -1,11 +1,8 @@
package ru.dbotthepony.mc.otm.block.entity.cable package ru.dbotthepony.mc.otm.block.entity.cable
import it.unimi.dsi.fastutil.ints.IntLists
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ReferenceArraySet import it.unimi.dsi.fastutil.objects.ReferenceArraySet
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
@ -19,6 +16,7 @@ import ru.dbotthepony.mc.otm.onceServer
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet import kotlin.collections.HashSet
import kotlin.collections.LinkedHashSet
import kotlin.math.ln import kotlin.math.ln
class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() { class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
@ -69,7 +67,7 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
class SegmentPath(private val a: BlockPos, private val b: BlockPos) { class SegmentPath(private val a: BlockPos, private val b: BlockPos) {
val segments = ObjectLinkedOpenHashSet<Segment>() val segments = LinkedHashSet<Segment>()
private var shortCircuit = false private var shortCircuit = false
private var lastTickTransfers = 0 private var lastTickTransfers = 0
private var lastTick = 0 private var lastTick = 0
@ -134,13 +132,25 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
nodes.add(node) nodes.add(node)
} }
private val nodes = ObjectLinkedOpenHashSet<EnergyCableBlockEntity.Node>() private val nodes = LinkedHashSet<EnergyCableBlockEntity.Node>()
private val paths = ReferenceOpenHashSet<SegmentPath>() private var paths = ReferenceLinkedOpenHashSet<SegmentPath>()
operator fun contains(node: EnergyCableBlockEntity.Node): Boolean { operator fun contains(node: EnergyCableBlockEntity.Node): Boolean {
return node in nodes return node in nodes
} }
fun missingNodes(from: Collection<EnergyCableBlockEntity.Node>): List<EnergyCableBlockEntity.Node> {
val missing = ArrayList<EnergyCableBlockEntity.Node>()
this.nodes.forEach {
if (it !in from) {
missing.add(it)
}
}
return missing
}
var throughput = Decimal.ZERO var throughput = Decimal.ZERO
private set private set
@ -284,9 +294,10 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
fun add(path: SegmentPath) { fun add(path: SegmentPath) {
check(paths.add(path)) { "Path $path shouldn't contain $this" } if (paths.add(path)) {
check(path.segments.add(this)) { "Path set and Segment disagree whenever $this is absent from $path" } check(path.segments.add(this)) { "Path set and Segment disagree whenever $this is absent from $path" }
} }
}
fun remove(path: SegmentPath) { fun remove(path: SegmentPath) {
check(paths.remove(path)) { "Path $path should already contain $this" } check(paths.remove(path)) { "Path $path should already contain $this" }
@ -295,23 +306,23 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
// breaks "instant snapshots" of segments atm // breaks "instant snapshots" of segments atm
// shouldn't cause major gameplay issues though // shouldn't cause major gameplay issues though
fun split(): List<Segment> { fun split(nodes: Collection<EnergyCableBlockEntity.Node>): List<Segment> {
if (nodes.isEmpty()) { for (node in nodes)
throw IllegalStateException("Empty segment somehow?") require(node in this.nodes) { "$node does not belong to $this" }
} else if (nodes.size == 1) {
if (this.nodes.size == 1) {
return listOf(this) return listOf(this)
} else { } else {
lastPoweredStatus = 0
val list = ArrayList(nodes)
val itr = list.iterator()
itr.next()
val result = ArrayList<Segment>() val result = ArrayList<Segment>()
result.add(this) result.add(this)
for (v in itr) { for (v in nodes) {
v.segment = Segment() v.segment = Segment()
v.segment.throughput = v.energyThroughput
v.segment.throughputKnown = true
v.segment.transferredLastTick = transferredLastTick v.segment.transferredLastTick = transferredLastTick
v.segment.paths.addAll(paths) v.segment.lastPoweredStatus = lastPoweredStatus
v.segment.paths = paths.clone()
paths.forEach { it.segments.add(v.segment) } paths.forEach { it.segments.add(v.segment) }
result.add(v.segment) result.add(v.segment)
} }
@ -391,7 +402,7 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
if (first.node === b) { if (first.node === b) {
// solution found // solution found
val solution = ArrayList<EnergyCableBlockEntity.Node>() val solution = LinkedHashSet<EnergyCableBlockEntity.Node>()
var last = first.parent var last = first.parent
solution.add(first.node) solution.add(first.node)
@ -402,10 +413,23 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
val touchedSegments = ReferenceArraySet<Segment>() val touchedSegments = ReferenceArraySet<Segment>()
solution.forEach { touchedSegments.addAll(it.segment.split()) }
for (it in solution) {
if (it.segment in touchedSegments)
continue
val diff = it.segment.missingNodes(solution)
// segment satisfies new constraints
if (diff.isEmpty()) {
touchedSegments.add(it.segment)
} else {
touchedSegments.addAll(it.segment.split(diff))
}
}
val path = SegmentPath(a.blockEntity.blockPos, b.blockEntity.blockPos) val path = SegmentPath(a.blockEntity.blockPos, b.blockEntity.blockPos)
solution.forEach { it.segment.add(path) } solution.forEach { it.segment.add(path) }
touchedSegments.forEach { it.tryCombine(touchedSegments) } touchedSegments.forEach { it.tryCombine(touchedSegments) }
return path return path