diff --git a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java index 492cc8972..50512d13c 100644 --- a/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java +++ b/src/main/java/ru/dbotthepony/mc/otm/OverdriveThatMatters.java @@ -149,6 +149,9 @@ public final class OverdriveThatMatters { EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onMovementInputUpdate); EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onScreenOpen); EVENT_BUS.addListener(EventPriority.NORMAL, ClientEventHandlerKt::onPostScreenInit); + EVENT_BUS.addListener(EventPriority.LOWEST, ClientEventHandlerKt::onClientTick); + EVENT_BUS.addListener(EventPriority.HIGHEST, ClientEventHandlerKt::onClientConnected); + EVENT_BUS.addListener(EventPriority.HIGHEST, ClientEventHandlerKt::onClientDisconnected); if (ModList.get().isLoaded("mekanism")) { EVENT_BUS.addListener(EventPriority.NORMAL, TooltipsKt::tooltipEvent); diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index eb05a7323..080ccab71 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -15,6 +15,7 @@ import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.core.IConditionalTickable import ru.dbotthepony.mc.otm.core.ITickable import ru.dbotthepony.mc.otm.core.TickList +import ru.dbotthepony.mc.otm.core.TimerQueue import java.util.* private val preServerTick = TickList() @@ -25,72 +26,6 @@ private val postWorldTick = WeakHashMap() private val preServerTickTimers = TimerQueue() private val postServerTickTimers = TimerQueue() -class TimerQueue { - private var ticks = 0 - private var head: Timer? = null - - inner class Timer(val timerTicks: Int, val runnable: Runnable) { - val ringAt = ticks + timerTicks - var finished = false - private set - - var next: Timer? = null - var prev: Timer? = null - - init { - if (head == null) { - head = this - } else { - var next = head - - while (next != null) { - if (next.ringAt >= this.ringAt) { - next.prev?.next = this - this.prev = next.prev - next.prev = this - this.next = next - break - } else if (next.next == null) { - next.next = this - this.prev = next - break - } else { - next = next.next - } - } - } - } - - fun execute() { - check(!finished) { "Already finished" } - runnable.run() - finished = true - } - } - - fun tick() { - ticks++ - - var head = head - - while (head != null) { - if (head.ringAt <= ticks) { - head.execute() - head = head.next - head?.prev = null - this.head = head - } else { - break - } - } - } - - fun clear() { - ticks = 0 - head = null - } -} - fun addPreTickTimer(inTicks: Int, callback: Runnable): TimerQueue.Timer? { if (SERVER_IS_DYING) { LOGGER.error("Refusing to add timer $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping")) @@ -180,7 +115,7 @@ fun onServerTick(event: ServerTickEvent) { } } -fun addPreServerTicker(ticker: IConditionalTickable) { +fun tickServerPre(ticker: IConditionalTickable) { if (SERVER_IS_DYING) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return @@ -189,7 +124,7 @@ fun addPreServerTicker(ticker: IConditionalTickable) { preServerTick.add(ticker) } -fun addPostServerTicker(ticker: IConditionalTickable) { +fun tickServer(ticker: IConditionalTickable) { if (SERVER_IS_DYING) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return @@ -198,7 +133,7 @@ fun addPostServerTicker(ticker: IConditionalTickable) { postServerTick.add(ticker) } -fun addPreServerTickerOnce(ticker: ITickable) { +fun onceServerPre(ticker: ITickable) { if (SERVER_IS_DYING) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return @@ -207,7 +142,7 @@ fun addPreServerTickerOnce(ticker: ITickable) { preServerTick.add(ticker) } -fun addPostServerTickerOnce(ticker: ITickable) { +fun onceServer(ticker: ITickable) { if (SERVER_IS_DYING) { LOGGER.error("Refusing to add ticker $ticker while server is dying", IllegalStateException("Server is stopping")) return diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt index e71579e4e..a84ffd254 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/MatteryPlayerCapability.kt @@ -732,7 +732,7 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial if (mattery.hasExoSuit && !ply.level.gameRules.getBoolean(GameRules.RULE_KEEPINVENTORY)) { waitingToDie.add(ply to mattery) - addPostServerTickerOnce { + onceServer { val index = waitingToDie.indexOfFirst { it.first == ply } if (index != -1) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt index d2f56cfb8..247d5611f 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/client/ClientEventHandler.kt @@ -2,15 +2,24 @@ package ru.dbotthepony.mc.otm.client import com.mojang.blaze3d.platform.InputConstants import net.minecraft.client.gui.screens.inventory.InventoryScreen +import net.minecraftforge.client.event.ClientPlayerNetworkEvent import net.minecraftforge.client.event.MovementInputUpdateEvent import net.minecraftforge.client.event.ScreenEvent +import net.minecraftforge.event.TickEvent +import net.minecraftforge.event.TickEvent.ClientTickEvent +import org.apache.logging.log4j.LogManager import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.client.render.UVWindingOrder import ru.dbotthepony.mc.otm.client.render.Widgets18 import ru.dbotthepony.mc.otm.client.screen.ExoSuitInventoryScreen import ru.dbotthepony.mc.otm.client.screen.panels.LargeSquareButtonPanel import ru.dbotthepony.mc.otm.client.screen.panels.Panel2Widget +import ru.dbotthepony.mc.otm.core.IConditionalTickable +import ru.dbotthepony.mc.otm.core.ITickable +import ru.dbotthepony.mc.otm.core.TickList +import ru.dbotthepony.mc.otm.core.TimerQueue import ru.dbotthepony.mc.otm.core.TranslatableComponent +import ru.dbotthepony.mc.otm.isClient import ru.dbotthepony.mc.otm.registry.AndroidFeatures fun onMovementInputUpdate(event: MovementInputUpdateEvent) { @@ -52,7 +61,7 @@ fun onPostScreenInit(event: ScreenEvent.Init.Post) { val screen = event.screen as? InventoryScreen ?: return if (player.hasExoSuit) { - event.addListener(Panel2Widget(LargeSquareButtonPanel(event.screen as InventoryScreen, null, + val widget = Panel2Widget(LargeSquareButtonPanel(screen, null, x = screen.guiLeft + screen.xSize + 2f, y = screen.guiTop.toFloat(), skinElement = Widgets18.RETURN_ARROW_LEFT, @@ -66,7 +75,14 @@ fun onPostScreenInit(event: ScreenEvent.Init.Post) { minecraft.setScreen(ExoSuitInventoryScreen(player.exoSuitMenu)) InputConstants.grabOrReleaseMouse(minecraft.window.window, 212993, mouseX, mouseY) - }).also { it.tooltip = TranslatableComponent("otm.gui.exosuit.go_in") })) + }).also { it.tooltip = TranslatableComponent("otm.gui.exosuit.go_in") }) + + event.addListener(widget) + + whileClient({ minecraft.screen == screen }) { + widget.panel.x = screen.guiLeft + screen.xSize + 2f + widget.panel.y = screen.guiTop.toFloat() + } } } @@ -81,3 +97,77 @@ fun onScreenOpen(event: ScreenEvent.Opening) { event.newScreen = ExoSuitInventoryScreen(player.exoSuitMenu) } } + +private val preTickList = TickList() +private val postTickList = TickList() + +private val preTimerList = TimerQueue() +private val postTimerList = TimerQueue() + +private var LOGGED_IN = false +private val LOGGER = LogManager.getLogger() + +fun onceClient(ticker: ITickable) { + check(isClient) { "Illegal side" } + postTickList.add(ticker, LOGGED_IN, "Not logged in") +} + +fun onceClientPre(ticker: ITickable) { + check(isClient) { "Illegal side" } + preTickList.add(ticker, LOGGED_IN, "Not logged in") +} + +fun tickClient(ticker: IConditionalTickable) { + check(isClient) { "Illegal side" } + postTickList.add(ticker, LOGGED_IN, "Not logged in") +} + +fun tickClientPre(ticker: IConditionalTickable) { + check(isClient) { "Illegal side" } + preTickList.add(ticker, LOGGED_IN, "Not logged in") +} + +fun untilClient(ticker: () -> Boolean) { + tickClient(IConditionalTickable.wrap(ticker)) +} + +fun untilClientPre(ticker: () -> Boolean) { + tickClientPre(IConditionalTickable.wrap(ticker)) +} + +fun whileClient(condition: () -> Boolean, ticker: () -> Unit) { + tickClient(IConditionalTickable.wrap(condition, ticker)) +} + +fun whileClientPre(condition: () -> Boolean, ticker: () -> Unit) { + tickClientPre(IConditionalTickable.wrap(condition, ticker)) +} + +fun onClientTick(event: ClientTickEvent) { + if (event.phase == TickEvent.Phase.START) { + preTimerList.tick() + preTickList.tick() + } else { + postTimerList.tick() + postTickList.tick() + } +} + +fun onClientDisconnected(event: ClientPlayerNetworkEvent.LoggingOut) { + LOGGED_IN = false + + preTimerList.clear() + preTickList.clear() + postTimerList.clear() + postTickList.clear() +} + +fun onClientConnected(event: ClientPlayerNetworkEvent.LoggingIn) { + LOGGED_IN = true + + preTimerList.clear() + preTickList.clear() + postTimerList.clear() + postTickList.clear() +} + diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt index 911354d20..123bf5d3c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/mekanism/QIO.kt @@ -17,7 +17,7 @@ import net.minecraftforge.event.AttachCapabilitiesEvent import net.minecraftforge.eventbus.api.SubscribeEvent import ru.dbotthepony.mc.otm.core.ITickable import ru.dbotthepony.mc.otm.OverdriveThatMatters -import ru.dbotthepony.mc.otm.addPostServerTickerOnce +import ru.dbotthepony.mc.otm.onceServer import ru.dbotthepony.mc.otm.capability.MatteryCapability import ru.dbotthepony.mc.otm.capability.isMekanismLoaded import ru.dbotthepony.mc.otm.core.isPositive @@ -285,7 +285,7 @@ fun attachCapabilities(event: AttachCapabilitiesEvent) { val capability = QIOStorage(event.`object` as TileEntityQIODriveArray) event.addCapability(QIO_LOCATION, capability) - addPostServerTickerOnce { + onceServer { if (!event.`object`.isRemoved && event.`object`.level?.isClientSide == false) { StorageNetworkGraph.discoverFull(event.`object`, capability.cell.storageNode) } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt index c7e796c70..076b0b138 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TickList.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.mc.otm.core +import org.apache.logging.log4j.LogManager import java.util.* import kotlin.ConcurrentModificationException @@ -28,6 +29,32 @@ class TickList { } } + fun add(ticker: IConditionalTickable, condition: Boolean, reason: String) { + if (!condition) { + LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) + return + } + + if (inTicker) { + conditionalValveTime.add(ticker) + } else { + conditional.addFirst(ticker) + } + } + + fun add(ticker: ITickable, condition: Boolean, reason: String) { + if (!condition) { + LOGGER.error("Refusing to add tickable $ticker because we $reason", IllegalStateException(reason)) + return + } + + if (inTicker) { + onceValveTime.add(ticker) + } else { + once.addFirst(ticker) + } + } + fun tick() { if (inTicker) { throw ConcurrentModificationException("Already ticking") @@ -68,6 +95,10 @@ class TickList { conditional.clear() once.clear() } + + companion object { + private val LOGGER = LogManager.getLogger() + } } fun interface ITickable { @@ -81,4 +112,27 @@ interface IConditionalTickable : ITickable { * If it suddenly turns true after being false, result is undefined. */ val canTick: Boolean + + companion object { + fun wrap(ticker: () -> Boolean): IConditionalTickable { + return object : IConditionalTickable { + override var canTick: Boolean = true + private set + + override fun tick() { + canTick = !ticker.invoke() + } + } + } + + fun wrap(condition: () -> Boolean, ticker: () -> Unit): IConditionalTickable { + return object : IConditionalTickable { + override val canTick: Boolean get() = condition.invoke() + + override fun tick() { + ticker.invoke() + } + } + } + } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt new file mode 100644 index 000000000..a9d1ed54f --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/TimerQueue.kt @@ -0,0 +1,67 @@ +package ru.dbotthepony.mc.otm.core + +class TimerQueue { + private var ticks = 0 + private var head: Timer? = null + + inner class Timer(val timerTicks: Int, val runnable: Runnable) { + val ringAt = ticks + timerTicks + var finished = false + private set + + var next: Timer? = null + var prev: Timer? = null + + init { + if (head == null) { + head = this + } else { + var next = head + + while (next != null) { + if (next.ringAt >= this.ringAt) { + next.prev?.next = this + this.prev = next.prev + next.prev = this + this.next = next + break + } else if (next.next == null) { + next.next = this + this.prev = next + break + } else { + next = next.next + } + } + } + } + + fun execute() { + check(!finished) { "Already finished" } + runnable.run() + finished = true + } + } + + fun tick() { + ticks++ + + var head = head + + while (head != null) { + if (head.ringAt <= ticks) { + head.execute() + head = head.next + head?.prev = null + this.head = head + } else { + break + } + } + } + + fun clear() { + ticks = 0 + head = null + } +} \ No newline at end of file diff --git a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt index 0b1adfb63..0a2d1edae 100644 --- a/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt +++ b/src/test/kotlin/ru/dbotthepony/mc/otm/tests/TimerQueueTests.kt @@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.tests import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test -import ru.dbotthepony.mc.otm.TimerQueue +import ru.dbotthepony.mc.otm.core.TimerQueue object TimerQueueTests { @Test