Multiblock formed/invalid/unloaded states; greatly improve multiblock scanning performance

This commit is contained in:
DBotThePony 2025-02-25 11:20:44 +07:00
parent 6ce84804d7
commit 304b6a65c8
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 193 additions and 89 deletions

View File

@ -6,6 +6,7 @@ import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.entity.FurnaceBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.multiblockEntity
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
@ -17,7 +18,7 @@ class MultiblockTestBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
override fun tick() {
super.tick()
if (config.update(level!!)) {
if (config.update(level!!) == MultiblockStatus.VALID) {
println("It matches!")
println("hopper block entities: ${config.blockEntities(HOPPERS)}")
}

View File

@ -32,6 +32,7 @@ import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
@ -169,9 +170,11 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
val multiblock = multiblock
if (multiblock != null) {
if (!multiblock.update(level, blockRotation.back)) {
val status = multiblock.update(level, blockRotation.back)
if (status == MultiblockStatus.INVALID) {
this.lastRange = findBlackHoleRange()
} else {
} else if (status == MultiblockStatus.VALID) {
val blackHole = multiblock.blockEntities(BLACK_HOLE).firstOrNull() ?: return
val matterExtracted = matter.extractMatter(if (mode == Mode.TARGET_MASS && blackHole.mass != targetMass) minOf(injectionRate, (blackHole.mass - targetMass).absoluteValue) else injectionRate, true)

View File

@ -4,6 +4,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2IntMap
import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
@ -18,9 +19,11 @@ import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.getChunkNow
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblock
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblockFactory
import ru.dbotthepony.mc.otm.core.multiblock.Strategy
@ -37,7 +40,7 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
private var cachedChargeEfficiency = Decimal.ONE
val formed: Boolean
get() = multiblock?.isValid == true
get() = multiblock?.isValid == MultiblockStatus.VALID
// dangerous as Reference2IntMap.Entry reference live data, and its behavior is undefined once flywheel updates again
val currentlyUsedCore: Reference2IntMap.Entry<Block>? get() {
@ -109,36 +112,41 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
super.tick()
val level = level!!
var isUnloaded = false
if (multiblock == null || !multiblock!!.update(level, blockRotation.front)) {
var height = 0
val base = blockPos + blockRotation.back.normal * 2
if (multiblock == null || multiblock!!.update(level, blockRotation.front) != MultiblockStatus.VALID) {
val chunk = level.chunkSource.getChunkNow(blockPos)
while (level.getBlockStateNow(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) {
height++
}
if (chunk == null) {
isUnloaded = true
} else {
var height = 0
val base = blockPos + blockRotation.back.normal * 2
if (height <= 1) {
lastHeight = 0
multiblock?.close()
multiblock = null
} else if (multiblock == null || lastHeight != height) {
lastHeight = height
multiblock?.close()
multiblock = getConfiguration(height).create(blockPos)
while (chunk.getBlockState(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) {
height++
}
if (height <= 1) {
lastHeight = 0
multiblock?.close()
multiblock = null
} else if (multiblock == null || lastHeight != height) {
lastHeight = height
multiblock?.close()
multiblock = getConfiguration(height).create(blockPos)
}
}
}
if (multiblock?.isValid == true) {
// energy.parent.batteryLevel *= Decimal("0.99994")
// this way energy loss is recorded in graph
if (multiblock?.isValid == MultiblockStatus.VALID) {
val entry = multiblock!!.blocks(FLYWHEEL_MATERIAL).reference2IntEntrySet().first()
val material = FlywheelMaterials[entry.key]!!
energy.parent.maxBatteryLevel = material.storage * entry.intValue
cachedChargeEfficiency = material.receiveEfficiency
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.PASSIVE_LOSS * material.momentumLossSpeed
energy.extractEnergy(currentLossPerTick, false)
} else {
} else if (!isUnloaded && multiblock?.isValid != MultiblockStatus.NOT_LOADED) {
energy.parent.maxBatteryLevel = Decimal.ZERO
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.ACTIVE_LOSS
energy.extractEnergy(currentLossPerTick, false)
@ -241,10 +249,13 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
block(MBlocks.FLYWHEEL_HOUSING)
if (y == height - 1) {
block(MBlocks.ENERGY_INPUT_INTERFACE)
block(MBlocks.ENERGY_OUTPUT_INTERFACE)
tag(EnergyInterfaceBlockEntity.INPUT_TAG)
tag(EnergyInterfaceBlockEntity.OUTPUT_TAG)
// wrap in subnode to narrow blockentity lookups
or {
block(MBlocks.ENERGY_INPUT_INTERFACE)
block(MBlocks.ENERGY_OUTPUT_INTERFACE)
tag(EnergyInterfaceBlockEntity.INPUT_TAG)
tag(EnergyInterfaceBlockEntity.OUTPUT_TAG)
}
}
}
}

View File

@ -36,6 +36,7 @@ import net.minecraft.world.item.component.ItemAttributeModifiers
import net.minecraft.world.item.crafting.CraftingInput
import net.minecraft.world.item.crafting.RecipeInput
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
@ -44,6 +45,8 @@ import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateHolder
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.chunk.ChunkSource
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.Vec3
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.items.IItemHandler
@ -127,13 +130,25 @@ operator fun JsonObject.set(s: String, value: Number) = add(s, JsonPrimitive(val
operator fun JsonObject.set(s: String, value: Boolean) = add(s, JsonPrimitive(value))
fun LevelAccessor.getBlockStateNow(pos: BlockPos): BlockState {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos) ?: Blocks.AIR.defaultBlockState()
return getBlockStateNowOrNull(pos) ?: Blocks.AIR.defaultBlockState()
}
fun LevelAccessor.getBlockStateNowOrNull(pos: BlockPos): BlockState? {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockState(pos)
}
fun LevelAccessor.getBlockEntityNow(pos: BlockPos): BlockEntity? {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockEntity(pos)
}
fun ChunkSource.getChunkNow(pos: BlockPos): LevelChunk? {
return getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))
}
fun ChunkSource.getChunkNow(pos: ChunkPos): LevelChunk? {
return getChunkNow(pos.x, pos.z)
}
inline val FluidStack.isNotEmpty get() = !isEmpty
inline val ItemStack.isNotEmpty get() = !isEmpty

View File

@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.Vec3i
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f
import org.joml.Matrix3f
@ -420,6 +421,12 @@ operator fun BlockPos.plus(direction: Direction): BlockPos = this.offset(directi
operator fun BlockPos.minus(direction: Vec3i): BlockPos = this.subtract(direction)
operator fun BlockPos.minus(direction: Direction): BlockPos = this.subtract(direction.normal)
operator fun ChunkPos.compareTo(other: ChunkPos): Int {
val cmp = x.compareTo(other.x)
if (cmp != 0) return cmp
return z.compareTo(other.z)
}
operator fun Vec3i.plus(direction: Vec3i): Vec3i = this.offset(direction)
operator fun Vec3i.plus(direction: Direction): Vec3i = this.offset(direction.normal)
operator fun Vec3i.minus(direction: Vec3i): Vec3i = this.subtract(direction)

View File

@ -12,7 +12,7 @@ interface IMultiblockAccess {
/**
* Whenever this multiblock is valid (all checks passed)
*/
val isValid: Boolean
val isValid: MultiblockStatus
val currentDirection: Direction?
val currentNodes: Map<BlockPos, IMultiblockNode>

View File

@ -0,0 +1,32 @@
package ru.dbotthepony.mc.otm.core.multiblock
enum class MultiblockStatus {
/**
* Multiblock is functional, configuration is valid
*/
VALID,
/**
* Multiblock status is unknown because it require to check
*/
NOT_LOADED,
/**
* Multiblock is invalid
*/
INVALID;
fun or(other: MultiblockStatus): MultiblockStatus {
if (ordinal <= other.ordinal) {
return this
} else {
return other
}
}
companion object {
fun valid(status: Boolean): MultiblockStatus {
return if (status) VALID else INVALID
}
}
}

View File

@ -7,19 +7,21 @@ import it.unimi.dsi.fastutil.objects.Reference2IntMaps
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.AABB
import ru.dbotthepony.kommons.util.Listenable
import ru.dbotthepony.mc.otm.core.collect.collect
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.getBlockEntityNow
import ru.dbotthepony.mc.otm.core.getBlockStateNow
import ru.dbotthepony.mc.otm.core.getBlockStateNowOrNull
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.compareTo
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
@ -35,7 +37,7 @@ import kotlin.collections.HashMap
* [close] MUST be called when multiblock goes out of scope
*/
class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMultiblockAccess, ISynchable, Closeable, GlobalBlockEntityRemovalListener {
override var isValid: Boolean = false
override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set
private val customChecks = factory.customChecks
@ -68,7 +70,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
get() = remotes.isNotEmpty()
private inner class Config(override val currentDirection: Direction, val pos: BlockPos, parts: Collection<ShapedMultiblockFactory.Part>) : IMultiblockAccess, ISynchable, GlobalBlockEntityRemovalListener, Comparable<Config> {
private inner class Part(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part) : Comparable<Part>, IMultiblockNode {
private inner class Part private constructor(override val pos: BlockPos, val prototype: ShapedMultiblockFactory.Part, val chunkPos: ChunkPos) : Comparable<Part>, IMultiblockNode {
constructor(pos: BlockPos, prototype: ShapedMultiblockFactory.Part) : this(pos, prototype, ChunkPos(pos))
var index = -1
private var blockEntity: BlockEntity? = null
@ -76,12 +80,10 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
private val assignedBlockEntityLists = ArrayList<BlockEntitySet<*>>(prototype.blockEntityTags.size)
private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val assignedBlockLists = ArrayList<Reference2IntMap<Block>>()
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it) }.collect(ImmutableList.toImmutableList()) }
private val children: ImmutableList<Part> by lazy(LazyThreadSafetyMode.NONE) { prototype.children.iterator().map { Part(pos, it, chunkPos) }.collect(ImmutableList.toImmutableList()) }
override fun compareTo(other: Part): Int {
val cmp = SectionPos.blockToSectionCoord(pos.x).compareTo(SectionPos.blockToSectionCoord(other.pos.x))
if (cmp != 0) return cmp
return SectionPos.blockToSectionCoord(pos.z).compareTo(SectionPos.blockToSectionCoord(other.pos.z))
return chunkPos.compareTo(other.chunkPos)
}
init {
@ -219,9 +221,19 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return test
}
fun test(levelAccessor: LevelAccessor): Boolean {
fun test(levelAccessor: LevelAccessor): MultiblockStatus {
val blockEntity = levelAccessor.getBlockEntityNow(pos)
val blockState = levelAccessor.getBlockStateNow(pos)
val blockState = levelAccessor.getBlockStateNowOrNull(pos) ?: return MultiblockStatus.NOT_LOADED
return test(levelAccessor, blockState, blockEntity)
}
fun test(levelAccessor: LevelAccessor, chunk: LevelChunk): MultiblockStatus {
val blockEntity = chunk.getBlockEntity(pos)
val blockState = chunk.getBlockState(pos)
return test(levelAccessor, blockState, blockEntity)
}
fun test(levelAccessor: LevelAccessor, blockState: BlockState, blockEntity: BlockEntity?): MultiblockStatus {
val status = test0(levelAccessor, blockState, blockEntity)
val previous = this.status
@ -231,7 +243,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
pushNetworkPartUpdate(this)
}
return status
return if (status) MultiblockStatus.VALID else MultiblockStatus.INVALID
}
private fun clearFull() {
@ -371,6 +383,20 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
.sorted() // group/localize parts by in-world chunks to maximize getChunk() cache hit rate
.collect(ImmutableMap.toImmutableMap({ it.pos }, { it }))
private val partsByChunk: ImmutableMap<ChunkPos, ImmutableList<Part>>
init {
val builder = HashMap<ChunkPos, ArrayList<Part>>()
for (part in this.parts.values) {
builder.computeIfAbsent(part.chunkPos) { ArrayList() }.add(part)
}
partsByChunk = builder.entries.stream()
.map { it.key to ImmutableList.copyOf(it.value) }
.collect(ImmutableMap.toImmutableMap({ it.first }, { it.second }))
}
init {
for ((i, part) in this.parts.values.withIndex()) {
part.index = i
@ -395,7 +421,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
while (networkChangelog.size > parts.size) networkChangelog.removeLast()
}
override var isValid: Boolean = false
override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set(value) {
if (value != field) {
field = value
@ -403,7 +429,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
}
private var iterator = this.parts.values.iterator()
private var validParts = 0
override fun compareTo(other: Config): Int {
@ -412,42 +437,46 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return cmp
}
fun updateRemaining(levelAccessor: LevelAccessor) {
fun update(levelAccessor: LevelAccessor, updateEverything: Boolean = remotes.isNotEmpty()): MultiblockStatus {
val networkVersion = networkVersion
if (!isValid) { // update rest
for (part in iterator) {
if (part.test(levelAccessor)) {
validParts++
}
isValid = MultiblockStatus.VALID
validParts = 0
for ((chunkPos, parts) in partsByChunk) {
val chunk = levelAccessor.chunkSource.getChunkNow(chunkPos.x, chunkPos.z)
if (chunk == null) {
isValid = MultiblockStatus.NOT_LOADED
break
}
} else {
for (part in iterator) {
if (part.test(levelAccessor))
validParts++
else {
isValid = false
break
if (updateEverything) {
for (part in parts) {
val status = part.test(levelAccessor, chunk)
if (status === MultiblockStatus.VALID) validParts++
isValid = isValid.or(status)
}
} else {
for (part in parts) {
val status = part.test(levelAccessor, chunk)
if (status === MultiblockStatus.VALID)
validParts++
else {
isValid = status
break
}
}
}
}
if (isValid != MultiblockStatus.VALID) clear()
if (this.networkVersion != networkVersion) {
remotes.forEach { it.listener.run() }
}
}
fun update(levelAccessor: LevelAccessor, updateEverything: Boolean = remotes.isNotEmpty()): Boolean {
isValid = true
iterator = this.parts.values.iterator()
validParts = 0
updateRemaining(levelAccessor)
if (updateEverything && iterator.hasNext()) {
updateRemaining(levelAccessor)
}
if (!isValid) clear()
return isValid
}
@ -565,7 +594,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true
isValid = stream.readBoolean()
isValid = MultiblockStatus.entries[stream.readByte().toInt()]
activeConfig = configurations[stream.readUnsignedByte().toInt()]
activeConfig.read(stream)
}
@ -580,7 +609,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
override fun write(stream: RegistryFriendlyByteBuf) {
stream.writeBoolean(isValid)
stream.writeByte(isValid.ordinal)
stream.writeByte(activeConfig.index)
remotes[activeConfig.index].write(stream)
}
@ -608,36 +637,36 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
}
override val currentDirection: Direction?
get() = if (isValid) activeConfig.currentDirection else null
get() = if (isValid == MultiblockStatus.VALID) activeConfig.currentDirection else null
override val boundingBox: AABB?
get() = if (isValid) activeConfig.boundingBox else null
get() = if (isValid == MultiblockStatus.VALID) activeConfig.boundingBox else null
override val currentNodes: Map<BlockPos, IMultiblockNode>
get() = activeConfig.parts
override fun <T : Any> blockEntities(tag: BlockEntityTag<T>): Set<T> {
if (!isValid) return setOf()
if (isValid != MultiblockStatus.VALID) return setOf()
return activeConfig.blockEntities(tag)
}
override fun blocks(tag: Any): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks(tag)
}
override fun blockStates(tag: Any): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates(tag)
}
override fun blocks(): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks()
}
override fun blockStates(): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap()
if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates()
}
@ -645,7 +674,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return activeConfig.blockEntityRemoved(blockEntity)
}
fun update(levelAccessor: LevelAccessor): Boolean {
fun update(levelAccessor: LevelAccessor): MultiblockStatus {
isUpdating = true
val thisGeneration = generation
@ -659,25 +688,29 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
configurations.add(max)
}
isValid = false
isValid = MultiblockStatus.INVALID
var hadUnloadedChunks = false
while (configurations.isNotEmpty()) {
val config = configurations.removeFirst()
val status = config.update(levelAccessor)
if (config.update(levelAccessor)) {
if (status == MultiblockStatus.VALID) {
if (customChecks.all { it.test(config) }) {
activeConfig = config
isValid = true
isValid = MultiblockStatus.VALID
remotes.forEach { it.listener.run() }
if (thisGeneration != generation) {
changeListeners.forEach { it.runnable.run() }
}
return true
return status
} else {
config.clear()
}
} else if (status == MultiblockStatus.NOT_LOADED) {
hadUnloadedChunks = true
}
}
@ -685,22 +718,23 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
changeListeners.forEach { it.runnable.run() }
}
return false
isValid = if (hadUnloadedChunks) MultiblockStatus.NOT_LOADED else MultiblockStatus.INVALID
return isValid
} finally {
isUpdating = false
}
}
fun update(levelAccessor: LevelAccessor, direction: Direction): Boolean {
fun update(levelAccessor: LevelAccessor, direction: Direction): MultiblockStatus {
isUpdating = true
val thisGeneration = generation
try {
var changes = false
if (activeConfig.currentDirection != direction && isValid) {
if (activeConfig.currentDirection != direction && isValid === MultiblockStatus.VALID) {
activeConfig.clear()
isValid = false
isValid = MultiblockStatus.INVALID
changes = true
}
@ -716,9 +750,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
activeConfig = config
isValid = config.update(levelAccessor)
if (isValid) {
isValid = customChecks.all { it.test(config) }
if (!isValid) config.clear()
if (isValid === MultiblockStatus.VALID) {
isValid = MultiblockStatus.valid(customChecks.all { it.test(config) })
if (isValid !== MultiblockStatus.VALID) config.clear()
}
if (changes) {
@ -739,6 +773,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
changeListeners.clear()
remotes.forEach { it.close() }
configurations.forEach { it.clear() }
isValid = false
isValid = MultiblockStatus.INVALID
}
}

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.menu.tech
import net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity
import ru.dbotthepony.mc.otm.core.multiblock.MultiblockStatus
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.DecimalInputWithFeedback
@ -15,7 +16,7 @@ class BlackHoleGeneratorMenu(
inventory: Inventory,
tile: BlackHoleGeneratorBlockEntity? = null,
) : MatteryMenu(MMenus.BLACK_HOLE_GENERATOR, p_38852_, inventory, tile) {
val formed = mSynchronizer.computedBoolean(BooleanSupplier { tile?.multiblock?.isValid ?: false })
val formed = mSynchronizer.computedBoolean(BooleanSupplier { tile?.multiblock?.isValid == MultiblockStatus.VALID })
val drawBuildingGuide = BooleanInputWithFeedback(this, tile?.let { it::drawBuildingGuide })
val energy = CombinedProfiledLevelGaugeWidget(this, tile?.energy)
val matter = CombinedProfiledLevelGaugeWidget(this, tile?.matter)