From 974179cb1f872dd0c69b30af644ff47ced34beb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:16:21 +0800 Subject: [PATCH 1/6] feat(legacy): Async tick support 1. waitTicks and waitUntil are available for all suspend functions 2. EventHook.Terminate for once operations --- .../net/ccbluex/liquidbounce/LiquidBounce.kt | 2 - .../ccbluex/liquidbounce/event/EventHook.kt | 23 +++- .../liquidbounce/event/EventManager.kt | 62 +++------- .../ccbluex/liquidbounce/event/Listenable.kt | 16 +++ .../liquidbounce/event/async/LoopManager.kt | 57 +++++++++ .../liquidbounce/event/async/TickScheduler.kt | 109 ++++++++++++++++++ .../module/modules/combat/TickBase.kt | 5 +- .../features/module/modules/movement/Step.kt | 28 +++-- .../utils/kotlin/CoroutineExtensions.kt | 6 - .../utils/timing/TickedActions.kt | 15 ++- .../liquidbounce/utils/timing/WaitMsUtils.kt | 44 ------- .../utils/timing/WaitTickUtils.kt | 16 ++- 12 files changed, 252 insertions(+), 131 deletions(-) create mode 100644 src/main/java/net/ccbluex/liquidbounce/event/async/LoopManager.kt create mode 100644 src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt delete mode 100644 src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitMsUtils.kt diff --git a/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt b/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt index 096474ced17..2baaf42aa77 100644 --- a/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt +++ b/src/main/java/net/ccbluex/liquidbounce/LiquidBounce.kt @@ -58,7 +58,6 @@ import net.ccbluex.liquidbounce.utils.render.MiniMapRegister import net.ccbluex.liquidbounce.utils.render.shader.Background import net.ccbluex.liquidbounce.utils.rotation.RotationUtils import net.ccbluex.liquidbounce.utils.timing.TickedActions -import net.ccbluex.liquidbounce.utils.timing.WaitMsUtils import net.ccbluex.liquidbounce.utils.timing.WaitTickUtils import java.util.concurrent.CompletableFuture import java.util.concurrent.Future @@ -181,7 +180,6 @@ object LiquidBounce { BPSUtils WaitTickUtils SilentHotbar - WaitMsUtils BlinkUtils // Load settings diff --git a/src/main/java/net/ccbluex/liquidbounce/event/EventHook.kt b/src/main/java/net/ccbluex/liquidbounce/event/EventHook.kt index b90f4547bfd..c6b8f6937cb 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/EventHook.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/EventHook.kt @@ -9,6 +9,9 @@ sealed class EventHook( val always: Boolean, val priority: Byte, ) { + val isActive: Boolean + get() = this.owner.handleEvents() || this.always + class Blocking( owner: Listenable, always: Boolean = false, @@ -16,6 +19,23 @@ sealed class EventHook( val action: (T) -> Unit ) : EventHook(owner, always, priority) + class Terminate( + owner: Listenable, + always: Boolean = false, + priority: Byte = 0, + maxExecutionTime: Int = 1, + val action: (T) -> Unit + ) : EventHook(owner, always, priority) { + init { + require(maxExecutionTime > 0) + } + + var remaining = maxExecutionTime + private set + + fun shouldStop(): Boolean = !isActive || remaining-- > 0 + } + class Async( owner: Listenable, /** @@ -32,6 +52,3 @@ sealed class EventHook( val action: suspend CoroutineScope.(T) -> Unit ) : EventHook(owner, always, priority) } - -val EventHook<*>.isActive: Boolean - get() = this.owner.handleEvents() || this.always diff --git a/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt b/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt index 13cc0848ddd..8c7f8766e67 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt @@ -9,8 +9,11 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import net.ccbluex.liquidbounce.event.async.LoopManager +import net.ccbluex.liquidbounce.event.async.TickScheduler import net.ccbluex.liquidbounce.utils.client.ClientUtils import java.util.* +import java.util.concurrent.CopyOnWriteArrayList import kotlin.coroutines.cancellation.CancellationException /** @@ -40,11 +43,12 @@ private fun List>.findIndexByPriority(item: EventHook<*>): Int { */ object EventManager : CoroutineScope by CoroutineScope(SupervisorJob()) { private val registry = ALL_EVENT_CLASSES.associateWithTo(IdentityHashMap(ALL_EVENT_CLASSES.size)) { - ArrayList>() + CopyOnWriteArrayList>() } init { LoopManager + TickScheduler } fun unregisterEventHook(eventClass: Class, eventHook: EventHook) { @@ -85,6 +89,17 @@ object EventManager : CoroutineScope by CoroutineScope(SupervisorJob()) { } } + is EventHook.Terminate -> { + try { + action(event) + } catch (e: Exception) { + ClientUtils.LOGGER.error("Exception during call event (terminate, remaining=${this.remaining})", e) + } + if (this.shouldStop()) { + unregisterEventHook(event::class.java, this) + } + } + is EventHook.Async -> { launch(dispatcher) { try { @@ -120,48 +135,3 @@ object EventManager : CoroutineScope by CoroutineScope(SupervisorJob()) { } } - -/** - * This manager will start a job for each hook. - * - * Once the job is finished, the next [UpdateEvent] (any stateless event is OK for this) will start a new one. - * - * This is designed to run **asynchronous** tasks instead of tick loops. - * - * @author MukjepScarlet - */ -internal object LoopManager : Listenable, CoroutineScope by CoroutineScope(SupervisorJob()) { - private val registry = IdentityHashMap, Job?>() - - operator fun plusAssign(eventHook: EventHook.Async) { - registry[eventHook] = null - } - - operator fun minusAssign(eventHook: EventHook.Async) { - registry.remove(eventHook) - } - - init { - handler(priority = Byte.MAX_VALUE) { - for ((eventHook, job) in registry) { - if (eventHook.isActive) { - if (job == null || !job.isActive) { - registry[eventHook] = launch(eventHook.dispatcher) { - try { - eventHook.action(this, UpdateEvent) - } catch (e: CancellationException) { - // The job is canceled due to handler is no longer active - return@launch - } catch (e: Exception) { - ClientUtils.LOGGER.error("Exception during loop in", e) - } - } - } - } else if (job != null) { - job.cancel() - registry[eventHook] = null - } - } - } - } -} diff --git a/src/main/java/net/ccbluex/liquidbounce/event/Listenable.kt b/src/main/java/net/ccbluex/liquidbounce/event/Listenable.kt index 15354b260ed..3824d2c98a1 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/Listenable.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/Listenable.kt @@ -8,6 +8,7 @@ package net.ccbluex.liquidbounce.event import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import net.ccbluex.liquidbounce.event.async.LoopManager interface Listenable { fun handleEvents(): Boolean = parent?.handleEvents() ?: true @@ -32,6 +33,21 @@ inline fun Listenable.handler( EventManager.registerEventHook(T::class.java, EventHook.Blocking(this, always, priority, action)) } +inline fun Listenable.terminateHandler( + always: Boolean = false, + priority: Byte = 0, + maxExecutionTime: Int, + noinline action: (T) -> Unit +) { + EventManager.registerEventHook(T::class.java, EventHook.Terminate(this, always, priority, maxExecutionTime, action)) +} + +inline fun Listenable.once( + always: Boolean = false, + priority: Byte = 0, + noinline action: (T) -> Unit +) = terminateHandler(always, priority, maxExecutionTime = 1, action) + inline fun Listenable.handler( dispatcher: CoroutineDispatcher, always: Boolean = false, diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/LoopManager.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/LoopManager.kt new file mode 100644 index 00000000000..1cae9026f16 --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/LoopManager.kt @@ -0,0 +1,57 @@ +/* + * LiquidBounce Hacked Client + * A free open source mixin-based injection hacked client for Minecraft using Minecraft Forge. + * https://github.com/CCBlueX/LiquidBounce/ + */ +package net.ccbluex.liquidbounce.event.async + +import kotlinx.coroutines.* +import net.ccbluex.liquidbounce.event.* + +import net.ccbluex.liquidbounce.utils.client.ClientUtils +import java.util.* + +/** + * This manager will start a job for each hook. + * + * Once the job is finished, the next [UpdateEvent] (any stateless event is OK for this) will start a new one. + * + * This is designed to run **asynchronous** tasks instead of tick loops. + * + * @author MukjepScarlet + */ +internal object LoopManager : Listenable, CoroutineScope by CoroutineScope(SupervisorJob()) { + private val registry = IdentityHashMap, Job?>() + + operator fun plusAssign(eventHook: EventHook.Async) { + registry[eventHook] = null + } + + operator fun minusAssign(eventHook: EventHook.Async) { + registry.remove(eventHook) + } + + init { + handler(priority = Byte.MAX_VALUE) { + for ((eventHook, job) in registry) { + if (eventHook.isActive) { + if (job == null || !job.isActive) { + registry[eventHook] = launch(eventHook.dispatcher) { + try { + eventHook.action(this, UpdateEvent) + } catch (e: CancellationException) { + // The job is canceled due to handler is no longer active + return@launch + } catch (e: Exception) { + ClientUtils.LOGGER.error("Exception during loop in", e) + } + } + } + } else if (job != null) { + job.cancel() + registry[eventHook] = null + } + } + } + } +} diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt new file mode 100644 index 00000000000..0d1b1cae962 --- /dev/null +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt @@ -0,0 +1,109 @@ +/* + * LiquidBounce Hacked Client + * A free open source mixin-based injection hacked client for Minecraft using Minecraft Forge. + * https://github.com/CCBlueX/LiquidBounce/ + */ +package net.ccbluex.liquidbounce.event.async + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import net.ccbluex.liquidbounce.event.GameTickEvent +import net.ccbluex.liquidbounce.event.Listenable +import net.ccbluex.liquidbounce.event.PacketEvent +import net.ccbluex.liquidbounce.event.handler +import net.ccbluex.liquidbounce.utils.client.MinecraftInstance +import java.util.function.BooleanSupplier +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.RestrictsSuspension +import kotlin.coroutines.resume + +/** + * This manager is for suspend tick functions. + * + * **ANY** scopes without [RestrictsSuspension] annotation can use wait actions. + * + * Note: These functions will be called on [Dispatchers.Main] (the Render thread). + * + * Most of the game events are called from the Render thread, except of [PacketEvent], it's called from the Netty client thread. + * You should carefully use this to prevent unexpected thread issue. + * + * @author MukjepScarlet + */ +object TickScheduler : Listenable, MinecraftInstance { + + private val schedules = arrayListOf() + + init { + handler(priority = Byte.MIN_VALUE) { + schedules.removeIf { it.asBoolean } + } + } + + fun addScheduled(breakLoop: BooleanSupplier) { + if (mc.isCallingFromMinecraftThread) { + schedules += breakLoop + } else { + mc.addScheduledTask { schedules += breakLoop } + } + } + +} + +/** + * Wait until given [condition] returns true. + * + * @param condition It will be called on [Dispatchers.Main] (the Render thread) + * @return Total ticks during waiting + */ +suspend inline fun waitUntil(crossinline condition: () -> Boolean): Int = + suspendCancellableCoroutine { cont -> + var waitingTick = -1 + TickScheduler.addScheduled { + waitingTick++ + if (condition()) { + cont.resume(waitingTick) + true + } else { + false + } + } + } + +/** + * Wait for given [ticks]. + */ +suspend fun waitTicks(ticks: Int) { + require(ticks >= 0) { "Negative tick: $ticks" } + + if (ticks == 0) { + return + } + + var remainingTick = ticks + waitUntil { --remainingTick == 0 } +} + +/** + * Start a tick sequence for given [Listenable] + * which will be cancelled if [Listenable.handleEvents] of the owner returns false + */ +fun Listenable.tickSequence( + context: CoroutineContext = Dispatchers.Unconfined, + body: suspend () -> Unit +) { + val job = GlobalScope.launch(context) { + body() + } + + TickScheduler.addScheduled { + when { + !this@tickSequence.handleEvents() -> { + job.cancel() + true + } + else -> job.isCompleted + } + } +} diff --git a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/combat/TickBase.kt b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/combat/TickBase.kt index 1c67493574e..a5c5e478c27 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/combat/TickBase.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/combat/TickBase.kt @@ -14,7 +14,6 @@ import net.ccbluex.liquidbounce.utils.kotlin.RandomUtils import net.ccbluex.liquidbounce.utils.render.RenderUtils.glColor import net.ccbluex.liquidbounce.utils.rotation.RotationUtils import net.ccbluex.liquidbounce.utils.simulation.SimulatedPlayer -import net.ccbluex.liquidbounce.utils.timing.WaitMsUtils import net.ccbluex.liquidbounce.utils.timing.WaitTickUtils import net.minecraft.entity.EntityLivingBase import net.minecraft.network.play.server.S08PacketPlayerPosLook @@ -117,7 +116,7 @@ object TickBase : Module("TickBase", Category.COMBAT) { WaitTickUtils.schedule(skipTicks) { tick() - WaitMsUtils.schedule(this) { + once { duringTickModification = false } } @@ -127,7 +126,7 @@ object TickBase : Module("TickBase", Category.COMBAT) { ticksToSkip = skipTicks WaitTickUtils.schedule(skipTicks) { - WaitMsUtils.schedule(this) { + once { duringTickModification = false } } diff --git a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/movement/Step.kt b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/movement/Step.kt index e5f1238dcfd..735ff70bc50 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/module/modules/movement/Step.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/module/modules/movement/Step.kt @@ -6,6 +6,7 @@ package net.ccbluex.liquidbounce.features.module.modules.movement import net.ccbluex.liquidbounce.event.* +import net.ccbluex.liquidbounce.event.async.waitTicks import net.ccbluex.liquidbounce.features.module.Category import net.ccbluex.liquidbounce.features.module.Module import net.ccbluex.liquidbounce.features.module.modules.exploit.Phase @@ -71,13 +72,13 @@ object Step : Module("Step", Category.MOVEMENT, gameDetecting = false) { thePlayer.stepHeight = 0.6F } - val onUpdate = handler { + val onUpdate = loopHandler { val mode = mode - val thePlayer = mc.thePlayer ?: return@handler + val thePlayer = mc.thePlayer ?: return@loopHandler - if (thePlayer.isOnLadder || thePlayer.isInLiquid || thePlayer.isInWeb) return@handler + if (thePlayer.isOnLadder || thePlayer.isInLiquid || thePlayer.isInWeb) return@loopHandler - if (!thePlayer.isMoving) return@handler + if (!thePlayer.isMoving) return@loopHandler // Motion steps when (mode) { @@ -93,7 +94,7 @@ object Step : Module("Step", Category.MOVEMENT, gameDetecting = false) { if (!couldStep() || chest.isNotEmpty()) { mc.timer.timerSpeed = 1f - return@handler + return@loopHandler } fakeJump() @@ -101,16 +102,13 @@ object Step : Module("Step", Category.MOVEMENT, gameDetecting = false) { // TODO: Improve Timer Balancing mc.timer.timerSpeed = 5f - WaitTickUtils.schedule(1) { - mc.timer.timerSpeed = 0.2f - } - WaitTickUtils.schedule(2) { - mc.timer.timerSpeed = 4f - } - WaitTickUtils.schedule(3) { - strafe(0.27F) - mc.timer.timerSpeed = 1f - } + waitTicks(1) + mc.timer.timerSpeed = 0.2f + waitTicks(1) + mc.timer.timerSpeed = 4f + waitTicks(1) + strafe(0.27F) + mc.timer.timerSpeed = 1f } "LAAC" -> diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/kotlin/CoroutineExtensions.kt b/src/main/java/net/ccbluex/liquidbounce/utils/kotlin/CoroutineExtensions.kt index accd536c9ec..6472718dce0 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/kotlin/CoroutineExtensions.kt +++ b/src/main/java/net/ccbluex/liquidbounce/utils/kotlin/CoroutineExtensions.kt @@ -44,9 +44,3 @@ private object RenderDispatcher : CoroutineDispatcher() { } } } - -suspend fun waitUntil(period: Long = 10L, condition: () -> Boolean) { - while (!condition()) { - delay(period) - } -} diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/timing/TickedActions.kt b/src/main/java/net/ccbluex/liquidbounce/utils/timing/TickedActions.kt index 405a5abc063..eb274c5b55f 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/timing/TickedActions.kt +++ b/src/main/java/net/ccbluex/liquidbounce/utils/timing/TickedActions.kt @@ -6,8 +6,9 @@ package net.ccbluex.liquidbounce.utils.timing import net.ccbluex.liquidbounce.event.* +import net.ccbluex.liquidbounce.event.async.TickScheduler +import net.ccbluex.liquidbounce.event.async.waitUntil import net.ccbluex.liquidbounce.features.module.Module -import net.ccbluex.liquidbounce.utils.kotlin.waitUntil import net.minecraft.item.ItemStack import java.util.concurrent.ConcurrentLinkedQueue @@ -15,14 +16,14 @@ object TickedActions : Listenable { private class Action( val owner: Module, val id: Int, - val action: () -> Unit + val action: Runnable ) private val actions = ConcurrentLinkedQueue() private val calledThisTick = LinkedHashSet() - private fun schedule(id: Int, module: Module, allowDuplicates: Boolean = false, action: () -> Unit) = + private fun schedule(id: Int, module: Module, allowDuplicates: Boolean = false, action: Runnable) = if (allowDuplicates || !isScheduled(id, module)) { actions += Action(module, id, action) true @@ -42,7 +43,7 @@ object TickedActions : Listenable { actions.toCollection(calledThisTick) calledThisTick.forEach { - it.action.invoke() + it.action.run() if (actions.isNotEmpty()) { actions.remove() } @@ -67,10 +68,12 @@ object TickedActions : Listenable { callback.invoke(newStack) } - fun Module.nextTick(id: Int = -1, allowDuplicates: Boolean = true, action: () -> Unit) = + fun Module.nextTick(id: Int = -1, allowDuplicates: Boolean = true, action: Runnable) = schedule(id, this, allowDuplicates, action) - suspend fun Module.awaitTicked() = waitUntil { hasNoTicked() } + suspend fun Module.awaitTicked() { + waitUntil { hasNoTicked() } + } fun Module.isTicked(id: Int) = isScheduled(id, this) diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitMsUtils.kt b/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitMsUtils.kt deleted file mode 100644 index 5df7e95d437..00000000000 --- a/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitMsUtils.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * LiquidBounce Hacked Client - * A free open source mixin-based injection hacked client for Minecraft using Minecraft Forge. - * https://github.com/CCBlueX/LiquidBounce/ - */ -package net.ccbluex.liquidbounce.utils.timing - -import net.ccbluex.liquidbounce.event.GameLoopEvent -import net.ccbluex.liquidbounce.event.Listenable -import net.ccbluex.liquidbounce.event.handler -import net.ccbluex.liquidbounce.utils.client.MinecraftInstance - -object WaitMsUtils : MinecraftInstance, Listenable { - - private val scheduledActions = mutableListOf() - - fun schedule(requester: Any? = null, ms: Long = 1L, action: () -> Unit = { }) = - conditionalSchedule(requester, ms) { action(); true } - - fun conditionalSchedule(requester: Any? = null, ms: Long? = null, action: () -> Boolean) { - if (ms == 0L) { - action() - - return - } - - synchronized(scheduledActions) { - scheduledActions += ScheduledAction(requester, System.currentTimeMillis() + (ms ?: 0L), action) - } - } - - fun hasScheduled(obj: Any) = scheduledActions.firstOrNull { it.requester == obj } != null - - val onRender = handler(priority = -1) { - synchronized(scheduledActions) { - scheduledActions.removeIf { - System.currentTimeMillis() >= it.ms && it.action() - } - } - } - - private data class ScheduledAction(val requester: Any?, val ms: Long, val action: () -> Boolean) - -} \ No newline at end of file diff --git a/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitTickUtils.kt b/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitTickUtils.kt index 4881ec59737..9001729b97d 100644 --- a/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitTickUtils.kt +++ b/src/main/java/net/ccbluex/liquidbounce/utils/timing/WaitTickUtils.kt @@ -12,6 +12,7 @@ import net.ccbluex.liquidbounce.utils.client.ClientUtils import net.ccbluex.liquidbounce.utils.client.MinecraftInstance import net.ccbluex.liquidbounce.utils.kotlin.removeEach +//@Deprecated("Use TickScheduler instead") object WaitTickUtils : MinecraftInstance, Listenable { private val scheduledActions = ArrayDeque() @@ -23,7 +24,7 @@ object WaitTickUtils : MinecraftInstance, Listenable { requester: Any? = null, ticks: Int? = null, isConditional: Boolean = true, - action: (Int) -> Boolean? + action: (tick: Int) -> Boolean? ) { if (ticks == 0) { action(0) @@ -45,11 +46,14 @@ object WaitTickUtils : MinecraftInstance, Listenable { val elapsed = action.duration - (action.ticks - currentTick) val shouldRemove = currentTick >= action.ticks - return@removeEach when { - !action.isConditional -> { - { action.action(elapsed) ?: true }.takeIf { shouldRemove }?.invoke() ?: false + if (!action.isConditional) { + if (shouldRemove) { + action.action(elapsed) ?: true + } else { + false } - else -> action.action(elapsed) ?: shouldRemove + } else { + action.action(elapsed) ?: shouldRemove } } } @@ -59,7 +63,7 @@ object WaitTickUtils : MinecraftInstance, Listenable { val duration: Int, val isConditional: Boolean, val ticks: Int, - val action: (Int) -> Boolean? + val action: (tick: Int) -> Boolean? ) } \ No newline at end of file From 991d953428b955e3a3b79ce86854b5fd63b7b548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:33:26 +0800 Subject: [PATCH 2/6] remove async tasks --- .../liquidbounce/event/EventManager.kt | 33 ++++++++++++++++++- .../liquidbounce/features/module/Module.kt | 2 ++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt b/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt index 8c7f8766e67..2a84e36b4af 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/EventManager.kt @@ -11,8 +11,10 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch import net.ccbluex.liquidbounce.event.async.LoopManager import net.ccbluex.liquidbounce.event.async.TickScheduler +import net.ccbluex.liquidbounce.event.async.waitTicks import net.ccbluex.liquidbounce.utils.client.ClientUtils import java.util.* +import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import kotlin.coroutines.cancellation.CancellationException @@ -46,13 +48,41 @@ object EventManager : CoroutineScope by CoroutineScope(SupervisorJob()) { CopyOnWriteArrayList>() } + private class AsyncTask(val owner: EventHook<*>, val job: Job) + + private val jobs = CopyOnWriteArrayList() + init { LoopManager TickScheduler + + LoopManager.loopHandler { + jobs.removeIf { !it.owner.isActive || !it.job.isActive } + waitTicks(1) + } + } + + fun Listenable.cancelAsyncJobs() { + jobs.removeIf { + if (it.owner.owner === this) { + it.job.cancel() + true + } else { + false + } + } } fun unregisterEventHook(eventClass: Class, eventHook: EventHook) { registry[eventClass]!!.remove(eventHook) + jobs.removeIf { + if (it.owner === eventHook) { + it.job.cancel() + true + } else { + false + } + } } fun registerEventHook(eventClass: Class, eventHook: EventHook): EventHook { @@ -101,13 +131,14 @@ object EventManager : CoroutineScope by CoroutineScope(SupervisorJob()) { } is EventHook.Async -> { - launch(dispatcher) { + val job = launch(dispatcher) { try { action(this, event) } catch (e: Exception) { ClientUtils.LOGGER.error("Exception during call event (async)", e) } } + jobs += AsyncTask(this, job) } } } diff --git a/src/main/java/net/ccbluex/liquidbounce/features/module/Module.kt b/src/main/java/net/ccbluex/liquidbounce/features/module/Module.kt index 8e5a0745ab8..bb0f7175a49 100644 --- a/src/main/java/net/ccbluex/liquidbounce/features/module/Module.kt +++ b/src/main/java/net/ccbluex/liquidbounce/features/module/Module.kt @@ -7,6 +7,7 @@ package net.ccbluex.liquidbounce.features.module import net.ccbluex.liquidbounce.LiquidBounce.isStarting import net.ccbluex.liquidbounce.config.Configurable +import net.ccbluex.liquidbounce.event.EventManager.cancelAsyncJobs import net.ccbluex.liquidbounce.event.Listenable import net.ccbluex.liquidbounce.features.module.modules.misc.GameDetector import net.ccbluex.liquidbounce.file.FileManager.modulesConfig @@ -133,6 +134,7 @@ open class Module( if (canBeEnabled) field = true } else { + cancelAsyncJobs() onDisable() field = false } From 290035f722e856f12aaf976fb23ac736a550d31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Mon, 24 Feb 2025 22:44:47 +0800 Subject: [PATCH 3/6] tickSequence --- .../ccbluex/liquidbounce/event/async/TickScheduler.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt index 0d1b1cae962..66aeb813bba 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt @@ -5,10 +5,7 @@ */ package net.ccbluex.liquidbounce.event.async -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.* import net.ccbluex.liquidbounce.event.GameTickEvent import net.ccbluex.liquidbounce.event.Listenable import net.ccbluex.liquidbounce.event.PacketEvent @@ -91,11 +88,9 @@ suspend fun waitTicks(ticks: Int) { */ fun Listenable.tickSequence( context: CoroutineContext = Dispatchers.Unconfined, - body: suspend () -> Unit + body: suspend CoroutineScope.() -> Unit ) { - val job = GlobalScope.launch(context) { - body() - } + val job = GlobalScope.launch(context, block = body) TickScheduler.addScheduled { when { From 6d9288ba6659baba250d9f1a6f2b597cd7f2acf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Mon, 24 Feb 2025 23:17:33 +0800 Subject: [PATCH 4/6] exception handler in waitUntil --- .../liquidbounce/event/async/TickScheduler.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt index 66aeb813bba..c58db5ff811 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt @@ -15,6 +15,7 @@ import java.util.function.BooleanSupplier import kotlin.coroutines.CoroutineContext import kotlin.coroutines.RestrictsSuspension import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException /** * This manager is for suspend tick functions. @@ -59,11 +60,16 @@ suspend inline fun waitUntil(crossinline condition: () -> Boolean): Int = var waitingTick = -1 TickScheduler.addScheduled { waitingTick++ - if (condition()) { - cont.resume(waitingTick) + try { + if (condition()) { + cont.resume(waitingTick) + true + } else { + false + } + } catch (e: Throwable) { + cont.resumeWithException(e) true - } else { - false } } } From 7742f6e0f69b895a663977d17c7465e48fb12c44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E8=91=89=20Scarlet?= <93977077+mukjepscarlet@users.noreply.github.com> Date: Tue, 25 Feb 2025 00:19:00 +0800 Subject: [PATCH 5/6] rename --- .../ccbluex/liquidbounce/event/async/TickScheduler.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt index c58db5ff811..240f3e107a1 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt @@ -39,7 +39,12 @@ object TickScheduler : Listenable, MinecraftInstance { } } - fun addScheduled(breakLoop: BooleanSupplier) { + /** + * Add a task for scheduling. + * + * @param breakLoop Stop tick the body when it returns `true` + */ + fun schedule(breakLoop: BooleanSupplier) { if (mc.isCallingFromMinecraftThread) { schedules += breakLoop } else { @@ -58,7 +63,7 @@ object TickScheduler : Listenable, MinecraftInstance { suspend inline fun waitUntil(crossinline condition: () -> Boolean): Int = suspendCancellableCoroutine { cont -> var waitingTick = -1 - TickScheduler.addScheduled { + TickScheduler.schedule { waitingTick++ try { if (condition()) { @@ -98,7 +103,7 @@ fun Listenable.tickSequence( ) { val job = GlobalScope.launch(context, block = body) - TickScheduler.addScheduled { + TickScheduler.schedule { when { !this@tickSequence.handleEvents() -> { job.cancel() From 0693b36a89339a57c1d46df03fe56aca13126bf6 Mon Sep 17 00:00:00 2001 From: MukjepScarlet <93977077+MukjepScarlet@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:02:28 +0800 Subject: [PATCH 6/6] priority --- .../java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt index 240f3e107a1..933c5fe2f56 100644 --- a/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt +++ b/src/main/java/net/ccbluex/liquidbounce/event/async/TickScheduler.kt @@ -34,7 +34,7 @@ object TickScheduler : Listenable, MinecraftInstance { private val schedules = arrayListOf() init { - handler(priority = Byte.MIN_VALUE) { + handler(priority = Byte.MAX_VALUE) { schedules.removeIf { it.asBoolean } } }