diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasManager.kt index 5414432f5..fb545a617 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/hud/atlas/HUDAtlasManager.kt @@ -30,6 +30,10 @@ class HUDAtlasManager(private val hudRenderer: HUDRenderer) { val elements: MutableMap = mutableMapOf() for ((resourceLocationString, versions) in data) { + if (resourceLocationString.startsWith("$")) { + // json schema + continue + } val resourceLocation = resourceLocationString.toResourceLocation() check(versions is Map<*, *>) diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt index 26bd2387e..c0d625185 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/HotbarInteractionHandler.kt @@ -23,6 +23,7 @@ import de.bixilon.minosoft.gui.rendering.modding.events.input.MouseScrollEvent import de.bixilon.minosoft.modding.event.EventInitiators import de.bixilon.minosoft.modding.event.events.SelectHotbarSlotEvent import de.bixilon.minosoft.modding.event.invoker.CallbackEventInvoker +import de.bixilon.minosoft.protocol.RateLimiter import de.bixilon.minosoft.protocol.packets.c2s.play.HotbarSlotSetC2SP import de.bixilon.minosoft.util.KUtil.toResourceLocation @@ -30,17 +31,17 @@ class HotbarInteractionHandler( val renderWindow: RenderWindow, ) { private val connection = renderWindow.connection + private val rateLimiter = RateLimiter() private var currentScrollOffset = 0.0 fun selectSlot(slot: Int) { - // ToDo: Rate limit? if (connection.player.selectedHotbarSlot == slot) { return } connection.player.selectedHotbarSlot = slot - connection.sendPacket(HotbarSlotSetC2SP(slot)) + rateLimiter += { connection.sendPacket(HotbarSlotSetC2SP(slot)) } connection.fireEvent(SelectHotbarSlotEvent(connection, EventInitiators.CLIENT, slot)) } @@ -78,4 +79,8 @@ class HotbarInteractionHandler( selectSlot(nextSlot) }) } + + fun draw(delta: Double) { + rateLimiter.work() + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt index 41f5b2e6a..3ea07b0b6 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/interaction/InteractionManager.kt @@ -23,4 +23,8 @@ class InteractionManager( fun init() { hotbar.init() } + + fun draw(delta: Double) { + hotbar.draw(delta) + } } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/RenderWindowInputHandler.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/RenderWindowInputHandler.kt index cfc553962..0e31c9b41 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/RenderWindowInputHandler.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/input/key/RenderWindowInputHandler.kt @@ -274,5 +274,7 @@ class RenderWindowInputHandler( camera.draw() leftClickHandler.draw(delta) rightClickHandler.draw(delta) + + interactionManager.draw(delta) } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/RateLimiter.kt b/src/main/java/de/bixilon/minosoft/protocol/RateLimiter.kt new file mode 100644 index 000000000..2fc42f8bd --- /dev/null +++ b/src/main/java/de/bixilon/minosoft/protocol/RateLimiter.kt @@ -0,0 +1,127 @@ +/* + * Minosoft + * Copyright (C) 2021 Moritz Zwerger + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + * + * This software is not affiliated with Mojang AB, the original developer of Minecraft. + */ + +package de.bixilon.minosoft.protocol + +import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition +import de.bixilon.minosoft.util.KUtil.synchronizedListOf +import de.bixilon.minosoft.util.KUtil.synchronizedSetOf +import de.bixilon.minosoft.util.KUtil.toSynchronizedList +import java.util.concurrent.locks.ReentrantLock + +typealias RateAction = (() -> Unit) + +class RateLimiter( + val limit: Int = ProtocolDefinition.TICKS_PER_SECOND, + val inTime: Long = 1000, + val allowForcePerform: Boolean = true, + val dependencies: MutableSet = synchronizedSetOf(), +) { + private val lock = ReentrantLock() + private var toDo: RateAction? = null + @Synchronized get + @Synchronized set + private var executions: MutableList = synchronizedListOf() + + val upToDate: Boolean + get() { + lock.lock() + val upToDate = toDo == null + lock.unlock() + return upToDate + } + + val canWork: Boolean + get() { + cleanup(true) + return executions.size < limit + } + private val _canWork: Boolean + get() { + cleanup(false) + return executions.size < limit + } + + /** + * Tries to perform a specific action + * + * @return If the action could be performed or has to wait + */ + fun perform(action: RateAction): Boolean { + lock.lock() + if (!_canWork) { + toDo = action + return false + } + + internalPerform(action) + lock.unlock() + return true + } + + operator fun plusAssign(action: RateAction) { + perform(action) + } + + private fun internalPerform(action: RateAction) { + for (dependency in dependencies) { + if (!dependency.upToDate) { + dependency.work() + } + check(dependency.upToDate) { "RateLimiter dependency is not upToDate!" } + } + toDo = null + action.invoke() + val time = System.currentTimeMillis() + executions += time + } + + fun forcePerform(action: RateAction) { + check(allowForcePerform) { "RateLimiter does not allow force performing!" } + lock.lock() + internalPerform(action) + lock.unlock() + } + + fun work() { + lock.lock() + cleanup(false) + if (!_canWork) { + return + } + toDo?.let { internalPerform(it) } + lock.unlock() + } + + private fun cleanup(lock: Boolean) { + if (lock) { + this.lock.lock() + } + val executions = executions.toSynchronizedList() + val time = System.currentTimeMillis() + + for (execution in executions) { + val addDelta = time - execution + if (addDelta - inTime >= 0L) { + // remove + this.executions.removeFirst() + } else { + break + } + } + if (lock) { + this.lock.unlock() + } + } +} + diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/PongC2SP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/PongC2SP.kt index 947d1f35c..7d93ac461 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/PongC2SP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/c2s/play/PongC2SP.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.protocol.packets.c2s.play +import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.protocol.packets.c2s.PlayC2SPacket import de.bixilon.minosoft.protocol.protocol.PlayOutByteBuffer import de.bixilon.minosoft.util.logging.Log @@ -26,6 +27,9 @@ class PongC2SP(val id: Int) : PlayC2SPacket { } override fun log() { - Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.VERBOSE) { "Pong (id=$id)" } + if (Minosoft.config.config.general.reduceProtocolLog) { + return + } + Log.log(LogMessageType.NETWORK_PACKETS_OUT, LogLevels.VERBOSE) { "Pong (id=$id)" } } } diff --git a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PingS2CP.kt b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PingS2CP.kt index 5101c8821..8ee52ea99 100644 --- a/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PingS2CP.kt +++ b/src/main/java/de/bixilon/minosoft/protocol/packets/s2c/play/PingS2CP.kt @@ -13,6 +13,7 @@ package de.bixilon.minosoft.protocol.packets.s2c.play +import de.bixilon.minosoft.Minosoft import de.bixilon.minosoft.protocol.network.connection.play.PlayConnection import de.bixilon.minosoft.protocol.packets.c2s.play.PongC2SP import de.bixilon.minosoft.protocol.packets.s2c.PlayS2CPacket @@ -30,6 +31,9 @@ class PingS2CP(buffer: PlayInByteBuffer) : PlayS2CPacket() { } override fun log() { + if (Minosoft.config.config.general.reduceProtocolLog) { + return + } Log.log(LogMessageType.NETWORK_PACKETS_IN, LogLevels.VERBOSE) { "Ping (id=$id)" } } } diff --git a/src/main/resources/assets/minosoft/mapping/atlas.json b/src/main/resources/assets/minosoft/mapping/atlas.json index 3dfe19744..80f2591ed 100644 --- a/src/main/resources/assets/minosoft/mapping/atlas.json +++ b/src/main/resources/assets/minosoft/mapping/atlas.json @@ -1,4 +1,5 @@ { + "$schema": "https://gitlab.bixilon.de/bixilon/minosoft/-/raw/hud/schemas/atlas.json", "minecraft:hotbar_base": { "0": { "texture": "minecraft:textures/gui/widgets.png",