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:
parent
be2424055d
commit
93d4329acd
@ -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()
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
292
src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt
Normal file
292
src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNode.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
199
src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt
Normal file
199
src/main/kotlin/ru/dbotthepony/mc/otm/graph/GraphNodeList.kt
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user