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.FurnaceBlockEntity
import net.minecraft.world.level.block.entity.HopperBlockEntity import net.minecraft.world.level.block.entity.HopperBlockEntity
import net.minecraft.world.level.block.state.BlockState 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.multiblockEntity
import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock import ru.dbotthepony.mc.otm.core.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.registry.game.MBlockEntities import ru.dbotthepony.mc.otm.registry.game.MBlockEntities
@ -17,7 +18,7 @@ class MultiblockTestBlockEntity(blockPos: BlockPos, blockState: BlockState) : Ma
override fun tick() { override fun tick() {
super.tick() super.tick()
if (config.update(level!!)) { if (config.update(level!!) == MultiblockStatus.VALID) {
println("It matches!") println("It matches!")
println("hopper block entities: ${config.blockEntities(HOPPERS)}") 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.plus
import ru.dbotthepony.mc.otm.core.math.times import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.multiblock.BlockEntityTag 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.multiblock.shapedMultiblock
import ru.dbotthepony.mc.otm.core.util.InvalidableLazy import ru.dbotthepony.mc.otm.core.util.InvalidableLazy
import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu import ru.dbotthepony.mc.otm.menu.tech.BlackHoleGeneratorMenu
@ -169,9 +170,11 @@ class BlackHoleGeneratorBlockEntity(blockPos: BlockPos, blockState: BlockState)
val multiblock = multiblock val multiblock = multiblock
if (multiblock != null) { if (multiblock != null) {
if (!multiblock.update(level, blockRotation.back)) { val status = multiblock.update(level, blockRotation.back)
if (status == MultiblockStatus.INVALID) {
this.lastRange = findBlackHoleRange() this.lastRange = findBlackHoleRange()
} else { } else if (status == MultiblockStatus.VALID) {
val blackHole = multiblock.blockEntities(BLACK_HOLE).firstOrNull() ?: return 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) 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.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.Reference2IntMap import it.unimi.dsi.fastutil.objects.Reference2IntMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.SectionPos
import net.minecraft.core.Vec3i import net.minecraft.core.Vec3i
import net.minecraft.world.entity.player.Inventory import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player 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.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.config.MachinesConfig import ru.dbotthepony.mc.otm.config.MachinesConfig
import ru.dbotthepony.mc.otm.core.getBlockStateNow 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.Decimal
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.times 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.ShapedMultiblock
import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblockFactory import ru.dbotthepony.mc.otm.core.multiblock.ShapedMultiblockFactory
import ru.dbotthepony.mc.otm.core.multiblock.Strategy import ru.dbotthepony.mc.otm.core.multiblock.Strategy
@ -37,7 +40,7 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
private var cachedChargeEfficiency = Decimal.ONE private var cachedChargeEfficiency = Decimal.ONE
val formed: Boolean 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 // dangerous as Reference2IntMap.Entry reference live data, and its behavior is undefined once flywheel updates again
val currentlyUsedCore: Reference2IntMap.Entry<Block>? get() { val currentlyUsedCore: Reference2IntMap.Entry<Block>? get() {
@ -109,36 +112,41 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
super.tick() super.tick()
val level = level!! val level = level!!
var isUnloaded = false
if (multiblock == null || !multiblock!!.update(level, blockRotation.front)) { if (multiblock == null || multiblock!!.update(level, blockRotation.front) != MultiblockStatus.VALID) {
var height = 0 val chunk = level.chunkSource.getChunkNow(blockPos)
val base = blockPos + blockRotation.back.normal * 2
while (level.getBlockStateNow(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) { if (chunk == null) {
height++ isUnloaded = true
} } else {
var height = 0
val base = blockPos + blockRotation.back.normal * 2
if (height <= 1) { while (chunk.getBlockState(base.atY(blockPos.y + height)).block == MBlocks.FLYWHEEL_SHAFT) {
lastHeight = 0 height++
multiblock?.close() }
multiblock = null
} else if (multiblock == null || lastHeight != height) { if (height <= 1) {
lastHeight = height lastHeight = 0
multiblock?.close() multiblock?.close()
multiblock = getConfiguration(height).create(blockPos) multiblock = null
} else if (multiblock == null || lastHeight != height) {
lastHeight = height
multiblock?.close()
multiblock = getConfiguration(height).create(blockPos)
}
} }
} }
if (multiblock?.isValid == true) { if (multiblock?.isValid == MultiblockStatus.VALID) {
// energy.parent.batteryLevel *= Decimal("0.99994")
// this way energy loss is recorded in graph
val entry = multiblock!!.blocks(FLYWHEEL_MATERIAL).reference2IntEntrySet().first() val entry = multiblock!!.blocks(FLYWHEEL_MATERIAL).reference2IntEntrySet().first()
val material = FlywheelMaterials[entry.key]!! val material = FlywheelMaterials[entry.key]!!
energy.parent.maxBatteryLevel = material.storage * entry.intValue energy.parent.maxBatteryLevel = material.storage * entry.intValue
cachedChargeEfficiency = material.receiveEfficiency cachedChargeEfficiency = material.receiveEfficiency
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.PASSIVE_LOSS * material.momentumLossSpeed currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.PASSIVE_LOSS * material.momentumLossSpeed
energy.extractEnergy(currentLossPerTick, false) energy.extractEnergy(currentLossPerTick, false)
} else { } else if (!isUnloaded && multiblock?.isValid != MultiblockStatus.NOT_LOADED) {
energy.parent.maxBatteryLevel = Decimal.ZERO energy.parent.maxBatteryLevel = Decimal.ZERO
currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.ACTIVE_LOSS currentLossPerTick = energy.parent.batteryLevel * MachinesConfig.Flywheel.ACTIVE_LOSS
energy.extractEnergy(currentLossPerTick, false) energy.extractEnergy(currentLossPerTick, false)
@ -241,10 +249,13 @@ class FlywheelBatteryBlockEntity(blockPos: BlockPos, blockState: BlockState) : M
block(MBlocks.FLYWHEEL_HOUSING) block(MBlocks.FLYWHEEL_HOUSING)
if (y == height - 1) { if (y == height - 1) {
block(MBlocks.ENERGY_INPUT_INTERFACE) // wrap in subnode to narrow blockentity lookups
block(MBlocks.ENERGY_OUTPUT_INTERFACE) or {
tag(EnergyInterfaceBlockEntity.INPUT_TAG) block(MBlocks.ENERGY_INPUT_INTERFACE)
tag(EnergyInterfaceBlockEntity.OUTPUT_TAG) 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.CraftingInput
import net.minecraft.world.item.crafting.RecipeInput import net.minecraft.world.item.crafting.RecipeInput
import net.minecraft.world.level.BlockGetter import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.Level import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block 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.BlockState
import net.minecraft.world.level.block.state.StateHolder import net.minecraft.world.level.block.state.StateHolder
import net.minecraft.world.level.block.state.properties.Property 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.minecraft.world.phys.Vec3
import net.neoforged.neoforge.fluids.FluidStack import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.items.IItemHandler 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)) operator fun JsonObject.set(s: String, value: Boolean) = add(s, JsonPrimitive(value))
fun LevelAccessor.getBlockStateNow(pos: BlockPos): BlockState { 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? { fun LevelAccessor.getBlockEntityNow(pos: BlockPos): BlockEntity? {
return chunkSource.getChunkNow(SectionPos.blockToSectionCoord(pos.x), SectionPos.blockToSectionCoord(pos.z))?.getBlockEntity(pos) 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 FluidStack.isNotEmpty get() = !isEmpty
inline val ItemStack.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.BlockPos
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.Vec3i import net.minecraft.core.Vec3i
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.phys.Vec3 import net.minecraft.world.phys.Vec3
import org.joml.AxisAngle4f import org.joml.AxisAngle4f
import org.joml.Matrix3f 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: Vec3i): BlockPos = this.subtract(direction)
operator fun BlockPos.minus(direction: Direction): BlockPos = this.subtract(direction.normal) 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: Vec3i): Vec3i = this.offset(direction)
operator fun Vec3i.plus(direction: Direction): Vec3i = this.offset(direction.normal) operator fun Vec3i.plus(direction: Direction): Vec3i = this.offset(direction.normal)
operator fun Vec3i.minus(direction: Vec3i): Vec3i = this.subtract(direction) 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) * Whenever this multiblock is valid (all checks passed)
*/ */
val isValid: Boolean val isValid: MultiblockStatus
val currentDirection: Direction? val currentDirection: Direction?
val currentNodes: Map<BlockPos, IMultiblockNode> 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 it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.core.Direction import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.network.RegistryFriendlyByteBuf import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.world.level.ChunkPos
import net.minecraft.world.level.LevelAccessor import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Block import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.state.BlockState import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.chunk.LevelChunk
import net.minecraft.world.phys.AABB import net.minecraft.world.phys.AABB
import ru.dbotthepony.kommons.util.Listenable import ru.dbotthepony.kommons.util.Listenable
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 ru.dbotthepony.mc.otm.core.getBlockEntityNow 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.Vector
import ru.dbotthepony.mc.otm.core.math.compareTo
import ru.dbotthepony.mc.otm.core.math.plus import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.registryName import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState 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 * [close] MUST be called when multiblock goes out of scope
*/ */
class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMultiblockAccess, ISynchable, Closeable, GlobalBlockEntityRemovalListener { class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMultiblockAccess, ISynchable, Closeable, GlobalBlockEntityRemovalListener {
override var isValid: Boolean = false override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set private set
private val customChecks = factory.customChecks private val customChecks = factory.customChecks
@ -68,7 +70,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
get() = remotes.isNotEmpty() 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 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 var index = -1
private var blockEntity: BlockEntity? = null 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 assignedBlockEntityLists = ArrayList<BlockEntitySet<*>>(prototype.blockEntityTags.size)
private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>() private val assignedBlockStateLists = ArrayList<Reference2IntMap<BlockState>>()
private val assignedBlockLists = ArrayList<Reference2IntMap<Block>>() 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 { override fun compareTo(other: Part): Int {
val cmp = SectionPos.blockToSectionCoord(pos.x).compareTo(SectionPos.blockToSectionCoord(other.pos.x)) return chunkPos.compareTo(other.chunkPos)
if (cmp != 0) return cmp
return SectionPos.blockToSectionCoord(pos.z).compareTo(SectionPos.blockToSectionCoord(other.pos.z))
} }
init { init {
@ -219,9 +221,19 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return test return test
} }
fun test(levelAccessor: LevelAccessor): Boolean { fun test(levelAccessor: LevelAccessor): MultiblockStatus {
val blockEntity = levelAccessor.getBlockEntityNow(pos) 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 status = test0(levelAccessor, blockState, blockEntity)
val previous = this.status val previous = this.status
@ -231,7 +243,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
pushNetworkPartUpdate(this) pushNetworkPartUpdate(this)
} }
return status return if (status) MultiblockStatus.VALID else MultiblockStatus.INVALID
} }
private fun clearFull() { 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 .sorted() // group/localize parts by in-world chunks to maximize getChunk() cache hit rate
.collect(ImmutableMap.toImmutableMap({ it.pos }, { it })) .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 { init {
for ((i, part) in this.parts.values.withIndex()) { for ((i, part) in this.parts.values.withIndex()) {
part.index = i part.index = i
@ -395,7 +421,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
while (networkChangelog.size > parts.size) networkChangelog.removeLast() while (networkChangelog.size > parts.size) networkChangelog.removeLast()
} }
override var isValid: Boolean = false override var isValid: MultiblockStatus = MultiblockStatus.INVALID
private set(value) { private set(value) {
if (value != field) { if (value != field) {
field = value field = value
@ -403,7 +429,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
} }
} }
private var iterator = this.parts.values.iterator()
private var validParts = 0 private var validParts = 0
override fun compareTo(other: Config): Int { override fun compareTo(other: Config): Int {
@ -412,42 +437,46 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return cmp return cmp
} }
fun updateRemaining(levelAccessor: LevelAccessor) { fun update(levelAccessor: LevelAccessor, updateEverything: Boolean = remotes.isNotEmpty()): MultiblockStatus {
val networkVersion = networkVersion val networkVersion = networkVersion
if (!isValid) { // update rest isValid = MultiblockStatus.VALID
for (part in iterator) { validParts = 0
if (part.test(levelAccessor)) {
validParts++ 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 (updateEverything) {
if (part.test(levelAccessor)) for (part in parts) {
validParts++ val status = part.test(levelAccessor, chunk)
else { if (status === MultiblockStatus.VALID) validParts++
isValid = false isValid = isValid.or(status)
break }
} 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) { if (this.networkVersion != networkVersion) {
remotes.forEach { it.listener.run() } 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 return isValid
} }
@ -565,7 +594,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
override fun read(stream: RegistryFriendlyByteBuf) { override fun read(stream: RegistryFriendlyByteBuf) {
isRemote = true isRemote = true
isValid = stream.readBoolean() isValid = MultiblockStatus.entries[stream.readByte().toInt()]
activeConfig = configurations[stream.readUnsignedByte().toInt()] activeConfig = configurations[stream.readUnsignedByte().toInt()]
activeConfig.read(stream) activeConfig.read(stream)
} }
@ -580,7 +609,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
} }
override fun write(stream: RegistryFriendlyByteBuf) { override fun write(stream: RegistryFriendlyByteBuf) {
stream.writeBoolean(isValid) stream.writeByte(isValid.ordinal)
stream.writeByte(activeConfig.index) stream.writeByte(activeConfig.index)
remotes[activeConfig.index].write(stream) remotes[activeConfig.index].write(stream)
} }
@ -608,36 +637,36 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
} }
override val currentDirection: Direction? override val currentDirection: Direction?
get() = if (isValid) activeConfig.currentDirection else null get() = if (isValid == MultiblockStatus.VALID) activeConfig.currentDirection else null
override val boundingBox: AABB? 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> override val currentNodes: Map<BlockPos, IMultiblockNode>
get() = activeConfig.parts get() = activeConfig.parts
override fun <T : Any> blockEntities(tag: BlockEntityTag<T>): Set<T> { override fun <T : Any> blockEntities(tag: BlockEntityTag<T>): Set<T> {
if (!isValid) return setOf() if (isValid != MultiblockStatus.VALID) return setOf()
return activeConfig.blockEntities(tag) return activeConfig.blockEntities(tag)
} }
override fun blocks(tag: Any): Reference2IntMap<Block> { override fun blocks(tag: Any): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap() if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks(tag) return activeConfig.blocks(tag)
} }
override fun blockStates(tag: Any): Reference2IntMap<BlockState> { override fun blockStates(tag: Any): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap() if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates(tag) return activeConfig.blockStates(tag)
} }
override fun blocks(): Reference2IntMap<Block> { override fun blocks(): Reference2IntMap<Block> {
if (!isValid) return Reference2IntMaps.emptyMap() if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blocks() return activeConfig.blocks()
} }
override fun blockStates(): Reference2IntMap<BlockState> { override fun blockStates(): Reference2IntMap<BlockState> {
if (!isValid) return Reference2IntMaps.emptyMap() if (isValid != MultiblockStatus.VALID) return Reference2IntMaps.emptyMap()
return activeConfig.blockStates() return activeConfig.blockStates()
} }
@ -645,7 +674,7 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
return activeConfig.blockEntityRemoved(blockEntity) return activeConfig.blockEntityRemoved(blockEntity)
} }
fun update(levelAccessor: LevelAccessor): Boolean { fun update(levelAccessor: LevelAccessor): MultiblockStatus {
isUpdating = true isUpdating = true
val thisGeneration = generation val thisGeneration = generation
@ -659,25 +688,29 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
configurations.add(max) configurations.add(max)
} }
isValid = false isValid = MultiblockStatus.INVALID
var hadUnloadedChunks = false
while (configurations.isNotEmpty()) { while (configurations.isNotEmpty()) {
val config = configurations.removeFirst() val config = configurations.removeFirst()
val status = config.update(levelAccessor)
if (config.update(levelAccessor)) { if (status == MultiblockStatus.VALID) {
if (customChecks.all { it.test(config) }) { if (customChecks.all { it.test(config) }) {
activeConfig = config activeConfig = config
isValid = true isValid = MultiblockStatus.VALID
remotes.forEach { it.listener.run() } remotes.forEach { it.listener.run() }
if (thisGeneration != generation) { if (thisGeneration != generation) {
changeListeners.forEach { it.runnable.run() } changeListeners.forEach { it.runnable.run() }
} }
return true return status
} else { } else {
config.clear() 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() } changeListeners.forEach { it.runnable.run() }
} }
return false isValid = if (hadUnloadedChunks) MultiblockStatus.NOT_LOADED else MultiblockStatus.INVALID
return isValid
} finally { } finally {
isUpdating = false isUpdating = false
} }
} }
fun update(levelAccessor: LevelAccessor, direction: Direction): Boolean { fun update(levelAccessor: LevelAccessor, direction: Direction): MultiblockStatus {
isUpdating = true isUpdating = true
val thisGeneration = generation val thisGeneration = generation
try { try {
var changes = false var changes = false
if (activeConfig.currentDirection != direction && isValid) { if (activeConfig.currentDirection != direction && isValid === MultiblockStatus.VALID) {
activeConfig.clear() activeConfig.clear()
isValid = false isValid = MultiblockStatus.INVALID
changes = true changes = true
} }
@ -716,9 +750,9 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
activeConfig = config activeConfig = config
isValid = config.update(levelAccessor) isValid = config.update(levelAccessor)
if (isValid) { if (isValid === MultiblockStatus.VALID) {
isValid = customChecks.all { it.test(config) } isValid = MultiblockStatus.valid(customChecks.all { it.test(config) })
if (!isValid) config.clear() if (isValid !== MultiblockStatus.VALID) config.clear()
} }
if (changes) { if (changes) {
@ -739,6 +773,6 @@ class ShapedMultiblock(pos: BlockPos, factory: ShapedMultiblockFactory) : IMulti
changeListeners.clear() changeListeners.clear()
remotes.forEach { it.close() } remotes.forEach { it.close() }
configurations.forEach { it.clear() } 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 net.minecraft.world.entity.player.Inventory
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleGeneratorBlockEntity 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.MatteryMenu
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.DecimalInputWithFeedback import ru.dbotthepony.mc.otm.menu.input.DecimalInputWithFeedback
@ -15,7 +16,7 @@ class BlackHoleGeneratorMenu(
inventory: Inventory, inventory: Inventory,
tile: BlackHoleGeneratorBlockEntity? = null, tile: BlackHoleGeneratorBlockEntity? = null,
) : MatteryMenu(MMenus.BLACK_HOLE_GENERATOR, p_38852_, inventory, tile) { ) : 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 drawBuildingGuide = BooleanInputWithFeedback(this, tile?.let { it::drawBuildingGuide })
val energy = CombinedProfiledLevelGaugeWidget(this, tile?.energy) val energy = CombinedProfiledLevelGaugeWidget(this, tile?.energy)
val matter = CombinedProfiledLevelGaugeWidget(this, tile?.matter) val matter = CombinedProfiledLevelGaugeWidget(this, tile?.matter)