From d40768840706e2f195784f3e4c5eda3bfa5aa5cb Mon Sep 17 00:00:00 2001 From: pomchom Date: Fri, 8 Mar 2024 10:10:05 -0500 Subject: [PATCH] support custom tick rates, preparation for lagslayer hud fix --- .../java/io/github/homchom/recode/Power.kt | 5 +- .../homchom/recode/event/BufferedEvent.kt | 20 ++++---- .../github/homchom/recode/game/GameEvents.kt | 4 ++ .../io/github/homchom/recode/game/GameTime.kt | 48 +++++++++++++++++-- .../recode/mod/mixin/render/MInGameHUD.java | 3 +- .../recode/multiplayer/CommandQueue.kt | 5 +- .../homchom/recode/render/RenderDucks.kt | 28 +++++++++++ .../homchom/recode/render/RenderEvents.kt | 31 +++--------- .../recode/sys/sidedchat/ChatPredicates.java | 3 -- .../homchom/recode/ui/text/StyledString.kt | 38 +++++++++++---- .../ui/text/StyledStringManipulation.kt | 2 - .../homchom/recode/ui/text/TextExtensions.kt | 10 +++- .../recode/util/coroutines/RateLimiter.kt | 2 +- 13 files changed, 139 insertions(+), 60 deletions(-) create mode 100644 src/main/java/io/github/homchom/recode/render/RenderDucks.kt delete mode 100644 src/main/java/io/github/homchom/recode/ui/text/StyledStringManipulation.kt diff --git a/src/main/java/io/github/homchom/recode/Power.kt b/src/main/java/io/github/homchom/recode/Power.kt index bf665a65..aef076cc 100644 --- a/src/main/java/io/github/homchom/recode/Power.kt +++ b/src/main/java/io/github/homchom/recode/Power.kt @@ -3,6 +3,7 @@ package io.github.homchom.recode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -25,7 +26,8 @@ interface PowerSink { class Power( extent: PowerSink? = null, private val onEnable: PowerCallback? = null, - private val onDisable: PowerCallback? = null + private val onDisable: PowerCallback? = null, + startEnabled: Boolean = false ) : PowerSink, CoroutineScope { private var charge = 0 @@ -41,6 +43,7 @@ class Power( init { extent?.use(this) + if (startEnabled) runBlocking { up() } } suspend fun up() = updateCharge { it + 1 } diff --git a/src/main/java/io/github/homchom/recode/event/BufferedEvent.kt b/src/main/java/io/github/homchom/recode/event/BufferedEvent.kt index 26de965a..34059b2b 100644 --- a/src/main/java/io/github/homchom/recode/event/BufferedEvent.kt +++ b/src/main/java/io/github/homchom/recode/event/BufferedEvent.kt @@ -2,10 +2,10 @@ package io.github.homchom.recode.event import com.google.common.cache.CacheBuilder import io.github.homchom.recode.PowerSink +import io.github.homchom.recode.game.currentTick import kotlinx.coroutines.flow.MutableStateFlow import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -import kotlin.time.DurationUnit import kotlin.time.toJavaDuration // TODO: replace stableInterval with a tick utility @@ -14,7 +14,7 @@ import kotlin.time.toJavaDuration /** * Creates a [BufferedCustomEvent] that runs asynchronously and caches the result. [BufferedCustomEvent.stabilize] * should be called once during each critical section of this event's execution; if this is done, the result will - * be cached roughly on [stableInterval]. + * be cached roughly on [stableTickInterval] (in ticks). * * @param keySelector A function that transforms [I] into a hashable key type [K] for the buffer. * @param contextGenerator A function that transforms [I] into the context type [T] when needed. @@ -22,13 +22,13 @@ import kotlin.time.toJavaDuration */ fun createBufferedEvent( resultCapture: (T) -> R, - stableInterval: Duration, + stableTickInterval: Int, keySelector: (I) -> K, contextGenerator: (I) -> T, cacheDuration: Duration = 1.seconds ): BufferedCustomEvent { val delegate = createEvent(resultCapture) - return BufferedFlowEvent(delegate, stableInterval, keySelector, contextGenerator, cacheDuration) + return BufferedFlowEvent(delegate, stableTickInterval, keySelector, contextGenerator, cacheDuration) } /** @@ -46,7 +46,7 @@ interface BufferedCustomEvent : ResultListenable { private class BufferedFlowEvent( private val delegate: CustomEvent, - stableInterval: Duration, + stableTickInterval: Int, private val keySelector: (I) -> K, private val contextGenerator: (I) -> T, cacheDuration: Duration = 1.seconds @@ -65,17 +65,17 @@ private class BufferedFlowEvent( var passIndex = -1 var runIndex = 0 - private var prevStamp = 0L + private var prevTick = 0L fun stamp() { - val now = System.currentTimeMillis() + val now = currentTick if (++passIndex == passes) { - val elapsed = (now - prevStamp).toInt().coerceAtLeast(1) - passes = stableInterval.toInt(DurationUnit.MILLISECONDS) / elapsed + val elapsed = (now - prevTick).toInt().coerceAtLeast(1) + passes = stableTickInterval / elapsed passIndex = 0 } runIndex = passIndex - prevStamp = now + prevTick = now } } diff --git a/src/main/java/io/github/homchom/recode/game/GameEvents.kt b/src/main/java/io/github/homchom/recode/game/GameEvents.kt index 84e05296..0e012e3b 100644 --- a/src/main/java/io/github/homchom/recode/game/GameEvents.kt +++ b/src/main/java/io/github/homchom/recode/game/GameEvents.kt @@ -6,8 +6,12 @@ import io.github.homchom.recode.event.createValidatedEvent import io.github.homchom.recode.event.wrapFabricEvent import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents.ClientStopping +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents.EndTick import net.minecraft.network.protocol.game.ClientboundSoundPacket +val AfterClientTickEvent = wrapFabricEvent(ClientTickEvents.END_CLIENT_TICK) { EndTick(it) } + val PlaySoundEvent = createValidatedEvent() val QuitGameEvent = wrapFabricEvent(ClientLifecycleEvents.CLIENT_STOPPING) { ClientStopping(it) } \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/game/GameTime.kt b/src/main/java/io/github/homchom/recode/game/GameTime.kt index 1670f109..002c3c91 100644 --- a/src/main/java/io/github/homchom/recode/game/GameTime.kt +++ b/src/main/java/io/github/homchom/recode/game/GameTime.kt @@ -1,8 +1,50 @@ package io.github.homchom.recode.game -import kotlin.time.Duration.Companion.milliseconds +import io.github.homchom.recode.Power +import io.github.homchom.recode.event.listen +import io.github.homchom.recode.event.listenEach +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.takeWhile /** - * @return this integer as a [kotlin.time.Duration] in ticks, where 20 ticks = 1 second. + * The current client tick. + * + * @see waitTicks + * @see AfterClientTickEvent */ -val Int.ticks get() = milliseconds * 50 \ No newline at end of file +val currentTick get() = TickRecorder.currentTick + +/** + * Suspends until [AfterClientTickEvent] runs [ticks] times. Unlike [kotlinx.coroutines.delay], this is + * event-based and respects tick rates. + */ +suspend fun waitTicks(ticks: Int) = AfterClientTickEvent.notifications.take(ticks).collect() + +private object TickRecorder { + var currentTick = 0L + + private val power = Power() + + init { + power.listenEach(AfterClientTickEvent) { currentTick++ } + } +} + +class TickCountdown(private val duration: Int, private val onFinish: () -> Unit = {}) { + val isActive get() = counter > 0 + + private var counter = 0 + private val power = Power(startEnabled = true) + + fun wind() { + val launch = !isActive + counter = duration + if (launch) power.listen(AfterClientTickEvent) { + takeWhile { --counter > 0 } + }.invokeOnCompletion { exception -> + counter = 0 + if (exception == null) onFinish() + } + } +} \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/mod/mixin/render/MInGameHUD.java b/src/main/java/io/github/homchom/recode/mod/mixin/render/MInGameHUD.java index bbf10d78..cf52ff1b 100644 --- a/src/main/java/io/github/homchom/recode/mod/mixin/render/MInGameHUD.java +++ b/src/main/java/io/github/homchom/recode/mod/mixin/render/MInGameHUD.java @@ -3,7 +3,6 @@ import io.github.homchom.recode.hypercube.state.DF; import io.github.homchom.recode.hypercube.state.PlotMode; import io.github.homchom.recode.mod.config.Config; -import io.github.homchom.recode.mod.features.LagslayerHUD; import io.github.homchom.recode.mod.features.StateOverlayHandler; import io.github.homchom.recode.mod.features.commands.CodeSearcher; import net.minecraft.client.Minecraft; @@ -20,7 +19,7 @@ public class MInGameHUD { @Inject(method = "renderEffects", at = @At("RETURN")) private void renderEffects(GuiGraphics guiGraphics, CallbackInfo ci) { - LagslayerHUD.onRender(guiGraphics); + //LagslayerHUD.onRender(guiGraphics); Minecraft mc = Minecraft.getInstance(); Font tr = mc.font; diff --git a/src/main/java/io/github/homchom/recode/multiplayer/CommandQueue.kt b/src/main/java/io/github/homchom/recode/multiplayer/CommandQueue.kt index a630b9d2..2372ff19 100644 --- a/src/main/java/io/github/homchom/recode/multiplayer/CommandQueue.kt +++ b/src/main/java/io/github/homchom/recode/multiplayer/CommandQueue.kt @@ -3,11 +3,10 @@ package io.github.homchom.recode.multiplayer import io.github.homchom.recode.RecodeDispatcher -import io.github.homchom.recode.game.ticks +import io.github.homchom.recode.game.waitTicks import io.github.homchom.recode.mc import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay import kotlinx.coroutines.launch // https://github.com/PaperMC/Velocity/issues/909 @@ -22,7 +21,7 @@ fun unsafelySendCommand(command: String) { do { val next = queue.removeFirst() mc.player?.connection?.sendUnsignedCommand(next) ?: break - delay(1.ticks) + waitTicks(1) } while (queue.isNotEmpty()) } finally { queue.clear() diff --git a/src/main/java/io/github/homchom/recode/render/RenderDucks.kt b/src/main/java/io/github/homchom/recode/render/RenderDucks.kt new file mode 100644 index 00000000..eb9aefd3 --- /dev/null +++ b/src/main/java/io/github/homchom/recode/render/RenderDucks.kt @@ -0,0 +1,28 @@ +package io.github.homchom.recode.render + +import net.minecraft.core.SectionPos +import net.minecraft.world.level.block.entity.BlockEntity + +/** + * An [net.minecraft.client.renderer.LevelRenderer] that is augmented by recode. + */ +@Suppress("FunctionName") +interface DRecodeLevelRenderer { + /** + * @returns A filtered list of block entities that should still be rendered. + */ + fun `recode$runBlockEntityEvents`( + blockEntities: Collection, + sectionPos: SectionPos? + ): List + + /** + * Gets and returns the RGBA hex of [blockEntity]'s outline, or `null` if it will not be outlined. + */ + fun `recode$getBlockEntityOutlineColor`(blockEntity: BlockEntity): Int? + + /** + * Processes all unprocessed entity and block entity outlines. + */ + fun `recode$processOutlines`(partialTick: Float) +} \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/render/RenderEvents.kt b/src/main/java/io/github/homchom/recode/render/RenderEvents.kt index 20def059..50544f54 100644 --- a/src/main/java/io/github/homchom/recode/render/RenderEvents.kt +++ b/src/main/java/io/github/homchom/recode/render/RenderEvents.kt @@ -5,14 +5,15 @@ package io.github.homchom.recode.render import com.mojang.blaze3d.systems.RenderSystem import io.github.homchom.recode.Power import io.github.homchom.recode.event.* -import io.github.homchom.recode.game.ticks import io.github.homchom.recode.mc import io.github.homchom.recode.util.Case import io.github.homchom.recode.util.math.MixedInt import io.github.homchom.recode.util.std.mapToArray +import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderContext import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents.BeforeBlockOutline +import net.minecraft.client.gui.GuiGraphics import net.minecraft.core.SectionPos import net.minecraft.world.level.block.entity.BlockEntity import net.minecraft.world.phys.HitResult @@ -41,7 +42,7 @@ val OutlineBlockEntitiesEvent = .filter { it.outlineColor != null } .associate { it.blockEntity.blockPos to it.outlineColor!! } }, - stableInterval = 3.ticks, + stableTickInterval = 3, keySelector = { Case(it.sectionPos) }, contextGenerator = { input -> input.blockEntities.mapToArray(::BlockEntityOutlineContext) @@ -64,26 +65,8 @@ data class BlockEntityOutlineContext( data class Input(val blockEntities: Collection, val sectionPos: SectionPos?) } -/** - * An [net.minecraft.client.renderer.LevelRenderer] that is augmented by recode. - */ -@Suppress("FunctionName") -interface DRecodeLevelRenderer { - /** - * @returns A filtered list of block entities that should still be rendered. - */ - fun `recode$runBlockEntityEvents`( - blockEntities: Collection, - sectionPos: SectionPos? - ): List - - /** - * Gets and returns the RGBA hex of [blockEntity]'s outline, or `null` if it will not be outlined. - */ - fun `recode$getBlockEntityOutlineColor`(blockEntity: BlockEntity): Int? +val AfterRenderHudEvent = wrapFabricEvent(HudRenderCallback.EVENT) { listener -> + HudRenderCallback { guiGraphics, tickDelta -> listener(HudRenderContext(guiGraphics, tickDelta)) } +} - /** - * Processes all unprocessed entity and block entity outlines. - */ - fun `recode$processOutlines`(partialTick: Float) -} \ No newline at end of file +data class HudRenderContext(val guiGraphics: GuiGraphics, val tickDelta: Float) \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/sys/sidedchat/ChatPredicates.java b/src/main/java/io/github/homchom/recode/sys/sidedchat/ChatPredicates.java index 746a8c74..8806e053 100644 --- a/src/main/java/io/github/homchom/recode/sys/sidedchat/ChatPredicates.java +++ b/src/main/java/io/github/homchom/recode/sys/sidedchat/ChatPredicates.java @@ -2,12 +2,9 @@ import com.google.common.collect.Lists; import io.github.homchom.recode.mod.config.Config; -import io.github.homchom.recode.render.*; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextColor; -import org.w3c.dom.css.RGBColor; - import java.util.List; import java.util.function.Predicate; diff --git a/src/main/java/io/github/homchom/recode/ui/text/StyledString.kt b/src/main/java/io/github/homchom/recode/ui/text/StyledString.kt index 7ccd3058..d33d483b 100644 --- a/src/main/java/io/github/homchom/recode/ui/text/StyledString.kt +++ b/src/main/java/io/github/homchom/recode/ui/text/StyledString.kt @@ -5,6 +5,8 @@ import net.kyori.adventure.text.ComponentIteratorType import net.kyori.adventure.text.ComponentLike import net.kyori.adventure.text.TextComponent import net.kyori.adventure.text.format.Style +import kotlin.math.max +import kotlin.math.min data class StyledChar(val char: Char, val style: Style) @@ -12,6 +14,8 @@ class StyledString private constructor( private val text: Component, override val size: Int ) : AbstractCollection(), ComponentLike by text { + val components = text.asFlatSequence().map { it as TextComponent } + constructor(vararg contents: Pair) : this( text { for ((string, style) in contents) literal(string, style) @@ -19,23 +23,37 @@ class StyledString private constructor( contents.sumOf { it.first.length } ) - constructor(text: Component) : this( - text.apply { - for (component in iterator(ComponentIteratorType.DEPTH_FIRST)) { - if (component !is TextComponent) { + constructor(text: ComponentLike) : this( + text.asComponent().also { component -> + for (comp in component) { + if (comp !is TextComponent) { throw IllegalArgumentException("StyledString must only consist of TextComponents") } } }, - text.iterable(ComponentIteratorType.DEPTH_FIRST) + text.asComponent().iterable(ComponentIteratorType.DEPTH_FIRST) .sumOf { (it as TextComponent).content().length } ) - override fun iterator() = iterator { - for (component in text.iterator(ComponentIteratorType.DEPTH_FIRST)) { - for (char in (component as TextComponent).content()) { - yield(StyledChar(char, component.style())) + override fun iterator() = components + .flatMap { component -> + component.content().map { StyledChar(it, component.style()) } + } + .iterator() + + fun substring(startIndex: Int, endIndex: Int = size) = StyledString(text { + var index = 0 + for (component in components) { + if (index >= endIndex) return@text + val nextIndex = index + component.content().length + if (nextIndex > startIndex) { + val substring = component.content().substring( + max(0, startIndex - index), + min(component.content().length, endIndex - index) + ) + literal(substring, style(component.style())) } + index = nextIndex } - } + }) } \ No newline at end of file diff --git a/src/main/java/io/github/homchom/recode/ui/text/StyledStringManipulation.kt b/src/main/java/io/github/homchom/recode/ui/text/StyledStringManipulation.kt deleted file mode 100644 index dcbea432..00000000 --- a/src/main/java/io/github/homchom/recode/ui/text/StyledStringManipulation.kt +++ /dev/null @@ -1,2 +0,0 @@ -package io.github.homchom.recode.ui.text - diff --git a/src/main/java/io/github/homchom/recode/ui/text/TextExtensions.kt b/src/main/java/io/github/homchom/recode/ui/text/TextExtensions.kt index 344b7786..026ce9e0 100644 --- a/src/main/java/io/github/homchom/recode/ui/text/TextExtensions.kt +++ b/src/main/java/io/github/homchom/recode/ui/text/TextExtensions.kt @@ -3,6 +3,7 @@ package io.github.homchom.recode.ui.text import net.kyori.adventure.text.Component +import net.kyori.adventure.text.ComponentIteratorType import net.kyori.adventure.text.format.Style /** @@ -38,15 +39,22 @@ fun Component.toFlatList() = buildList { /** * Returns a flattened [Sequence] of this [Component]'s nodes, where parent and child styles are recursively merged. + * + * @see toFlatList */ fun Component.asFlatSequence(): Sequence = sequence { yield(this@asFlatSequence) for (child in children()) { - val merged = mergeStyle(child) + val merged = child.mergeStyle(this@asFlatSequence) yieldAll(merged.asFlatSequence()) } } +/** + * @see Component.iterator + */ +operator fun Component.iterator(): Iterator = iterator(ComponentIteratorType.DEPTH_FIRST) + /** * @return Whether this [Component] equals [other] when flattened. */ diff --git a/src/main/java/io/github/homchom/recode/util/coroutines/RateLimiter.kt b/src/main/java/io/github/homchom/recode/util/coroutines/RateLimiter.kt index bdf027a9..77bd4f12 100644 --- a/src/main/java/io/github/homchom/recode/util/coroutines/RateLimiter.kt +++ b/src/main/java/io/github/homchom/recode/util/coroutines/RateLimiter.kt @@ -34,7 +34,7 @@ class RateLimiter @OptIn(DelicateCoroutinesApi::class) constructor( /** * Records the occurrence of an event that should be rate-limited. * - * @return `false` if the rate + * @return `false` if the rate limit was exceeded. */ fun record(): Boolean { val currentCount = _count.updateAndGet { it + 1 }