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

This commit is contained in:
DBotThePony 2023-08-06 01:39:16 +07:00
parent be2424055d
commit 93d4329acd
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 558 additions and 500 deletions

View File

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

View File

@ -15,20 +15,24 @@ 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)
}
}
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)
}
}
}
init {
exposeGlobally(MatteryCapability.MATTER_NODE, matterNode)
@ -50,20 +54,24 @@ 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)
}
}
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)
}
}
}
init {
exposeGlobally(MatteryCapability.STORAGE_NODE, storageNode)

View File

@ -69,20 +69,24 @@ 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)
}
}
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)
}
}
}
init {
exposeSideless(MatteryCapability.STORAGE_NODE, cell)

View File

@ -58,21 +58,24 @@ abstract class AbstractStorageImportExport<T>(
}
val cell: StorageNode = object : StorageNode(energy) {
override fun onNeighbour(direction: Direction) {
override fun onNeighbour(link: Link) {
if (link is DirectionLink) {
level?.once {
if (!isRemoved) {
val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, true)
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)
}
}
}
}
override fun onUnNeighbour(direction: Direction) {
override fun onUnNeighbour(link: Link) {
if (link is DirectionLink) {
level?.once {
if (!isRemoved) {
val newState = this@AbstractStorageImportExport.blockState.setValue(CableBlock.MAPPING_CONNECTION_PROP[direction]!!, false)
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)
@ -80,6 +83,7 @@ abstract class AbstractStorageImportExport<T>(
}
}
}
}
init {
exposeSideless(MatteryCapability.STORAGE_NODE, cell)

View File

@ -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<N : Graph6Node<N, G>, G : Abstract6Graph<N, G>> : IConditionalTickable {
private val nodesInternal = ArrayList<N>()
private val conditional = ArrayList<IConditionalTickable>()
private val always = ArrayList<ITickable>()
protected val nodes: List<N> = 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<UUID, Any>()
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<N>) {
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<WeakReference<Abstract6Graph<*, *>>>()
private val next = ArrayList<WeakReference<Abstract6Graph<*, *>>>()
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()
}
}
}
}
}

View File

@ -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<N : Graph6Node<N, G>, G : Abstract6Graph<N, G>>(val graphFactory: () -> G) {
private val neighbours = EnumMap<Direction, N>(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<out N>
) {
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<out N>) {
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 <N : Graph6Node<N, G>, G : Abstract6Graph<N, G>> 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<N>
val small: ArrayList<N>
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 <N : Graph6Node<N, G>, G : Abstract6Graph<N, G>> flood(startingNode: N, seen: Int): ArrayList<N> {
val unopen = ArrayList<N>()
val result = ArrayList<N>()
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
}
}
}

View File

@ -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<N : GraphNode<N, G>, G : GraphNodeList<N, G>>(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<Link, N>()
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<out N>
) {
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<out N>) {
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 <N : GraphNode<N, G>, G : GraphNodeList<N, G>> 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 <N : GraphNode<N, G>, G : GraphNodeList<N, G>> rebuildGraphs(nodes: Collection<N>) {
if (nodes.isEmpty())
return
val seen = ++nextSeen
val floods = ArrayList<ArrayList<N>>()
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 <N : GraphNode<N, G>, G : GraphNodeList<N, G>> breakConnection(a: N, b: N, key: Link) {
removeNeighbours(a, b, key)
rebuildGraphs(listOf(a, b))
}
private fun <N : GraphNode<N, G>, G : GraphNodeList<N, G>> flood(startingNode: N, seen: Int): ArrayList<N> {
val unopen = ArrayList<N>()
val result = ArrayList<N>()
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
}
}
}

View File

@ -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<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : IConditionalTickable {
private val queuedAdd = ArrayDeque<N>()
private val queuedRemove = ArrayDeque<N>()
private val nodesInternal = ArrayList<N>()
private val conditional = ArrayList<IConditionalTickable>()
private val always = ArrayList<ITickable>()
private var isTicking = false
private var shouldQueueChanges = false
protected val nodes: List<N> = Collections.unmodifiableList(nodesInternal)
val size get() = nodesInternal.size
var isValid = true
private set
/**
* Allows storing arbitrary data by external code
*/
val userData = HashMap<UUID, Any>()
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<N>) {
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<WeakReference<GraphNodeList<*, *>>>()
private val queue = ArrayList<WeakReference<GraphNodeList<*, *>>>()
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()
}
}
}
}
}

View File

@ -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<MatterNode, MatterGraph>(), IMatterGraphListener {
class MatterGraph : GraphNodeList<MatterNode, MatterGraph>(), IMatterGraphListener {
private val listeners = ObjectOpenHashSet<IMatterGraphListener>()
fun addListener(listener: IMatterGraphListener) = listeners.add(listener)

View File

@ -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<MatterNode, MatterGraph>(::MatterGraph), IMatterGraphListener {
open class MatterNode : GraphNode<MatterNode, MatterGraph>(::MatterGraph), IMatterGraphListener {
open fun getMatterHandler(): IMatterStorage? = null
open fun getPatternHandler(): IPatternStorage? = null
open fun getTaskHandler(): IReplicationTaskProvider? = null

View File

@ -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<StorageNode, StorageGraph>() {
class StorageGraph : GraphNodeList<StorageNode, StorageGraph>() {
private val virtualComponents = Object2ObjectArrayMap<StorageStackType<*>, VirtualComponent<*>>()
val powerDemandingNodes = LinkedList<IMatteryEnergyStorage>()
@ -46,11 +39,11 @@ class StorageGraph : Abstract6Graph<StorageNode, StorageGraph>() {
}
override fun onNodeAdded(node: StorageNode) {
//node.attachComponents(this)
node.attachComponents(this)
}
override fun onNodeRemoved(node: StorageNode) {
//node.removeComponents(this)
node.removeComponents(this)
}
}

View File

@ -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<StorageNode, StorageGraph>(::StorageGraph) {
open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null) : GraphNode<StorageNode, StorageGraph>(::StorageGraph) {
protected val components = ArrayList<IStorage<*>>()
/**
@ -37,7 +37,6 @@ open class StorageNode(private val energyDemander: IMatteryEnergyStorage? = null
return
}
if (components != null)
for (component in components) {
to.add(component)
}