Compare commits

...

4 Commits

Author SHA1 Message Date
43afa73df7
Minimally functional segmented power network, with functional per-tick limited throughput 2025-01-14 16:55:36 +07:00
0673fbf352
Merge remote-tracking branch 'origin/1.21' into 1.21 2025-01-11 10:24:06 +07:00
3f75e40e9c
Merge remote-tracking branch 'origin/1.21' into 1.21 2025-01-10 20:02:31 +07:00
5f6da754f9
I just need multiblock rendering
Have to add a little multiblock rendering
Are you okay? - Yeah, i just need to `add a little more multiblock rendering`
Rend-er-ing
Rend
and R in ING is RENDER-MAN
Hard Rendy the Bender Render Fender
2025-01-10 20:02:25 +07:00
11 changed files with 344 additions and 49 deletions

View File

@ -222,7 +222,7 @@ fun tickServer(ticker: IConditionalTickable) {
postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping")
} }
fun Level.timer(time: Int, ticker: Runnable): TickList.Timer? { fun Level.once(time: Int, ticker: Runnable): TickList.Timer? {
if (this.isClientSide) return null if (this.isClientSide) return null
if (!SERVER_IS_LIVE) { if (!SERVER_IS_LIVE) {

View File

@ -15,9 +15,13 @@ import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.config.CablesConfig import ru.dbotthepony.mc.otm.config.CablesConfig
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.BlockRotation
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.core.util.countingLazy
import ru.dbotthepony.mc.otm.graph.GraphNode import ru.dbotthepony.mc.otm.graph.GraphNode
import ru.dbotthepony.mc.otm.once import ru.dbotthepony.mc.otm.once
import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.onceServer
@ -37,7 +41,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
field = value field = value
if (value) { if (value) {
node.graph.livelyNodes.add(node) node.graph.addLivelyNode(node)
} }
} }
@ -55,7 +59,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
if (isEnabled) { if (isEnabled) {
if (neighbour.isPresent) { if (neighbour.isPresent) {
if (neighbour.get() !is CableSide) { if (neighbour.get() !is CableSide) {
node.graph.livelyNodes.add(node) node.graph.addLivelyNode(node)
} }
} }
@ -90,7 +94,27 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
get() = Decimal.POSITIVE_INFINITY get() = Decimal.POSITIVE_INFINITY
} }
private val isPoweredState by countingLazy(blockStateChangesCounter) {
this.blockState[BlockStateProperties.POWERED]
}
inner class Node : GraphNode<Node, EnergyCableGraph>(::EnergyCableGraph) { inner class Node : GraphNode<Node, EnergyCableGraph>(::EnergyCableGraph) {
private var _segment = EnergyCableGraph.Segment(this)
var segment: EnergyCableGraph.Segment
get() { return _segment }
set(value) {
if (_segment === value) return
_segment.remove(this)
value.add(this)
_segment = value
}
fun onInvalidate() {
_segment = EnergyCableGraph.Segment(this)
updatePoweredState(false)
}
val sides get() = energySides val sides get() = energySides
override fun onNeighbour(link: Link) { override fun onNeighbour(link: Link) {
@ -105,6 +129,35 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
} }
} }
private var updateTimer: TickList.Timer? = null
private var ongoingNewState: Boolean? = null
fun updatePoweredState(newState: Boolean) {
if (isRemoved) return
val level = level ?: return
if (isPoweredState != newState || ongoingNewState != newState) {
updateTimer?.cancel()
if (isPoweredState == newState) {
ongoingNewState = null
updateTimer?.cancel()
} else {
ongoingNewState = newState
// reduces flickering and block updates pressure
updateTimer = level.once(4) {
ongoingNewState = null
updateTimer = null
if (!isRemoved) {
level.setBlock(blockPos, blockState.set(BlockStateProperties.POWERED, newState), Block.UPDATE_CLIENTS)
}
}
}
}
}
val blockEntity get() = this@EnergyCableBlockEntity val blockEntity get() = this@EnergyCableBlockEntity
val canTraverse get() = energyThroughput > Decimal.ZERO val canTraverse get() = energyThroughput > Decimal.ZERO
val energyThroughput get() = this@EnergyCableBlockEntity.energyThroughput val energyThroughput get() = this@EnergyCableBlockEntity.energyThroughput
@ -115,30 +168,14 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
if (!SERVER_IS_LIVE) return if (!SERVER_IS_LIVE) return
val level = level val level = level
val powered = node.graph.livelyNodes.filter {
it.blockEntity.energySides.filter {
side -> side.value.neighbour.isPresent && side.value.neighbour.get() !is CableSide
}.isNotEmpty()
}.size >= 2
level?.once { level?.once {
if (!node.isValid) return@once if (!node.isValid) return@once
val newState = blockState val newState = blockState
.setValue(CableBlock.MAPPING_CONNECTION_PROP[side]!!, status) .setValue(CableBlock.MAPPING_CONNECTION_PROP[side]!!, status)
.setValue(BlockStateProperties.POWERED, powered)
if (newState !== blockState && SERVER_IS_LIVE) if (newState !== blockState && SERVER_IS_LIVE)
level.setBlock(blockPos, newState, Block.UPDATE_CLIENTS) level.setBlock(blockPos, newState, Block.UPDATE_CLIENTS)
node.graph.livelyNodes.forEach {
if (it.isValid) {
val newState = it.blockEntity.blockState.setValue(BlockStateProperties.POWERED, powered)
if (newState !== it.blockEntity.blockState && SERVER_IS_LIVE)
level.setBlock(it.blockEntity.blockPos, newState, Block.UPDATE_CLIENTS)
}
}
} }
} }
@ -154,7 +191,9 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
override fun setLevel(level: Level) { override fun setLevel(level: Level) {
super.setLevel(level) super.setLevel(level)
node.discover(this, MatteryCapability.ENERGY_CABLE_NODE)
if (!level.isClientSide)
node.discover(this, MatteryCapability.ENERGY_CABLE_NODE)
} }
override fun setRemoved() { override fun setRemoved() {

View File

@ -1,37 +1,245 @@
package ru.dbotthepony.mc.otm.block.entity.cable package ru.dbotthepony.mc.otm.block.entity.cable
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.core.BlockPos
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.UNIVERSE_TICKS
import ru.dbotthepony.mc.otm.capability.receiveEnergy import ru.dbotthepony.mc.otm.capability.receiveEnergy
import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.shuffle
import ru.dbotthepony.mc.otm.graph.GraphNodeList import ru.dbotthepony.mc.otm.graph.GraphNodeList
import ru.dbotthepony.mc.otm.onceServer
import java.util.* import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
import kotlin.math.ln import kotlin.math.ln
class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() { class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableGraph>() {
val livelyNodes = HashSet<EnergyCableBlockEntity.Node>() private val livelyNodes = HashSet<EnergyCableBlockEntity.Node>()
private val livelyNodesList = ArrayList<EnergyCableBlockEntity.Node>()
private val pathCache = HashMap<Pair<EnergyCableBlockEntity.Node, EnergyCableBlockEntity.Node>, Decimal?>() fun addLivelyNode(node: EnergyCableBlockEntity.Node) {
require(node in nodesSet) { "$node does not belong to $this" }
if (livelyNodes.add(node)) {
livelyNodesList.add(node)
}
}
// TODO: LRU cache?
private val pathCache = HashMap<Pair<EnergyCableBlockEntity.Node, EnergyCableBlockEntity.Node>, ArrayList<SegmentPath?>>()
private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable<SearchNode> { private class SearchNode(val node: EnergyCableBlockEntity.Node, target: EnergyCableBlockEntity.Node, var parent: SearchNode? = null) : Comparable<SearchNode> {
val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.energyThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble()) val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.segment.availableThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble())
override fun compareTo(other: SearchNode): Int { override fun compareTo(other: SearchNode): Int {
return heuristics.compareTo(other.heuristics) return heuristics.compareTo(other.heuristics)
} }
} }
fun invalidatePathCache() { class SegmentPath(private val a: BlockPos, private val b: BlockPos) {
pathCache.clear() val segments = HashSet<Segment>()
private var shortCircuit = false
private var lastTickTransfers = 0
private var lastTick = 0
val availableThroughput: Decimal
get() = segments.minOfOrNull { it.availableThroughput } ?: Decimal.ZERO
fun transfer(amount: Decimal, simulate: Boolean, instantSnapshot: MutableMap<Segment, Decimal>): Decimal {
if (!amount.isPositive || shortCircuit) {
return Decimal.ZERO
}
if (lastTickTransfers++ >= 40000) {
if (lastTick == UNIVERSE_TICKS) {
shortCircuit = true
LOGGER.warn("Cable path from $a to $b appears to be a short circuit, disabling.")
} else {
lastTick = UNIVERSE_TICKS
lastTickTransfers = 0
}
}
var min = amount
for (segment in segments) {
min = minOf(segment.transfer(amount, instantSnapshot), min)
if (!min.isPositive) return Decimal.ZERO
}
if (!simulate)
segments.forEach { it.transfer(min) }
return min
}
fun remove() {
segments.toTypedArray().forEach { it.remove(this) }
}
} }
private fun getPath(a: EnergyCableBlockEntity.Node, b: EnergyCableBlockEntity.Node): Decimal? { // TODO: Actual segment logic (merging segments)
class Segment() {
constructor(node: EnergyCableBlockEntity.Node) : this() {
nodes.add(node)
}
private val nodes = HashSet<EnergyCableBlockEntity.Node>()
private val paths = HashSet<SegmentPath>()
var throughput = Decimal.ZERO
private set
var transferredLastTick = Decimal.ZERO
private set
val availableThroughput: Decimal
get() = throughput - transferredLastTick
private var throughputKnown = false
private var lastTick = 0
private var lastPoweredStatus = 0
private fun updateBlockstates(): Boolean {
if (lastTick + 2 < UNIVERSE_TICKS) {
transferredLastTick = Decimal.ZERO
lastTick = UNIVERSE_TICKS
}
val newState = transferredLastTick.isPositive
val numState = if (newState) 1 else -1
if (numState != lastPoweredStatus) {
lastPoweredStatus = numState
nodes.forEach { if (it.segment === this) it.updatePoweredState(newState) }
}
return newState
}
private var hasBlockstateTimer = false
private fun blockstateUpdateStep() {
hasBlockstateTimer = updateBlockstates()
if (hasBlockstateTimer) {
onceServer(20) {
blockstateUpdateStep()
}
}
}
fun transfer(amount: Decimal, instantSnapshot: MutableMap<Segment, Decimal>): Decimal {
if (lastTick != UNIVERSE_TICKS) {
transferredLastTick = Decimal.ZERO
lastTick = UNIVERSE_TICKS
}
val currentTransferred = instantSnapshot[this] ?: transferredLastTick
if (currentTransferred >= throughput || !amount.isPositive) {
return Decimal.ZERO
}
val new = minOf(currentTransferred + amount, throughput)
val diff = new - currentTransferred
instantSnapshot[this] = new
return diff
}
fun transfer(amount: Decimal): Decimal {
if (lastTick != UNIVERSE_TICKS) {
transferredLastTick = Decimal.ZERO
lastTick = UNIVERSE_TICKS
}
if (transferredLastTick >= throughput || !amount.isPositive) {
return Decimal.ZERO
}
val new = minOf(transferredLastTick + amount, throughput)
val diff = new - transferredLastTick
transferredLastTick = new
if (!hasBlockstateTimer) {
blockstateUpdateStep()
}
return diff
}
fun remove(node: EnergyCableBlockEntity.Node) {
check(nodes.remove(node)) { "Tried to remove node $node from segment $this, but that node does not belong to this segment" }
if (nodes.isEmpty())
throughput = Decimal.ZERO
else
throughput = nodes.maxOf { it.energyThroughput }
}
fun add(node: EnergyCableBlockEntity.Node) {
check(nodes.add(node)) { "Tried to add node $node to segment $this, but we already have that node added" }
throughput = maxOf(throughput, node.energyThroughput)
}
fun add(path: SegmentPath) {
check(paths.add(path)) { "Path $path should already contain $this" }
check(path.segments.add(this)) { "Path set and Segment disagree whenever $this is absent from $path" }
if (!throughputKnown) {
throughput = nodes.maxOf { it.energyThroughput }
throughputKnown = true
}
}
fun remove(path: SegmentPath) {
check(paths.remove(path)) { "Path $path shouldn't contain $this" }
check(path.segments.remove(this)) { "Path set and Segment disagree whenever $this is present in $path" }
}
// breaks "instant snapshots" of segments atm
// shouldn't cause major gameplay issues though
fun split(): List<Segment> {
if (nodes.isEmpty()) {
throw IllegalStateException("Empty segment somehow?")
} else if (nodes.size == 1) {
return listOf(this)
} else {
lastPoweredStatus = 0
val list = ArrayList(nodes)
val itr = list.iterator()
itr.next()
val result = ArrayList<Segment>()
result.add(this)
for (v in itr) {
v.segment = Segment()
v.segment.paths.addAll(paths)
paths.forEach { it.segments.add(v.segment) }
result.add(v.segment)
}
return result
}
}
}
fun invalidatePathCache() {
pathCache.clear()
nodes.forEach { it.onInvalidate() }
}
// TODO: Multiple paths, so energy can be delivered to receiver through different paths if previously found path is congested
private fun getPath(a: EnergyCableBlockEntity.Node, b: EnergyCableBlockEntity.Node, energyToTransfer: Decimal): SegmentPath? {
if (!a.canTraverse || !b.canTraverse) if (!a.canTraverse || !b.canTraverse)
return null return null
val key = a to b val list = pathCache.computeIfAbsent(a to b) { ArrayList(1) }
if (key in pathCache) if (list.isNotEmpty())
return pathCache[key] return list.first
// no free paths available, try to find extra one // no free paths available, try to find extra one
// while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search), // while this use A* algorithm, this is done purely for biasing search towards end point (to speed up search),
@ -57,9 +265,13 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
last = last.parent last = last.parent
} }
val calc = solution.minOf { it.energyThroughput } val touchedSegments = ArrayList<Segment>()
pathCache[key] = calc solution.forEach { touchedSegments.addAll(it.segment.split()) }
return calc val path = SegmentPath(a.blockEntity.blockPos, b.blockEntity.blockPos)
touchedSegments.forEach { it.add(path) }
list.add(path)
return path
} else { } else {
for (neighbour in first.node.neighboursView.values) { for (neighbour in first.node.neighboursView.values) {
if (!seenNodes.add(neighbour) || !neighbour.canTraverse) continue if (!seenNodes.add(neighbour) || !neighbour.canTraverse) continue
@ -69,24 +281,31 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
// solution does not exist // solution does not exist
pathCache[key] = null list.add(null)
return null return null
} }
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) { override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
livelyNodes.remove(node) if (livelyNodes.remove(node)) {
check(livelyNodesList.remove(node))
}
invalidatePathCache() invalidatePathCache()
} }
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) { override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
livelyNodes.add(node) check(livelyNodes.add(node))
livelyNodesList.add(node)
invalidatePathCache() invalidatePathCache()
} }
fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal { fun receiveEnergy(howMuch: Decimal, simulate: Boolean, fromNode: EnergyCableBlockEntity.Node, fromSide: RelativeSide): Decimal {
val itr = livelyNodes.iterator() livelyNodesList.shuffle(fromNode.blockEntity.level!!.random)
val itr = livelyNodesList.iterator()
var received = Decimal.ZERO var received = Decimal.ZERO
var residue = howMuch.coerceAtMost(fromNode.energyThroughput) var residue = howMuch.coerceAtMost(fromNode.energyThroughput)
val snapshot = Reference2ObjectOpenHashMap<Segment, Decimal>()
for (node in itr) { for (node in itr) {
var hit = false var hit = false
@ -100,11 +319,11 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
side.neighbour.get()?.let { side.neighbour.get()?.let {
if (it !is EnergyCableBlockEntity.CableSide) { if (it !is EnergyCableBlockEntity.CableSide) {
val limit = getPath(fromNode, node) val path = getPath(fromNode, node, residue)
hit = true hit = true
if (limit != null) { if (path != null) {
val thisReceived = it.receiveEnergy(residue.coerceAtMost(limit), simulate) val thisReceived = it.receiveEnergy(path.transfer(residue, simulate, snapshot), simulate)
received += thisReceived received += thisReceived
residue -= thisReceived residue -= thisReceived
if (!residue.isPositive) return received if (!residue.isPositive) return received
@ -116,9 +335,14 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
if (!hit) { if (!hit) {
itr.remove() itr.remove()
check(livelyNodes.remove(node))
} }
} }
return received return received
} }
companion object {
private val LOGGER = LogManager.getLogger()
}
} }

View File

@ -54,6 +54,22 @@ fun IMultiblockAccess.render(
val nodes = currentNodes val nodes = currentNodes
for (node in nodes.values) { for (node in nodes.values) {
if (node.displayBlocks.isNotEmpty() && node.status == NodeStatus.VALID) {
continue
}
// TODO: rendering
// TODO: rendering
// TODO: rendering
// TODO: Rendering
// TODO: Rendering...
// TODO: Rendering!..
// TODO: Rendering!..
// TODO: Rendering!
// TODO: Rendering!!
// TODO: RENDERING!!
// TODO: RENDERING!!!
val color = when (node.status) { val color = when (node.status) {
NodeStatus.UNKNOWN -> UNKNOWN NodeStatus.UNKNOWN -> UNKNOWN
NodeStatus.VALID -> VALID NodeStatus.VALID -> VALID

View File

@ -74,7 +74,7 @@ enum class BlockRotation(
init { init {
val crossproduct = front.normal.cross(top.normal) val crossproduct = front.normal.cross(top.normal)
right = Direction.values().first { it.normal == crossproduct } right = Direction.entries.first { it.normal == crossproduct }
} }
val left: Direction = right.opposite val left: Direction = right.opposite
@ -165,7 +165,7 @@ enum class BlockRotation(
private val mapped = EnumMap<Direction, EnumMap<Direction, BlockRotation>>(Direction::class.java) private val mapped = EnumMap<Direction, EnumMap<Direction, BlockRotation>>(Direction::class.java)
init { init {
for (value in values()) { for (value in entries) {
val (front, top) = value val (front, top) = value
mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value) mapped.computeIfAbsent(front) { EnumMap(Direction::class.java) }.put(top, value)
} }

View File

@ -1,8 +1,12 @@
package ru.dbotthepony.mc.otm.core.multiblock package ru.dbotthepony.mc.otm.core.multiblock
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.state.BlockState
interface IMultiblockNode { interface IMultiblockNode {
val pos: BlockPos val pos: BlockPos
val status: NodeStatus val status: NodeStatus
val displayBlocks: List<BlockState>
get() = listOf()
} }

View File

@ -85,6 +85,8 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
private var lastSuccessfulPathPredicate = -1 private var lastSuccessfulPathPredicate = -1
private var lastSuccessfulPathChildren = -1 private var lastSuccessfulPathChildren = -1
override val displayBlocks: List<BlockState> = prototype.displayBlocks.asList()
override var status: NodeStatus = NodeStatus.UNKNOWN override var status: NodeStatus = NodeStatus.UNKNOWN
private set private set

View File

@ -51,6 +51,7 @@ class ShapedMultiblockBuilder {
val blockStateTags = ObjectArraySet<Any>() val blockStateTags = ObjectArraySet<Any>()
val blockTags = ObjectArraySet<Any>() val blockTags = ObjectArraySet<Any>()
val blockEntityTags = ObjectArraySet<BlockEntityTag<*>>() val blockEntityTags = ObjectArraySet<BlockEntityTag<*>>()
val displayBlocks = HashSet<BlockState>()
/** /**
* Marks this node report its block in [ShapedMultiblock.blockStates] method when called with specified [value] * Marks this node report its block in [ShapedMultiblock.blockStates] method when called with specified [value]
@ -192,6 +193,7 @@ class ShapedMultiblockBuilder {
ImmutableSet.copyOf(blockStateTags), ImmutableSet.copyOf(blockStateTags),
ImmutableSet.copyOf(blockTags), ImmutableSet.copyOf(blockTags),
ImmutableSet.copyOf(blockEntityTags), ImmutableSet.copyOf(blockEntityTags),
ImmutableSet.copyOf(displayBlocks)
) )
} }

View File

@ -4,6 +4,7 @@ import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableSet import com.google.common.collect.ImmutableSet
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.world.level.block.Rotation import net.minecraft.world.level.block.Rotation
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.core.collect.collect import ru.dbotthepony.mc.otm.core.collect.collect
import ru.dbotthepony.mc.otm.core.collect.map import ru.dbotthepony.mc.otm.core.collect.map
import java.util.function.Predicate import java.util.function.Predicate
@ -17,6 +18,7 @@ class ShapedMultiblockFactory(val north: ImmutableSet<Part>, val customChecks: I
val blockStateTags: ImmutableSet<Any>, val blockStateTags: ImmutableSet<Any>,
val blockTags: ImmutableSet<Any>, val blockTags: ImmutableSet<Any>,
val blockEntityTags: ImmutableSet<BlockEntityTag<*>>, val blockEntityTags: ImmutableSet<BlockEntityTag<*>>,
val displayBlocks: ImmutableSet<BlockState>,
) )
/** /**

View File

@ -6,18 +6,20 @@ import java.lang.ref.WeakReference
import java.util.* import java.util.*
import kotlin.collections.ArrayDeque import kotlin.collections.ArrayDeque
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashSet
open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : IConditionalTickable { open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : IConditionalTickable {
private val queuedAdd = ArrayDeque<N>() private val queuedAdd = ArrayDeque<N>()
private val queuedRemove = ArrayDeque<N>() private val queuedRemove = ArrayDeque<N>()
private val nodesInternal = ArrayList<N>() private val nodesInternal = ArrayList<N>()
private val nodesSetInternal = HashSet<N>()
private val conditional = ArrayList<IConditionalTickable>() private val conditional = ArrayList<IConditionalTickable>()
private val always = ArrayList<ITickable>() private val always = ArrayList<ITickable>()
private var isTicking = false private var isTicking = false
private var shouldQueueChanges = false private var shouldQueueChanges = false
protected val nodes: List<N> = Collections.unmodifiableList(nodesInternal) val nodes: List<N> = Collections.unmodifiableList(nodesInternal)
val nodesSet: Set<N> = Collections.unmodifiableSet(nodesSetInternal)
val size get() = nodesInternal.size val size get() = nodesInternal.size
var isValid = true var isValid = true
@ -36,7 +38,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
} }
fun beginTicking(node: N) { fun beginTicking(node: N) {
require(node in nodesInternal || node in queuedAdd) { "Node $node does not belong to $this" } require(node in nodesSetInternal || node in queuedAdd) { "Node $node does not belong to $this" }
if (node in queuedRemove) return if (node in queuedRemove) return
if (node is IConditionalTickable) { if (node is IConditionalTickable) {
@ -53,6 +55,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
private fun addNow(node: N) { private fun addNow(node: N) {
node.graph = this as G node.graph = this as G
nodesInternal.add(node) nodesInternal.add(node)
nodesSetInternal.add(node)
if (node is IConditionalTickable) { if (node is IConditionalTickable) {
conditional.add(node) conditional.add(node)
@ -69,6 +72,8 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
if (removeFromList) if (removeFromList)
nodesInternal.remove(node) nodesInternal.remove(node)
nodesSetInternal.remove(node)
if (node is IConditionalTickable) if (node is IConditionalTickable)
conditional.remove(node) conditional.remove(node)
else if (node is ITickable) else if (node is ITickable)
@ -108,7 +113,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
fun addNodeQueued(node: N): Boolean { fun addNodeQueued(node: N): Boolean {
check(isValid) { "$this is no longer valid" } check(isValid) { "$this is no longer valid" }
if (node in nodesInternal || node in queuedAdd) if (node in nodesSetInternal || node in queuedAdd)
return false return false
queuedAdd.add(node) queuedAdd.add(node)
@ -119,7 +124,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
fun addNode(node: N): Boolean { fun addNode(node: N): Boolean {
check(isValid) { "$this is no longer valid" } check(isValid) { "$this is no longer valid" }
if (node in nodesInternal) if (node in nodesSetInternal)
return false return false
if (shouldQueueChanges) { if (shouldQueueChanges) {
@ -137,7 +142,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
fun removeNode(node: N): Boolean { fun removeNode(node: N): Boolean {
check(isValid) { "$this is no longer valid" } check(isValid) { "$this is no longer valid" }
if (node !in nodesInternal) if (node !in nodesSetInternal)
return false return false
if (shouldQueueChanges) { if (shouldQueueChanges) {
@ -155,6 +160,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
fun retain(nodes: Set<N>) { fun retain(nodes: Set<N>) {
check(isValid) { "$this is no longer valid" } check(isValid) { "$this is no longer valid" }
queuedAdd.retainAll(nodes) queuedAdd.retainAll(nodes)
nodesInternal.removeIf { nodesInternal.removeIf {
if (it !in nodes) { if (it !in nodes) {
removeNow(it, false) removeNow(it, false)

View File

@ -26,7 +26,7 @@ import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.util.TickList import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.item.MatteryItem import ru.dbotthepony.mc.otm.item.MatteryItem
import ru.dbotthepony.mc.otm.registry.MDataComponentTypes import ru.dbotthepony.mc.otm.registry.MDataComponentTypes
import ru.dbotthepony.mc.otm.timer import ru.dbotthepony.mc.otm.once
import java.util.* import java.util.*
class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) { class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
@ -79,7 +79,7 @@ class RedstoneInteractorItem : MatteryItem(Properties().stacksTo(1)) {
for (pos in positions) { for (pos in positions) {
val shouldSendUpdate = pos !in map val shouldSendUpdate = pos !in map
val timer = context.level.timer(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) { val timer = context.level.once(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) {
map.remove(pos) map.remove(pos)
context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block) context.level.updateNeighborsAt(pos, context.level.getBlockState(pos).block)
} ?: throw RuntimeException("Timer unexpectedly ended up being null") } ?: throw RuntimeException("Timer unexpectedly ended up being null")