Improve ticker, actually remove nodes from storage graph

This commit is contained in:
DBotThePony 2022-03-21 23:40:22 +07:00
parent d4aa8cd7ee
commit 7152108cc8
Signed by: DBot
GPG Key ID: DCC23B5715498507
12 changed files with 261 additions and 88 deletions

View File

@ -1,89 +1,177 @@
@file:Suppress("unused", "UNUSED_PARAMETER")
package ru.dbotthepony.mc.otm
import net.minecraft.client.multiplayer.ClientLevel
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.ServerTickEvent
import net.minecraftforge.event.TickEvent.WorldTickEvent
import net.minecraftforge.event.server.ServerAboutToStartEvent
import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.eventbus.api.EventPriority
import net.minecraftforge.eventbus.api.SubscribeEvent
import net.minecraftforge.fml.LogicalSide
import java.util.*
import java.util.function.Consumer
import java.util.function.Supplier
import kotlin.collections.ArrayList
private val tick_until = WeakHashMap<Level, ArrayList<Supplier<Boolean>>>()
private val tick_once = WeakHashMap<Level, ArrayList<Runnable>>()
private val preServerTick = ArrayList<IConditionalTickable>()
private val postServerTick = ArrayList<IConditionalTickable>()
private val preServerTickOnce = ArrayList<ITickable>()
private val postServerTickOnce = ArrayList<ITickable>()
@SubscribeEvent(priority = EventPriority.HIGHEST)
@Suppress("unused")
fun onServerStarting(event: ServerAboutToStartEvent?) {
tick_until.clear()
private val preWorldTick = WeakHashMap<Level, ArrayList<IConditionalTickable>>()
private val postWorldTick = WeakHashMap<Level, ArrayList<IConditionalTickable>>()
private val preWorldTickOnce = WeakHashMap<Level, ArrayList<ITickable>>()
private val postWorldTickOnce = WeakHashMap<Level, ArrayList<ITickable>>()
fun interface ITickable {
fun tick()
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
@Suppress("unused")
fun onServerStopping(event: ServerStoppingEvent?) {
tick_until.clear()
interface IConditionalTickable : ITickable {
/**
* Once this returns false, it should stay false.
*
* If it suddenly turns true after being false, result is undefined.
*/
val canTick: Boolean
}
@SubscribeEvent(priority = EventPriority.LOWEST)
@Suppress("unused")
fun onPreTick(event: WorldTickEvent) {
if (event.phase != TickEvent.Phase.START || event.side != LogicalSide.SERVER) return
fun onServerTick(event: ServerTickEvent) {
if (event.phase === TickEvent.Phase.START) {
for (i in preServerTick.size - 1 downTo 0) {
val ticker = preServerTick[i]
// удаляем список сразу что бы если кто-либо добавит туда элементы у нас была "копия"
val until = tick_until.remove(event.world)
if (until != null) {
for (i in until.indices.reversed()) {
if (until[i].get()) {
until.removeAt(i)
if (!ticker.canTick) {
preServerTick.removeAt(i)
} else {
ticker.tick()
}
}
if (until.size != 0) {
val replaced = tick_until.put(event.world, until)
for (i in preServerTickOnce.size - 1 downTo 0) {
preServerTickOnce[i].tick()
preServerTickOnce.removeAt(i)
}
} else {
for (i in postServerTick.size - 1 downTo 0) {
val ticker = postServerTick[i]
if (replaced != null) {
until.addAll(replaced)
if (!ticker.canTick) {
postServerTick.removeAt(i)
} else {
ticker.tick()
}
}
for (i in postServerTickOnce.size - 1 downTo 0) {
postServerTickOnce[i].tick()
postServerTickOnce.removeAt(i)
}
}
}
fun addPreServerTicker(ticker: IConditionalTickable) {
preServerTick.add(ticker)
}
fun addPostServerTicker(ticker: IConditionalTickable) {
postServerTick.add(ticker)
}
fun addPreServerTickerOnce(ticker: ITickable) {
preServerTickOnce.add(ticker)
}
fun addPostServerTickerOnce(ticker: ITickable) {
postServerTickOnce.add(ticker)
}
@SubscribeEvent(priority = EventPriority.LOWEST)
fun onWorldTick(event: WorldTickEvent) {
if (event.phase === TickEvent.Phase.START) {
val it = preWorldTick[event.world]
if (it != null) {
for (i in it.size - 1 downTo 0) {
val ticker = it[i]
if (!ticker.canTick) {
it.removeAt(i)
} else {
ticker.tick()
}
}
}
val it2 = preWorldTickOnce.remove(event.world)
if (it2 != null) {
for (i in it2.size - 1 downTo 0) {
it2[i].tick()
it2.removeAt(i)
}
}
} else {
val it = postWorldTick[event.world]
if (it != null) {
for (i in it.size - 1 downTo 0) {
val ticker = it[i]
if (!ticker.canTick) {
it.removeAt(i)
} else {
ticker.tick()
}
}
}
val it2 = postWorldTickOnce.remove(event.world)
if (it2 != null) {
for (i in it2.size - 1 downTo 0) {
it2[i].tick()
it2.removeAt(i)
}
}
}
val once = tick_once.remove(event.world)
if (once != null)
for (ticker in once)
ticker.run()
}
fun tickUntil(level: Level, ticker: Supplier<Boolean>) {
tick_until.computeIfAbsent(level) { ArrayList() }.add(ticker)
fun addPreWorldTicker(level: Level, ticker: IConditionalTickable) {
preWorldTick.computeIfAbsent(level) { ArrayList() }.add(ticker)
}
fun tickUntilServer(level: Level, ticker: Supplier<Boolean>) {
if (level is ServerLevel)
tick_until.computeIfAbsent(level) { ArrayList() }.add(ticker)
fun addPostWorldTicker(level: Level, ticker: IConditionalTickable) {
postWorldTick.computeIfAbsent(level) { ArrayList() }.add(ticker)
}
fun tickUntilClient(level: Level, ticker: Supplier<Boolean>) {
if (level is ClientLevel)
tick_until.computeIfAbsent(level) { ArrayList() }.add(ticker)
fun addPreWorldTickerOnce(level: Level, ticker: ITickable) {
preWorldTickOnce.computeIfAbsent(level) { ArrayList() }.add(ticker)
}
fun tickOnce(level: Level, ticker: Runnable) {
tick_once.computeIfAbsent(level) { ArrayList() }.add(ticker)
fun addPostWorldTickerOnce(level: Level, ticker: ITickable) {
postWorldTickOnce.computeIfAbsent(level) { ArrayList() }.add(ticker)
}
fun tickOnceServer(level: Level, ticker: Runnable) {
if (level is ServerLevel)
tick_once.computeIfAbsent(level) { ArrayList() }.add(ticker)
private fun clear() {
preServerTick.clear()
postServerTick.clear()
preWorldTick.clear()
postWorldTick.clear()
preServerTickOnce.clear()
postServerTickOnce.clear()
preWorldTickOnce.clear()
postWorldTickOnce.clear()
}
fun tickOnceClient(level: Level, ticker: Runnable) {
if (level is ClientLevel)
tick_once.computeIfAbsent(level) { ArrayList() }.add(ticker)
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onServerStarting(event: ServerAboutToStartEvent?) {
clear()
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
fun onServerStopping(event: ServerStoppingEvent?) {
clear()
}

View File

@ -2,7 +2,6 @@ package ru.dbotthepony.mc.otm.block
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.BlockGetter
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.Block
@ -14,11 +13,11 @@ import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.level.block.state.StateDefinition
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.addPreWorldTickerOnce
import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.worker.WorkerState
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.shapes.BlockShapes
import ru.dbotthepony.mc.otm.tickOnceServer
class ChemicalGeneratorBlock : RotatableMatteryBlock(), EntityBlock {
override fun newBlockEntity(p_153215_: BlockPos, p_153216_: BlockState): BlockEntity {
@ -55,7 +54,7 @@ class ChemicalGeneratorBlock : RotatableMatteryBlock(), EntityBlock {
val tile = level.getBlockEntity(pos)
if (tile is ChemicalGeneratorBlockEntity) {
tickOnceServer(level) {
addPreWorldTickerOnce(level) {
tile.checkSurroundings()
}
}

View File

@ -22,6 +22,7 @@ import net.minecraft.world.level.material.MaterialColor
import net.minecraft.world.level.material.PushReaction
import net.minecraft.world.phys.shapes.CollisionContext
import net.minecraft.world.phys.shapes.VoxelShape
import ru.dbotthepony.mc.otm.addPreWorldTickerOnce
import ru.dbotthepony.mc.otm.block.entity.GravitationStabilizerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.worker.WorkerState
@ -30,7 +31,6 @@ import ru.dbotthepony.mc.otm.core.times
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.registry.MBlocks
import ru.dbotthepony.mc.otm.shapes.BlockShapes
import ru.dbotthepony.mc.otm.tickOnceServer
import kotlin.math.PI
private val props = BlockBehaviour.Properties.of(Material.STONE, MaterialColor.COLOR_BLUE).requiresCorrectToolForDrops().strength(3f, 600.0f)
@ -120,8 +120,8 @@ class BlockGravitationStabilizer : RotatableMatteryBlock(props), EntityBlock {
) {
super.neighborChanged(state, level, pos, sender, sender_pos, flag)
tickOnceServer(level) {
if (level.getBlockState(pos).block !is BlockGravitationStabilizer) return@tickOnceServer
addPreWorldTickerOnce(level) {
if (level.getBlockState(pos).block !is BlockGravitationStabilizer) return@addPreWorldTickerOnce
val bb = getBoundingBlock(level, state, pos)
if (bb.block !is BlockGravitationStabilizerLens) {
@ -181,8 +181,8 @@ class BlockGravitationStabilizerLens : RotatableMatteryBlock(props) {
) {
super.neighborChanged(state, level, pos, sender, sender_pos, flag)
tickOnceServer(level) {
if (level.getBlockState(pos).block !is BlockGravitationStabilizerLens) return@tickOnceServer
addPreWorldTickerOnce(level) {
if (level.getBlockState(pos).block !is BlockGravitationStabilizerLens) return@addPreWorldTickerOnce
val bb = getBoundingBlock(level, state, pos)
if (bb.block !is BlockGravitationStabilizer) {

View File

@ -9,10 +9,9 @@ import net.minecraft.world.level.block.entity.BlockEntity
import net.minecraft.world.level.block.entity.BlockEntityTicker
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import ru.dbotthepony.mc.otm.block.entity.ChemicalGeneratorBlockEntity
import ru.dbotthepony.mc.otm.addPreWorldTickerOnce
import ru.dbotthepony.mc.otm.block.entity.StorageBusBlockEntity
import ru.dbotthepony.mc.otm.registry.MBlockEntities
import ru.dbotthepony.mc.otm.tickOnceServer
import ru.dbotthepony.mc.otm.unaryMinus
class StorageBusBlock : RotatableMatteryBlock(), EntityBlock {
@ -51,7 +50,7 @@ class StorageBusBlock : RotatableMatteryBlock(), EntityBlock {
val tile = level.getBlockEntity(pos)
if (tile is StorageBusBlockEntity) {
tickOnceServer(level) {
addPreWorldTickerOnce(level) {
tile.checkSurroundings()
}
}

View File

@ -81,7 +81,7 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
override val defaultDisplayName: Component
get() = MACHINE_NAME
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu? {
override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu {
return DriveRackMenu(containerID, inventory, this)
}
@ -101,6 +101,11 @@ class DriveRackBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
cell.revive()
}
override fun setRemoved() {
super.setRemoved()
cell.destroy(level)
}
companion object {
private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.drive_rack")
private val STORAGE = ImpreciseFraction(80_000)

View File

@ -59,6 +59,11 @@ class ItemMonitorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
cell.revive()
}
override fun setRemoved() {
super.setRemoved()
cell.destroy(level)
}
companion object {
private val MACHINE_NAME = TranslatableComponent("block.overdrive_that_matters.item_monitor")
}

View File

@ -22,6 +22,7 @@ import net.minecraft.nbt.ByteTag
import net.minecraft.network.chat.Component
import net.minecraft.world.level.Level
import net.minecraftforge.common.capabilities.Capability
import ru.dbotthepony.mc.otm.addPreWorldTickerOnce
import ru.dbotthepony.mc.otm.ifHas
import ru.dbotthepony.mc.otm.set
@ -70,32 +71,32 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
protected fun tickOnce(func: Runnable) {
val level = level
if (level != null) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func.run() }
if (level != null) addPreWorldTickerOnce(level) { if (!isRemoved) func.run() }
}
protected fun tickOnceServer(func: Runnable) {
val level = level
if (level is ServerLevel) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func.run() }
if (level is ServerLevel) addPreWorldTickerOnce(level) { if (!isRemoved) func.run() }
}
protected fun tickOnceClient(func: Runnable) {
val level = level
if (level is ClientLevel) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func.run() }
if (level is ClientLevel) addPreWorldTickerOnce(level) { if (!isRemoved) func.run() }
}
protected fun tickOnce(func: (Level) -> Unit) {
val level = level
if (level != null) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func(level) }
if (level != null) addPreWorldTickerOnce(level) { if (!isRemoved) func(level) }
}
protected fun tickOnceServer(func: (ServerLevel) -> Unit) {
val level = level
if (level is ServerLevel) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func(level) }
if (level is ServerLevel) addPreWorldTickerOnce(level) { if (!isRemoved) func(level) }
}
protected fun tickOnceClient(func: (ClientLevel) -> Unit) {
val level = level
if (level is ClientLevel) ru.dbotthepony.mc.otm.tickOnce(level) { if (!isRemoved) func(level) }
if (level is ClientLevel) addPreWorldTickerOnce(level) { if (!isRemoved) func(level) }
}
protected fun <T> getAndBind(

View File

@ -333,6 +333,11 @@ class StorageBusBlockEntity(blockPos: BlockPos, blockState: BlockState) : Matter
component?.scan()
}
override fun setRemoved() {
super.setRemoved()
cell.destroy(level)
}
fun checkSurroundings() {
val front = blockPos + blockState.getValue(RotatableMatteryBlock.FACING_FULL)
val storage = level?.getBlockEntity(front)?.getCapability(CapabilityItemHandler.ITEM_HANDLER_CAPABILITY)?.let { if (it.isPresent) it else null }

View File

@ -1,27 +1,51 @@
package ru.dbotthepony.mc.otm.graph
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
import net.minecraft.core.BlockPos
import net.minecraft.core.Direction
import net.minecraft.core.SectionPos
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.IConditionalTickable
import ru.dbotthepony.mc.otm.addPreWorldTicker
import ru.dbotthepony.mc.otm.core.plus
import ru.dbotthepony.mc.otm.tickUntil
import java.util.*
import kotlin.collections.ArrayList
abstract class Abstract6Graph<T> {
abstract class Abstract6Graph<T> : IConditionalTickable {
@JvmField
protected val _nodes = ArrayList<Graph6Node<T>>()
fun size() = _nodes.size
private val tickable = ArrayList<Graph6Node<T>>()
@JvmField
val nodes = Collections.unmodifiableCollection(_nodes)
val nodes: Collection<Graph6Node<T>> = Collections.unmodifiableCollection(_nodes)
/**
* Allows storing arbitrary data by external code
*/
@JvmField
val userData = Object2ObjectAVLTreeMap<UUID, Any>()
abstract fun onNodeRemoved(node: Graph6Node<T>)
abstract fun onNodeAdded(node: Graph6Node<T>)
override val canTick: Boolean
get() = _nodes.size > 0
override fun tick() {
for (i in tickable.size - 1 downTo 0) {
val node = tickable[i]
if (node.canTick) {
node.tick()
} else {
tickable.removeAt(i)
}
}
}
fun removeNode(node: Graph6Node<T>) {
if (!_nodes.contains(node))
throw IllegalStateException("Not containing node $node")
@ -29,6 +53,10 @@ abstract class Abstract6Graph<T> {
_nodes.remove(node)
node.graph = null
onNodeRemoved(node)
if (node.canTick) {
tickable.remove(node)
}
}
fun addNode(node: Graph6Node<T>) {
@ -38,6 +66,10 @@ abstract class Abstract6Graph<T> {
_nodes.add(node)
node.graph = this
onNodeAdded(node)
if (node.canTick) {
tickable.add(node)
}
}
fun merge(other: Abstract6Graph<T>): Abstract6Graph<T> {
@ -65,9 +97,16 @@ abstract class Abstract6Graph<T> {
nodeGetter: (BlockEntity) -> Graph6Node<T>?,
factory: () -> Abstract6Graph<T>
) {
tickUntil(level) {
!node.valid || discover(level, blockPos, node, nodeGetter, factory)
}
addPreWorldTicker(level, object : IConditionalTickable {
override fun tick() {
discovered = discover(level, blockPos, node, nodeGetter, factory)
}
private var discovered = false
override val canTick: Boolean
get() = !discovered && node.valid
})
}
@JvmStatic

View File

@ -1,6 +1,8 @@
package ru.dbotthepony.mc.otm.graph
import net.minecraft.core.Direction
import ru.dbotthepony.mc.otm.IConditionalTickable
import ru.dbotthepony.mc.otm.ITickable
interface GraphNodeListener {
fun onNeighbourTop(node: Graph6Node<*>) = onNeighbour(node, Direction.UP)
@ -23,7 +25,7 @@ interface GraphNodeListener {
}
// Вершина графа, содержит то, к какому графу принадлежит, соседей и своё "значение"
class Graph6Node<T>(@JvmField val value: T, graph: Abstract6Graph<T>? = null) {
class Graph6Node<T>(@JvmField val value: T, graph: Abstract6Graph<T>? = null) : IConditionalTickable {
var graph: Abstract6Graph<T>? = null
init {
@ -182,6 +184,23 @@ class Graph6Node<T>(@JvmField val value: T, graph: Abstract6Graph<T>? = null) {
}
}
override val canTick: Boolean
get() {
return if (value is IConditionalTickable) {
value.canTick
} else {
value is ITickable
}
}
override fun tick() {
if (value is ITickable) {
value.tick()
} else {
throw ClassCastException("$value is not tickable")
}
}
var seen: Int = 0
private fun _flood(): List<GraphFlooder<T>> {

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.graph.storage
import net.minecraft.world.level.Level
import net.minecraftforge.common.util.LazyOptional
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.storage.IStorage
@ -117,4 +118,12 @@ open class BasicStorageGraphNode : IStorageGraphNode {
fun get(): LazyOptional<IStorageGraphNode> {
return if (valid) resolver else LazyOptional.empty()
}
fun destroy(level: Level?) {
if (level != null) {
storageNode.destroy { StorageNetworkGraph(level) }
} else {
storageGraph?.removeNode(storageNode)
}
}
}

View File

@ -3,20 +3,19 @@ package ru.dbotthepony.mc.otm.graph.storage
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntity
import ru.dbotthepony.mc.otm.addPreServerTicker
import ru.dbotthepony.mc.otm.addPreWorldTicker
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.graph.Abstract6Graph
import ru.dbotthepony.mc.otm.graph.Graph6Node
import ru.dbotthepony.mc.otm.orNull
import ru.dbotthepony.mc.otm.storage.*
class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
class StorageNetworkGraph(private val level: Level) : Abstract6Graph<IStorageGraphNode>() {
private val virtualComponents = Object2ObjectArrayMap<StorageStackType<*>, VirtualComponent<*>>()
fun <T : IStorageStack> insertStack(obj: T, simulate: Boolean): T {
return (getVirtualComponent(StorageRegistry.get(obj::class.java)) as VirtualComponent<T>).insertStack(obj, simulate)
}
/**
* Returns a [VirtualComponent] representing [type] storage
*/
@ -31,6 +30,8 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
return virtualComponents.computeIfAbsent(StorageRegistry.get(type), Object2ObjectFunction { VirtualComponent(type) }) as VirtualComponent<T>
}
private var addedTicker = false
fun <T : IStorageStack> add(storage: IStorage<T>) {
getVirtualComponent(storage.storageType).add(storage)
}
@ -43,6 +44,11 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
for (identity in node.value.fetchComponents()) {
add(identity)
}
if (!addedTicker) {
addedTicker = true
addPreWorldTicker(level, this)
}
}
override fun onNodeRemoved(node: Graph6Node<IStorageGraphNode>) {
@ -63,9 +69,8 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
node,
fun(_tile): Graph6Node<IStorageGraphNode>? {
return _tile.getCapability(MatteryCapability.STORAGE_NODE).orNull()?.storageNode
},
::StorageNetworkGraph
)
}
) { StorageNetworkGraph(tile.level!!) }
}
@JvmStatic
@ -80,8 +85,7 @@ class StorageNetworkGraph : Abstract6Graph<IStorageGraphNode>() {
fun(_tile): Graph6Node<IStorageGraphNode>? {
return _tile.getCapability(MatteryCapability.STORAGE_NODE).orNull()?.storageNode
},
::StorageNetworkGraph
)
) { StorageNetworkGraph(tile.level!!) }
}
}
}