diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index f1c7c6aeb..eec7a0839 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -180,22 +180,6 @@ fun tickServer(ticker: IConditionalTickable) { postServerTick.add(ticker, SERVER_IS_LIVE, "Server is stopping") } -fun tickUntilServerPre(ticker: () -> Boolean) { - preServerTick.until(ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickUntilServer(ticker: () -> Boolean) { - postServerTick.until(ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickWhileServerPre(condition: () -> Boolean, ticker: () -> Unit) { - preServerTick.`while`(condition, ticker, SERVER_IS_LIVE, "Server is stopping") -} - -fun tickWhileServer(condition: () -> Boolean, ticker: () -> Unit) { - postServerTick.`while`(condition, ticker, SERVER_IS_LIVE, "Server is stopping") -} - fun Level.once(ticker: ITickable) { if (this.isClientSide) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt index b4a250319..3db9d7ab0 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryDeviceBlockEntity.kt @@ -129,18 +129,21 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } inner class Piece(val side: RelativeSide) : IFluidHandler, ITickable { - init { - tickList.always(this) + private val ticker = tickList.Ticker(this) + private val controller = sides[side]!!.Cap(ForgeCapabilities.FLUID_HANDLER, this) + private val neighbour by sides[side]!!.track(ForgeCapabilities.FLUID_HANDLER) + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && flow != FlowDirection.NONE && !redstoneControl.isBlockedByRedstone + } + + init { // https://tenor.com/view/simp-metal-gear-liquid-snake-running-gif-16717852 savetables.enum(::flow, "fluid_${side}_flow", FlowDirection::valueOf) savetables.bool(::automatePull, "fluid_${side}_pull") savetables.bool(::automatePush, "fluid_${side}_push") } - private val controller = sides[side]!!.Cap(ForgeCapabilities.FLUID_HANDLER, this) - private val neighbour by sides[side]!!.track(ForgeCapabilities.FLUID_HANDLER) - var flow by synchronizer.enum(possibleModes, setter = { value, access, setByRemote -> require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" } @@ -154,6 +157,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo controller.close() controller.expose() } + + updateTickerState() } }) @@ -162,14 +167,27 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo set(value) { field = value setChangedLight() + updateTickerState() } + // var automatePush by synchronizer.bool().property var automatePush = false set(value) { field = value setChangedLight() + updateTickerState() } + init { + tickList.once { + redstoneControl.addListener { + updateTickerState() + } + + updateTickerState() + } + } + override fun tick() { if (flow == FlowDirection.NONE || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone) return @@ -324,25 +342,40 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo override val canSetBatteryLevel: Boolean by energy::canSetBatteryLevel + private val ticker = tickList.Ticker(this) + + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && energyFlow != FlowDirection.NONE && !redstoneControl.isBlockedByRedstone + } + // var automatePull by synchronizer.bool().property var automatePull = false set(value) { field = value setChangedLight() + updateTickerState() } + // var automatePush by synchronizer.bool().property var automatePush = false set(value) { field = value setChangedLight() + updateTickerState() } init { - tickList.always(this) - savetables.enum(::energyFlow, "energy_${side}_flow", FlowDirection::valueOf) savetables.bool(::automatePull, "energy_${side}_pull") savetables.bool(::automatePush, "energy_${side}_push") + + tickList.once { + redstoneControl.addListener { + updateTickerState() + } + + updateTickerState() + } } override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal { @@ -406,6 +439,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo controller.expose() } } + + updateTickerState() } }) @@ -533,11 +568,26 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo inner class Piece(val side: RelativeSide) : IItemHandler, ITickable { private var currentHandler: IItemHandler = EmptyItemHandler + set(value) { + field = value + updateTickerState() + } + private val capController = sides[side]!!.Cap(ForgeCapabilities.ITEM_HANDLER, this) private val neighbour by sides[side]!!.track(ForgeCapabilities.ITEM_HANDLER) + private val ticker = tickList.Ticker(this) + + private var innerSlotPull = 0 + private var outerSlotPull = 0 + + private var innerSlotPush = 0 + private var outerSlotPush = 0 + + private fun updateTickerState() { + ticker.isEnabled = (automatePull || automatePush) && mode != ItemHandlerMode.DISABLED && !redstoneControl.isBlockedByRedstone && currentHandler.slots != 0 + } init { - tickList.always(this) capController.close() } @@ -565,28 +615,6 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo } }) - /*var automatePull by synchronizer.bool(setter = { value, access, _ -> - if (access.readBoolean() != value) { - access.write(value) - - if (value) { - innerSlotPush = 0 - outerSlotPush = 0 - } - } - }).property - - var automatePush by synchronizer.bool(setter = { value, access, _ -> - if (access.readBoolean() != value) { - access.write(value) - - if (value) { - innerSlotPush = 0 - outerSlotPush = 0 - } - } - }).property*/ - var automatePull = false set(value) { if (field != value) { @@ -597,6 +625,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo innerSlotPush = 0 outerSlotPush = 0 } + + updateTickerState() } } @@ -610,6 +640,8 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo innerSlotPush = 0 outerSlotPush = 0 } + + updateTickerState() } } @@ -617,14 +649,16 @@ abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blo savetables.bool(::automatePull, "itemhandler_${side}_automatePull") savetables.bool(::automatePush, "itemhandler_${side}_automatePush") savetables.enum(::mode, "itemhandler_${side}_mode", ItemHandlerMode::valueOf) + + tickList.once { + redstoneControl.addListener { + updateTickerState() + } + + updateTickerState() + } } - private var innerSlotPull = 0 - private var outerSlotPull = 0 - - private var innerSlotPush = 0 - private var outerSlotPush = 0 - override fun tick() { if (mode == ItemHandlerMode.DISABLED || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone || currentHandler.slots == 0) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt index e49245b47..4f311198a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.block.entity +import it.unimi.dsi.fastutil.booleans.BooleanConsumer import net.minecraft.nbt.CompoundTag import net.minecraftforge.common.util.INBTSerializable import ru.dbotthepony.mc.otm.core.nbt.mapString @@ -13,9 +14,14 @@ interface IRedstoneControlled { abstract class AbstractRedstoneControl : INBTSerializable { abstract var redstoneSetting: RedstoneSetting abstract var redstoneSignal: Int + protected val listeners = ArrayList() val isBlockedByRedstone: Boolean get() = !redstoneSetting.test(redstoneSignal) + fun addListener(callback: BooleanConsumer) { + listeners.add(callback) + } + override fun serializeNBT(): CompoundTag { return CompoundTag().also { it[SETTING_KEY] = redstoneSetting.toString() @@ -47,7 +53,11 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) -> val old = isBlockedByRedstone field = level val state = isBlockedByRedstone - valueChanges.invoke(state, old) + + if (state != old) { + valueChanges.invoke(state, old) + listeners.forEach { it.accept(state) } + } } override var redstoneSignal: Int = 0 @@ -56,7 +66,11 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) -> val old = isBlockedByRedstone field = setting val state = isBlockedByRedstone - valueChanges.invoke(state, old) + + if (state != old) { + valueChanges.invoke(state, old) + listeners.forEach { it.accept(state) } + } } } @@ -72,7 +86,11 @@ class SynchronizedRedstoneControl( val old = isBlockedByRedstone access.write(value) val state = isBlockedByRedstone - valueChanges.invoke(state, old) + + if (state != old) { + valueChanges.invoke(state, old) + listeners.forEach { it.accept(state) } + } } }) @@ -84,7 +102,11 @@ class SynchronizedRedstoneControl( val old = isBlockedByRedstone access.write(value) val state = isBlockedByRedstone - valueChanges.invoke(state, old) + + if (state != old) { + valueChanges.invoke(state, old) + listeners.forEach { it.accept(state) } + } } }).property } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt index a0a46e119..d39a40ba6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/Ext.kt @@ -10,6 +10,7 @@ import com.google.common.collect.ImmutableSet import com.google.gson.JsonElement import com.google.gson.JsonObject import com.google.gson.JsonPrimitive +import it.unimi.dsi.fastutil.objects.ObjectComparators import net.minecraft.core.BlockPos import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.NbtAccounter @@ -417,3 +418,12 @@ fun List.searchInsertionIndex(element: E, comparator: Comparator, from fun MutableList.addSorted(element: E, comparator: Comparator) { add(searchInsertionIndex(element, comparator), element) } + +/** + * Inserts [element] into [MutableList] at index determined by comparing values themselves + * + * If [MutableList] is not sorted, result of this function is undefined + */ +fun > MutableList.addSorted(element: E) { + add(searchInsertionIndex(element, ObjectComparators.NATURAL_COMPARATOR), element) +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt index e7a95b0da..13df85dc2 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/TickList.kt @@ -1,16 +1,18 @@ package ru.dbotthepony.mc.otm.core.util import org.apache.logging.log4j.LogManager +import ru.dbotthepony.mc.otm.core.addSorted class TickList : ITickable { private val conditional = ArrayDeque() - private val conditionalValveTime = ArrayList() + private val conditionalQueued = ArrayList() private val once = ArrayDeque() - private val onceValveTime = ArrayList() + private val onceQueued = ArrayList() private val always = ArrayList() - private val alwaysValveTime = ArrayList() + private val alwaysQueued = ArrayList() + private val toRemoveFromAlways = ArrayList() private val timers = ArrayDeque() @@ -19,45 +21,18 @@ class TickList : ITickable { var ticks = 0 private set - private var nothingToDo = true - - inner class Timer(val timerTicks: Int, val runnable: Runnable) { + inner class Timer(val timerTicks: Int, val runnable: Runnable) : Comparable { val ringAt = ticks + timerTicks var finished = false private set init { - nothingToDo = false + timers.addSorted(this) + } - if (timers.isEmpty()) { - timers.addLast(this) - } else { - val iterator = timers.listIterator() - var hit = false - - for (value in iterator) { - if (value.ringAt == ringAt) { - hit = true - iterator.add(this) - break - } else if (value.ringAt > ringAt) { - if (iterator.hasPrevious()) { - iterator.previous() - iterator.add(this) - } else { - timers.addFirst(this) - } - - hit = true - break - } - } - - if (!hit) { - timers.addLast(this) - } - } + override fun compareTo(other: Timer): Int { + return ringAt.compareTo(other.ringAt) } fun execute() { @@ -67,13 +42,49 @@ class TickList : ITickable { } } - fun add(ticker: IConditionalTickable) { - if (inTicker) { - conditionalValveTime.add(ticker) - } else { - conditional.addFirst(ticker) - nothingToDo = false + inner class Ticker(parent: ITickable) : ITickable by parent { + init { + add(this, always, alwaysQueued) } + + var isEnabled = true + set(value) { + if (field != value) { + field = value + + if (value) { + add(this, always, alwaysQueued) + } else { + alwaysQueued.remove(this) + + if (inTicker) { + toRemoveFromAlways.add(this) + } else { + always.remove(this) + } + } + } + } + + fun disable() { + isEnabled = false + } + + fun enable() { + isEnabled = true + } + } + + private fun add(value: T, regular: MutableList, queue: MutableList) { + if (inTicker) { + queue.add(value) + } else { + regular.add(value) + } + } + + fun add(ticker: IConditionalTickable) { + add(ticker, conditional, conditionalQueued) } fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) { @@ -86,12 +97,7 @@ class TickList : ITickable { } fun once(ticker: ITickable) { - if (inTicker) { - onceValveTime.add(ticker) - } else { - once.addFirst(ticker) - nothingToDo = false - } + add(ticker, once, onceQueued) } fun once(ticker: ITickable, condition: Boolean, reason: String) { @@ -104,12 +110,7 @@ class TickList : ITickable { } fun always(ticker: ITickable) { - if (inTicker) { - alwaysValveTime.add(ticker) - } else { - always.add(ticker) - nothingToDo = false - } + add(ticker, always, alwaysQueued) } fun timer(timerTicks: Int, action: Runnable, condition: Boolean, reason: String): Timer? { @@ -125,23 +126,7 @@ class TickList : ITickable { return Timer(timerTicks, action) } - fun until(ticker: () -> Boolean) = add(IConditionalTickable.wrap(ticker)) - fun `while`(tickerCondition: () -> Boolean, ticker: () -> Unit) = add( - IConditionalTickable.wrap( - tickerCondition, - ticker - ) - ) - - fun until(ticker: () -> Boolean, condition: Boolean, reason: String) = add(IConditionalTickable.wrap(ticker), condition, reason) - fun `while`(tickerCondition: () -> Boolean, ticker: () -> Unit, condition: Boolean, reason: String) = add( - IConditionalTickable.wrap(tickerCondition, ticker), condition, reason) - override fun tick() { - if (nothingToDo) { - return - } - if (inTicker) { throw ConcurrentModificationException("Already ticking") } @@ -150,23 +135,12 @@ class TickList : ITickable { inTicker = true try { - var nothingToDo = true - val conditional = conditional - val once = once - val always = always - val alwaysValveTime = alwaysValveTime - val conditionalValveTime = conditionalValveTime - val onceValveTime = onceValveTime - val timers = timers - if (conditional.isNotEmpty()) { val iterator = conditional.iterator() for (ticker in iterator) { if (!ticker.tick()) { iterator.remove() - } else { - nothingToDo = false } } } @@ -179,40 +153,40 @@ class TickList : ITickable { once.clear() } + if (toRemoveFromAlways.isNotEmpty()) { + for (v in toRemoveFromAlways) always.remove(v) + toRemoveFromAlways.clear() + } + if (always.isNotEmpty()) { for (ticker in always) { ticker.tick() } - - nothingToDo = false } - if (alwaysValveTime.isNotEmpty()) { - always.addAll(alwaysValveTime) - alwaysValveTime.clear() - nothingToDo = false + if (alwaysQueued.isNotEmpty()) { + always.ensureCapacity(always.size + alwaysQueued.size) + for (v in alwaysQueued) always.add(v) // avoid toArray() + alwaysQueued.clear() } - if (conditionalValveTime.isNotEmpty()) { - for (ticker in conditionalValveTime) { + if (conditionalQueued.isNotEmpty()) { + for (ticker in conditionalQueued) { conditional.addFirst(ticker) } - conditionalValveTime.clear() - nothingToDo = false + conditionalQueued.clear() } - if (onceValveTime.isNotEmpty()) { - for (ticker in onceValveTime) { + if (onceQueued.isNotEmpty()) { + for (ticker in onceQueued) { once.addFirst(ticker) } - onceValveTime.clear() - nothingToDo = false + onceQueued.clear() } while (timers.isNotEmpty()) { - nothingToDo = false val head = timers.first() if (head.ringAt <= ticks) { @@ -222,8 +196,6 @@ class TickList : ITickable { break } } - - this.nothingToDo = nothingToDo } finally { inTicker = false } @@ -233,13 +205,13 @@ class TickList : ITickable { if (inTicker) throw ConcurrentModificationException() conditional.clear() - conditionalValveTime.clear() + conditionalQueued.clear() once.clear() - onceValveTime.clear() + onceQueued.clear() always.clear() - alwaysValveTime.clear() + alwaysQueued.clear() timers.clear() }