Compare commits

..

No commits in common. "43afa73df7743bc28578e1f8a383ffa037043dda" and "c0b8488afd853b1799d1c040dcbb417b56fed00b" have entirely different histories.

11 changed files with 47 additions and 342 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.once(time: Int, ticker: Runnable): TickList.Timer? { fun Level.timer(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,13 +15,9 @@ 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
@ -41,7 +37,7 @@ abstract class EnergyCableBlockEntity(type: BlockEntityType<*>, blockPos: BlockP
field = value field = value
if (value) { if (value) {
node.graph.addLivelyNode(node) node.graph.livelyNodes.add(node)
} }
} }
@ -59,7 +55,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.addLivelyNode(node) node.graph.livelyNodes.add(node)
} }
} }
@ -94,27 +90,7 @@ 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) {
@ -129,35 +105,6 @@ 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
@ -168,14 +115,30 @@ 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)
}
}
} }
} }
@ -191,9 +154,7 @@ 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,245 +1,37 @@
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>() {
private val livelyNodes = HashSet<EnergyCableBlockEntity.Node>() val livelyNodes = HashSet<EnergyCableBlockEntity.Node>()
private val livelyNodesList = ArrayList<EnergyCableBlockEntity.Node>()
fun addLivelyNode(node: EnergyCableBlockEntity.Node) { private val pathCache = HashMap<Pair<EnergyCableBlockEntity.Node, EnergyCableBlockEntity.Node>, Decimal?>()
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.segment.availableThroughput.coerceAtMost(Decimal.LONG_MAX_VALUE).toDouble()) val heuristics: Double = node.position.distSqr(target.position) * 0.0001 - ln(node.energyThroughput.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)
} }
} }
class SegmentPath(private val a: BlockPos, private val b: BlockPos) {
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) }
}
}
// 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() { fun invalidatePathCache() {
pathCache.clear() 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): Decimal? {
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 list = pathCache.computeIfAbsent(a to b) { ArrayList(1) } val key = a to b
if (list.isNotEmpty()) if (key in pathCache)
return list.first return pathCache[key]
// 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),
@ -265,13 +57,9 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
last = last.parent last = last.parent
} }
val touchedSegments = ArrayList<Segment>() val calc = solution.minOf { it.energyThroughput }
solution.forEach { touchedSegments.addAll(it.segment.split()) } pathCache[key] = calc
val path = SegmentPath(a.blockEntity.blockPos, b.blockEntity.blockPos) return calc
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
@ -281,31 +69,24 @@ class EnergyCableGraph : GraphNodeList<EnergyCableBlockEntity.Node, EnergyCableG
} }
// solution does not exist // solution does not exist
list.add(null) pathCache[key] = null
return null return null
} }
override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) { override fun onNodeRemoved(node: EnergyCableBlockEntity.Node) {
if (livelyNodes.remove(node)) { livelyNodes.remove(node)
check(livelyNodesList.remove(node))
}
invalidatePathCache() invalidatePathCache()
} }
override fun onNodeAdded(node: EnergyCableBlockEntity.Node) { override fun onNodeAdded(node: EnergyCableBlockEntity.Node) {
check(livelyNodes.add(node)) 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 {
livelyNodesList.shuffle(fromNode.blockEntity.level!!.random) val itr = livelyNodes.iterator()
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
@ -319,11 +100,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 path = getPath(fromNode, node, residue) val limit = getPath(fromNode, node)
hit = true hit = true
if (path != null) { if (limit != null) {
val thisReceived = it.receiveEnergy(path.transfer(residue, simulate, snapshot), simulate) val thisReceived = it.receiveEnergy(residue.coerceAtMost(limit), simulate)
received += thisReceived received += thisReceived
residue -= thisReceived residue -= thisReceived
if (!residue.isPositive) return received if (!residue.isPositive) return received
@ -335,14 +116,9 @@ 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,22 +54,6 @@ 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.entries.first { it.normal == crossproduct } right = Direction.values().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 entries) { for (value in values()) {
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,12 +1,8 @@
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,8 +85,6 @@ 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,7 +51,6 @@ 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]
@ -193,7 +192,6 @@ 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,7 +4,6 @@ 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
@ -18,7 +17,6 @@ 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,20 +6,18 @@ 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
val nodes: List<N> = Collections.unmodifiableList(nodesInternal) protected 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
@ -38,7 +36,7 @@ open class GraphNodeList<N : GraphNode<N, G>, G : GraphNodeList<N, G>> : ICondit
} }
fun beginTicking(node: N) { fun beginTicking(node: N) {
require(node in nodesSetInternal || node in queuedAdd) { "Node $node does not belong to $this" } require(node in nodesInternal || 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) {
@ -55,7 +53,6 @@ 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)
@ -72,8 +69,6 @@ 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)
@ -113,7 +108,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 nodesSetInternal || node in queuedAdd) if (node in nodesInternal || node in queuedAdd)
return false return false
queuedAdd.add(node) queuedAdd.add(node)
@ -124,7 +119,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 nodesSetInternal) if (node in nodesInternal)
return false return false
if (shouldQueueChanges) { if (shouldQueueChanges) {
@ -142,7 +137,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 nodesSetInternal) if (node !in nodesInternal)
return false return false
if (shouldQueueChanges) { if (shouldQueueChanges) {
@ -160,7 +155,6 @@ 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.once import ru.dbotthepony.mc.otm.timer
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.once(context.itemInHand.getOrDefault(MDataComponentTypes.TICK_TIMER, TickTimer.TICK_30).ticks) { val timer = context.level.timer(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")