From 93d4329acd596759c9de3d5efc6efb5d3f0f95a9 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sun, 6 Aug 2023 01:39:16 +0700 Subject: [PATCH] Refine graphs logic, make neighbouring be not limited on in-world directions, properly add freshly created nodes to graph after they have been properly initialized --- .../dbotthepony/mc/otm/GlobalEventHandler.kt | 5 +- .../dbotthepony/mc/otm/block/entity/Cables.kt | 40 ++- .../entity/storage/StorageBusBlockEntity.kt | 20 +- .../block/entity/storage/StorageInterfaces.kt | 28 +- .../mc/otm/graph/Abstract6Graph.kt | 172 ----------- .../ru/dbotthepony/mc/otm/graph/Graph6Node.kt | 268 ---------------- .../ru/dbotthepony/mc/otm/graph/GraphNode.kt | 292 ++++++++++++++++++ .../dbotthepony/mc/otm/graph/GraphNodeList.kt | 199 ++++++++++++ .../mc/otm/graph/matter/MatterGraph.kt | 4 +- .../mc/otm/graph/matter/MatterNode.kt | 4 +- .../mc/otm/graph/storage/StorageGraph.kt | 15 +- .../mc/otm/graph/storage/StorageNode.kt | 11 +- 12 files changed, 558 insertions(+), 500 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index eec7a0839..2d66cbc24 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -16,12 +16,11 @@ import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.fml.loading.FMLLoader import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage -import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.core.util.IConditionalTickable import ru.dbotthepony.mc.otm.core.util.ITickable import ru.dbotthepony.mc.otm.core.util.TickList -import ru.dbotthepony.mc.otm.graph.Abstract6Graph +import ru.dbotthepony.mc.otm.graph.GraphNodeList import ru.dbotthepony.mc.otm.network.MatteryNetworkChannel import java.util.* @@ -150,7 +149,7 @@ fun onServerTick(event: ServerTickEvent) { } else { postServerTick.tick() // чтоб не плодить кучу подписчиков, вызовем напрямую отсюда - Abstract6Graph.tick() + GraphNodeList.tick() AbstractProfiledStorage.onServerPostTick() MatteryNetworkChannel.onServerPostTick() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt index 12d498cf7..9301f3efc 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/Cables.kt @@ -15,18 +15,22 @@ import ru.dbotthepony.mc.otm.registry.MBlockEntities class MatterCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : MatteryBlockEntity(MBlockEntities.MATTER_CABLE, p_155229_, p_155230_) { val matterNode = object : MatterNode() { - override fun onNeighbour(direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, true) + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - override fun onUnNeighbour(direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } @@ -50,18 +54,22 @@ class StorageCableBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Matt override fun attachComponents(to: StorageGraph) {} override fun removeComponents(from: StorageGraph) {} - override fun onNeighbour(direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, true) + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== blockState && SERVER_IS_LIVE) - level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== blockState && SERVER_IS_LIVE) + level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - override fun onUnNeighbour(direction: Direction) { - val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== blockState && SERVER_IS_LIVE) - level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== blockState && SERVER_IS_LIVE) + level!!.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt index 3153d60da..2e5635e39 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageBusBlockEntity.kt @@ -69,18 +69,22 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter } val cell: StorageNode = object : StorageNode(energy) { - override fun onNeighbour(direction: Direction) { - val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, true) + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } - override fun onUnNeighbour(direction: Direction) { - val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + val newState = this@StorageBusBlockEntity.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@StorageBusBlockEntity.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt index 547ece08f..b6b548c32 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StorageInterfaces.kt @@ -58,24 +58,28 @@ abstract class AbstractStorageImportExport( } val cell: StorageNode = object : StorageNode(energy) { - override fun onNeighbour(direction: Direction) { - level?.once { - if (!isRemoved) { - val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, true) + override fun onNeighbour(link: Link) { + if (link is DirectionLink) { + level?.once { + if (!isRemoved) { + val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, true) - if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } } - override fun onUnNeighbour(direction: Direction) { - level?.once { - if (!isRemoved) { - val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, false) + override fun onUnNeighbour(link: Link) { + if (link is DirectionLink) { + level?.once { + if (!isRemoved) { + val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[link.direction]!!, false) - if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) - level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + if (newState !== this@AbstractStorageImportExport.blockState && SERVER_IS_LIVE) + level?.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) + } } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt deleted file mode 100644 index 6999a110e..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Abstract6Graph.kt +++ /dev/null @@ -1,172 +0,0 @@ -package ru.dbotthepony.mc.otm.graph - -import ru.dbotthepony.mc.otm.core.util.IConditionalTickable -import ru.dbotthepony.mc.otm.core.util.ITickable -import java.lang.ref.WeakReference -import java.util.* -import kotlin.collections.ArrayList - -open class Abstract6Graph, G : Abstract6Graph> : IConditionalTickable { - private val nodesInternal = ArrayList() - private val conditional = ArrayList() - private val always = ArrayList() - - protected val nodes: List = Collections.unmodifiableList(nodesInternal) - - val size get() = nodesInternal.size - - var isMerged = false - private set - - private var isTicking = false - - /** - * Allows storing arbitrary data by external code - */ - val userData = HashMap() - - open fun onNodeRemoved(node: N) {} - open fun onNodeAdded(node: N) {} - open fun onMergedInto(other: G) {} - - protected open fun innerTick(): Boolean { - return false - } - - final override fun tick(): Boolean { - if (isMerged) - return false - - // позволяет вершинам изменять список тикающих вершин - for (i in conditional.size - 1 downTo 0) { - val node = conditional[i] - - if (!node.tick()) { - conditional.removeAt(i) - } - } - - // позволяет вершинам изменять список тикающих вершин - for (node in always.size - 1 downTo 0) { - always[node].tick() - } - - return innerTick() || always.isNotEmpty() || conditional.isNotEmpty() - } - - fun addNode(node: N): Boolean { - if (node in nodesInternal) - return false - - nodesInternal.add(node) - - if (node is IConditionalTickable) { - conditional.add(node) - - if (!isTicking) { - isTicking = true - next.add(WeakReference(this)) - } - } else if (node is ITickable) { - always.add(node) - - if (!isTicking) { - isTicking = true - next.add(WeakReference(this)) - } - } - - onNodeAdded(node) - return true - } - - fun removeNode(node: N): Boolean { - if (!nodesInternal.remove(node)) - return false - - nodesInternal.remove(node) - - if (node is IConditionalTickable) - conditional.remove(node) - else if (node is ITickable) - always.remove(node) - - onNodeRemoved(node) - return true - } - - fun retain(nodes: Set) { - for (i in this.nodesInternal.size - 1 downTo 0) { - if (this.nodesInternal[i] !in nodes) { - val node = this.nodesInternal[i] - - this.nodesInternal.removeAt(i) - - if (node is IConditionalTickable) - conditional.remove(node) - else if (node is ITickable) - always.remove(node) - - onNodeRemoved(node) - } - } - } - - fun merge(other: G, setter: (N, G) -> Unit): G { - if (other === this) - return this - - if (size >= other.size) { - for (node in other.nodesInternal) { - nodesInternal.add(node) - setter.invoke(node, this as G) - onNodeAdded(node) - - if (node is IConditionalTickable) { - conditional.add(node) - - if (!isTicking) { - isTicking = true - next.add(WeakReference(this)) - } - } else if (node is ITickable) { - always.add(node) - - if (!isTicking) { - isTicking = true - next.add(WeakReference(this)) - } - } - } - - other.isMerged = true - other.onMergedInto(this as G) - return this - } else { - return other.merge(this as G, setter) - } - } - - companion object { - private val graphs = ArrayList>>() - private val next = ArrayList>>() - - fun tick() { - if (next.isNotEmpty()) { - graphs.addAll(next) - next.clear() - } - - val iterator = graphs.iterator() - - for (value in iterator) { - val graph = value.get() - - if (graph == null || !graph.tick()) { - graph?.isTicking = false - iterator.remove() - } - } - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt deleted file mode 100644 index 9c0ff9ebe..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/Graph6Node.kt +++ /dev/null @@ -1,268 +0,0 @@ -package ru.dbotthepony.mc.otm.graph - -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet -import net.minecraft.core.BlockPos -import net.minecraft.core.Direction -import net.minecraft.core.SectionPos -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.level.block.entity.BlockEntity -import net.minecraftforge.common.capabilities.Capability -import ru.dbotthepony.mc.otm.addTicker -import ru.dbotthepony.mc.otm.core.math.plus -import ru.dbotthepony.mc.otm.core.math.unaryMinus -import ru.dbotthepony.mc.otm.core.orNull -import java.util.EnumMap -import java.util.concurrent.atomic.AtomicInteger - -open class Graph6Node, G : Abstract6Graph>(val graphFactory: () -> G) { - private val neighbours = EnumMap(Direction::class.java) - - var graph: G = graphFactory.invoke() - private set - - init { - graph.addNode(this as N) - } - - private var seen: Int = 0 - - operator fun get(direction: Direction): N? = neighbours[direction] - - operator fun set(direction: Direction, node: N?) { - set(direction, node, false) - } - - fun set(direction: Direction, node: N?, allowReplacement: Boolean) { - check(isValid) { "Can not neighbour any node while this node is invalid" } - - val old = neighbours[direction] - if (old === node) return - - if (old != null) - breakConnection(this as N, old, direction) - - if (node != null) { - require(node.isValid) { "Can not neighbour invalid node" } - - val opposite = -direction - - if (allowReplacement) { - node.neighbours[opposite]?.let { - breakConnection(node, it, opposite) - } - - check(node.neighbours[opposite] == null) { "$node didn't break connection at direction $opposite" } - } else { - check(node.neighbours[opposite] == null) { "Trying to form connection from $this to $node at direction $direction, but $node already has neighbour at $opposite (${node.neighbours[opposite]})!" } - } - - node.neighbours[opposite] = this as N - neighbours[direction] = node - node.graph.merge(graph, setter) - node.onNeighbour(opposite) - onNeighbour(direction) - } else { - neighbours.remove(direction) - } - } - - var top: N? - get() = neighbours[Direction.UP] - set(value) { - set(Direction.UP, value) - } - - var bottom: N? - get() = neighbours[Direction.DOWN] - set(value) { - set(Direction.DOWN, value) - } - - var south: N? - get() = neighbours[Direction.SOUTH] - set(value) { - set(Direction.SOUTH, value) - } - - var north: N? - get() = neighbours[Direction.NORTH] - set(value) { - set(Direction.NORTH, value) - } - - var west: N? - get() = neighbours[Direction.WEST] - set(value) { - set(Direction.WEST, value) - } - - var east: N? - get() = neighbours[Direction.EAST] - set(value) { - set(Direction.EAST, value) - } - - open fun onNeighbour(direction: Direction) {} - open fun onUnNeighbour(direction: Direction) {} - - protected open fun invalidate() {} - protected open fun revive() {} - - var isValid: Boolean = true - set(value) { - if (value == field) return - field = value - - if (!value) { - val neighbours = ArrayList(neighbours.entries) - - for ((dir, node) in neighbours) { - breakConnection(this as N, node, dir) - } - - graph.removeNode(this as N) - invalidate() - } else { - revive() - } - } - - fun discover( - level: ServerLevel, - blockPos: BlockPos, - nodeGetter: (BlockEntity) -> N? - ) { - if (!isValid) return - - level.addTicker { - isValid && !discoverStep(level, blockPos, nodeGetter) - } - } - - fun discover( - level: ServerLevel, - blockPos: BlockPos, - capability: Capability - ) { - if (!isValid) return - - level.addTicker { - isValid && !discoverStep(level, blockPos) { it.getCapability(capability).orNull() } - } - } - - fun discover(blockEntity: BlockEntity, nodeGetter: (BlockEntity) -> N?) { - discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, nodeGetter) - } - - fun discover(blockEntity: BlockEntity, capability: Capability) { - discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, capability) - } - - fun discoverStep( - level: ServerLevel, - blockPos: BlockPos, - nodeGetter: (BlockEntity) -> N?, - ): Boolean { - if (!isValid) return false - var fullDiscovery = true - - for (dir in directions) { - val offset = blockPos + dir - val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(offset.x), SectionPos.blockToSectionCoord(offset.z)) - - if (chunk == null) { - fullDiscovery = false - set(dir, null) - continue - } - - val entity = chunk.getBlockEntity(offset) - - if (entity != null) { - set(dir, nodeGetter(entity)) - } else { - set(dir, null) - } - } - - return fullDiscovery - } - - companion object { - private val setter = Graph6Node<*, *>::graph::set - private val nextSeen = AtomicInteger() - private val directions = Direction.values() - - private fun , G : Abstract6Graph> breakConnection(a: N, b: N, direction: Direction) { - val opposite = -direction - - require(a.neighbours[direction] === b) { "$a does not neighbour with $b at direction $direction (forward)" } - require(b.neighbours[opposite] === a) { "$b does not neighbour with $a at direction $opposite (backward)" } - require(a.graph === b.graph) { "$a and $b belong to different graphs (${a.graph} vs ${b.graph})" } - - a.neighbours.remove(direction) - b.neighbours.remove(opposite) - - val seen = nextSeen.incrementAndGet() - val flood1 = flood(a, seen) - - if (b.seen != seen) { - val flood2 = flood(b, seen) - - val big: ArrayList - val small: ArrayList - - if (flood1.size >= flood2.size) { - big = flood1 - small = flood2 - } else { - big = flood2 - small = flood1 - } - - a.graph.retain(ReferenceOpenHashSet(big)) - val newGraph = a.graphFactory.invoke() - - for (node in small) { - node.graph = newGraph - } - - for (node in small) { - if (node.isValid) { - newGraph.addNode(node) - } - } - } - - if (a.isValid) - a.onUnNeighbour(direction) - - if (b.isValid) - b.onUnNeighbour(opposite) - } - - private fun , G : Abstract6Graph> flood(startingNode: N, seen: Int): ArrayList { - val unopen = ArrayList() - val result = ArrayList() - unopen.add(startingNode) - - while (unopen.isNotEmpty()) { - val last = unopen.removeLast() - - if (last.seen < seen) { - result.add(last) - last.seen = seen - - for (node in last.neighbours.values) { - if (node.seen < seen) { - unopen.add(node) - } - } - } - } - - return result - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt new file mode 100644 index 000000000..64d45298b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt @@ -0,0 +1,292 @@ +package ru.dbotthepony.mc.otm.graph + +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet +import net.minecraft.core.BlockPos +import net.minecraft.core.Direction +import net.minecraft.core.SectionPos +import net.minecraft.server.level.ServerLevel +import net.minecraft.world.level.block.entity.BlockEntity +import net.minecraftforge.common.capabilities.Capability +import ru.dbotthepony.mc.otm.addTicker +import ru.dbotthepony.mc.otm.core.math.plus +import ru.dbotthepony.mc.otm.core.orNull + +open class GraphNode, G : GraphNodeList>(val graphFactory: () -> G) { + interface Link { + val opposite: Link + operator fun unaryMinus() = opposite + } + + data class DirectionLink(val direction: Direction) : Link { + override val opposite: Link + get() = wrapped[direction.opposite.ordinal] + + override fun toString(): String { + return "Link[$direction]" + } + } + + private val neighbours = Object2ObjectOpenHashMap() + + var graph: G = graphFactory.invoke() + internal set + + init { + check(graph.addNodeQueued(this as N)) + } + + private var seen: Int = 0 + + operator fun get(key: Link): N? = neighbours[key] + + operator fun set(key: Link, node: N?) { + set(key, node, false) + } + + fun set(key: Link, target: N?, allowReplacement: Boolean) { + check(isValid) { "Can not neighbour any node while this node is invalid" } + + val old = neighbours[key] + if (old === target) return + + if (old != null) + breakConnection(this as N, old, key) + + if (target != null) { + require(target.isValid) { "Can not neighbour invalid node" } + + val opposite = key.opposite + + if (allowReplacement) { + target.neighbours[opposite]?.let { + breakConnection(target, it, opposite) + } + + check(target.neighbours[opposite] == null) { "$target didn't break connection at direction $opposite" } + } else { + check(target.neighbours[opposite] == null) { "Trying to form connection from $this to $target at $key, but $target already has neighbour at $opposite (${target.neighbours[opposite]})!" } + } + + target.neighbours[opposite] = this as N + neighbours[key] = target + target.graph.merge(graph) + target.onNeighbour(opposite) + onNeighbour(key) + } else { + neighbours.remove(key) + } + } + + var top: N? + get() = neighbours[UP] + set(value) { + set(UP, value) + } + + var bottom: N? + get() = neighbours[DOWN] + set(value) { + set(DOWN, value) + } + + var south: N? + get() = neighbours[SOUTH] + set(value) { + set(SOUTH, value) + } + + var north: N? + get() = neighbours[NORTH] + set(value) { + set(NORTH, value) + } + + var west: N? + get() = neighbours[WEST] + set(value) { + set(WEST, value) + } + + var east: N? + get() = neighbours[EAST] + set(value) { + set(EAST, value) + } + + protected open fun onNeighbour(link: Link) {} + protected open fun onUnNeighbour(link: Link) {} + + protected open fun invalidate() {} + protected open fun revive() {} + + var isValid: Boolean = true + set(value) { + if (value == field) return + field = value + + if (!value) { + val neighbours = neighbours.entries.map { it.key to it.value } + + for ((link, node) in neighbours) { + removeNeighbours(this as N, node, link) + } + + graph.removeNode(this as N) + rebuildGraphs(neighbours.map { it.second }) + invalidate() + } else { + revive() + } + } + + fun discover( + level: ServerLevel, + blockPos: BlockPos, + nodeGetter: (BlockEntity) -> N? + ) { + if (!isValid) return + + level.addTicker { + isValid && !discoverStep(level, blockPos, nodeGetter) + } + } + + fun discover( + level: ServerLevel, + blockPos: BlockPos, + capability: Capability + ) { + if (!isValid) return + + level.addTicker { + isValid && !discoverStep(level, blockPos) { it.getCapability(capability).orNull() } + } + } + + fun discover(blockEntity: BlockEntity, nodeGetter: (BlockEntity) -> N?) { + discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, nodeGetter) + } + + fun discover(blockEntity: BlockEntity, capability: Capability) { + discover(blockEntity.level as? ServerLevel ?: return, blockEntity.blockPos, capability) + } + + fun discoverStep( + level: ServerLevel, + blockPos: BlockPos, + nodeGetter: (BlockEntity) -> N?, + ): Boolean { + if (!isValid) return false + var fullDiscovery = true + + for (dir in wrapped) { + val offset = blockPos + dir.direction + val chunk = level.chunkSource.getChunkNow(SectionPos.blockToSectionCoord(offset.x), SectionPos.blockToSectionCoord(offset.z)) + + if (chunk == null) { + fullDiscovery = false + set(dir, null) + continue + } + + val entity = chunk.getBlockEntity(offset) + + if (entity != null) { + set(dir, nodeGetter(entity)) + } else { + set(dir, null) + } + } + + return fullDiscovery + } + + companion object { + private var nextSeen = 0 + private val wrapped = Direction.entries.map { DirectionLink(it) } + + fun link(direction: Direction): Link { + return wrapped[direction.ordinal] + } + + val UP = link(Direction.UP) + val DOWN = link(Direction.DOWN) + val NORTH = link(Direction.NORTH) + val SOUTH = link(Direction.SOUTH) + val WEST = link(Direction.WEST) + val EAST = link(Direction.EAST) + + private fun , G : GraphNodeList> removeNeighbours(a: N, b: N, key: Link) { + val opposite = key.opposite + + // проверяем, действительно ли a соединён с b по key + require(a.neighbours[key] === b) { "$a does not neighbour with $b at $key (a -> b)" } + + // проверяем обратную связь + require(b.neighbours[opposite] === a) { "$b does not neighbour with $a at $opposite (b -> a)" } + + // находятся ли они в одном графе + require(a.graph === b.graph) { "$a and $b belong to different graphs (${a.graph} / ${b.graph})" } + + a.neighbours.remove(key) + b.neighbours.remove(opposite) + + if (a.isValid) a.onUnNeighbour(key) + if (b.isValid) b.onUnNeighbour(key.opposite) + } + + private fun , G : GraphNodeList> rebuildGraphs(nodes: Collection) { + if (nodes.isEmpty()) + return + + val seen = ++nextSeen + val floods = ArrayList>() + + for (node in nodes) { + if (node.seen < seen) { + floods.add(flood(node, seen)) + } + } + + floods.sortedBy { it.first().graph.size } + floods.first().first().graph.retain(ReferenceOpenHashSet(floods.first())) + + for (i in 1 until floods.size) { + val graph = floods[i].first().graphFactory.invoke() + + for (node in floods[i]) { + graph.addNode(node) + } + } + } + + private fun , G : GraphNodeList> breakConnection(a: N, b: N, key: Link) { + removeNeighbours(a, b, key) + rebuildGraphs(listOf(a, b)) + } + + private fun , G : GraphNodeList> flood(startingNode: N, seen: Int): ArrayList { + val unopen = ArrayList() + val result = ArrayList() + unopen.add(startingNode) + + while (unopen.isNotEmpty()) { + val last = unopen.removeLast() + + if (last.seen < seen) { + result.add(last) + last.seen = seen + + for (node in last.neighbours.values) { + if (node.seen < seen) { + unopen.add(node) + } + } + } + } + + return result + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt new file mode 100644 index 000000000..763b7beeb --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt @@ -0,0 +1,199 @@ +package ru.dbotthepony.mc.otm.graph + +import ru.dbotthepony.mc.otm.core.util.IConditionalTickable +import ru.dbotthepony.mc.otm.core.util.ITickable +import java.lang.ref.WeakReference +import java.util.* +import kotlin.collections.ArrayDeque +import kotlin.collections.ArrayList + +open class GraphNodeList, G : GraphNodeList> : IConditionalTickable { + private val queuedAdd = ArrayDeque() + private val queuedRemove = ArrayDeque() + private val nodesInternal = ArrayList() + private val conditional = ArrayList() + private val always = ArrayList() + private var isTicking = false + private var shouldQueueChanges = false + + protected val nodes: List = Collections.unmodifiableList(nodesInternal) + + val size get() = nodesInternal.size + + var isValid = true + private set + + /** + * Allows storing arbitrary data by external code + */ + val userData = HashMap() + + protected open fun onNodeRemoved(node: N) {} + protected open fun onNodeAdded(node: N) {} + + protected open fun innerTick(): Boolean { + return false + } + + private fun addNow(node: N) { + node.graph = this as G + nodesInternal.add(node) + + if (node is IConditionalTickable) + conditional.add(node) + else if (node is ITickable) + always.add(node) + + onNodeAdded(node) + } + + private fun removeNow(node: N, removeFromList: Boolean = true) { + if (removeFromList) + nodesInternal.remove(node) + + if (node is IConditionalTickable) + conditional.remove(node) + else if (node is ITickable) + always.remove(node) + + onNodeRemoved(node) + } + + private fun processAddQueue() { + while (queuedAdd.isNotEmpty()) { + addNow(queuedAdd.removeFirst()) + } + } + + private fun processRemoveQueue() { + while (queuedRemove.isNotEmpty()) { + removeNow(queuedRemove.removeFirst()) + } + } + + private fun processQueues() { + processRemoveQueue() + processAddQueue() + } + + final override fun tick(): Boolean { + if (!isValid) + return false + + processQueues() + conditional.removeIf { !it.tick() } + for (node in always) node.tick() + + return innerTick() || always.isNotEmpty() || conditional.isNotEmpty() + } + + fun addNodeQueued(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node in nodesInternal || node in queuedAdd) + return false + + queuedAdd.add(node) + beginTicking() + return true + } + + fun addNode(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node in nodesInternal) + return false + + if (shouldQueueChanges) { + if (node in queuedAdd) return false + queuedAdd.add(node) + beginTicking() + } else { + queuedAdd.remove(node) + addNow(node) + } + + return true + } + + fun removeNode(node: N): Boolean { + check(isValid) { "$this is no longer valid" } + + if (node !in nodesInternal) + return false + + if (shouldQueueChanges) { + if (node in queuedRemove) return false + queuedRemove.add(node) + beginTicking() + } else { + queuedRemove.remove(node) + removeNow(node) + } + + return true + } + + fun retain(nodes: Set) { + check(isValid) { "$this is no longer valid" } + queuedAdd.retainAll(nodes) + nodesInternal.removeIf { + if (it !in nodes) { + removeNow(it, false) + true + } else { + false + } + } + } + + fun merge(other: G): G { + if (other === this) + return this + + check(isValid) { "$this is no longer valid" } + + if (size >= other.size) { + shouldQueueChanges = true + other.processQueues() + other.nodesInternal.forEach { if (it.isValid) addNow(it) } + processQueues() + shouldQueueChanges = false + + other.isValid = false + return this as G + } else { + return other.merge(this as G) + } + } + + private fun beginTicking() { + if (!isTicking) { + isTicking = true + queue.add(WeakReference(this)) + } + } + + companion object { + private val graphs = ArrayList>>() + private val queue = ArrayList>>() + + fun tick() { + if (queue.isNotEmpty()) { + graphs.addAll(queue) + queue.clear() + } + + val iterator = graphs.iterator() + + for (value in iterator) { + val graph = value.get() + + if (graph == null || !graph.tick()) { + graph?.isTicking = false + iterator.remove() + } + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt index 2632bb0de..0518c56e2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterGraph.kt @@ -6,13 +6,13 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection import ru.dbotthepony.mc.otm.capability.matter.* import ru.dbotthepony.mc.otm.core.filterNotNull import ru.dbotthepony.mc.otm.core.math.Decimal -import ru.dbotthepony.mc.otm.graph.Abstract6Graph +import ru.dbotthepony.mc.otm.graph.GraphNodeList import java.util.* import java.util.function.Predicate import java.util.stream.Stream @Suppress("unused") -class MatterGraph : Abstract6Graph(), IMatterGraphListener { +class MatterGraph : GraphNodeList(), IMatterGraphListener { private val listeners = ObjectOpenHashSet() fun addListener(listener: IMatterGraphListener) = listeners.add(listener) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt index 33b885a98..17b1ca440 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/matter/MatterNode.kt @@ -5,9 +5,9 @@ import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage import ru.dbotthepony.mc.otm.capability.matter.IReplicationTaskProvider -import ru.dbotthepony.mc.otm.graph.Graph6Node +import ru.dbotthepony.mc.otm.graph.GraphNode -open class MatterNode : Graph6Node(::MatterGraph), IMatterGraphListener { +open class MatterNode : GraphNode(::MatterGraph), IMatterGraphListener { open fun getMatterHandler(): IMatterStorage? = null open fun getPatternHandler(): IPatternStorage? = null open fun getTaskHandler(): IReplicationTaskProvider? = null diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt index b85b66ea0..11ce5f853 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageGraph.kt @@ -2,19 +2,12 @@ package ru.dbotthepony.mc.otm.graph.storage import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap import it.unimi.dsi.fastutil.objects.Object2ObjectFunction -import net.minecraft.server.level.ServerLevel -import net.minecraft.world.level.Level -import net.minecraft.world.level.block.entity.BlockEntity -import ru.dbotthepony.mc.otm.addTickerPre import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.capability.MatteryCapability -import ru.dbotthepony.mc.otm.graph.Abstract6Graph -import ru.dbotthepony.mc.otm.graph.Graph6Node -import ru.dbotthepony.mc.otm.core.orNull +import ru.dbotthepony.mc.otm.graph.GraphNodeList import ru.dbotthepony.mc.otm.storage.* import java.util.LinkedList -class StorageGraph : Abstract6Graph() { +class StorageGraph : GraphNodeList() { private val virtualComponents = Object2ObjectArrayMap, VirtualComponent<*>>() val powerDemandingNodes = LinkedList() @@ -46,11 +39,11 @@ class StorageGraph : Abstract6Graph() { } override fun onNodeAdded(node: StorageNode) { - //node.attachComponents(this) + node.attachComponents(this) } override fun onNodeRemoved(node: StorageNode) { - //node.removeComponents(this) + node.removeComponents(this) } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt index c2b1c3e53..b06493c0c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/graph/storage/StorageNode.kt @@ -3,13 +3,13 @@ package ru.dbotthepony.mc.otm.graph.storage import net.minecraft.world.level.block.entity.BlockEntity import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage -import ru.dbotthepony.mc.otm.graph.Graph6Node +import ru.dbotthepony.mc.otm.graph.GraphNode import ru.dbotthepony.mc.otm.storage.IStorage import ru.dbotthepony.mc.otm.storage.IStorageStack import ru.dbotthepony.mc.otm.storage.StorageStackType import java.util.* -open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : Graph6Node(::StorageGraph) { +open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode(::StorageGraph) { protected val components = ArrayList>() /** @@ -37,10 +37,9 @@ open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null return } - if (components != null) - for (component in components) { - to.add(component) - } + for (component in components) { + to.add(component) + } } /**