From 741e7a8439fa62cd2df41d8bd9a1d369e922812e Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 18:29:08 -0700 Subject: [PATCH 1/9] Added `SuspendableStep` to `game.model.coroutine.suspendable.step` --- .../suspendable/step/SuspendableStep.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/SuspendableStep.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/SuspendableStep.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/SuspendableStep.kt new file mode 100644 index 0000000000..2ad2d02185 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/SuspendableStep.kt @@ -0,0 +1,26 @@ +package gg.rsmod.game.model.coroutine.suspendable.step + +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume + +/** + * Represents a continuation that can be suspended adn stepped over in the future depending on a condition. + * + * @author Curtis Woodard + */ +interface SuspendableStep: Continuation { + /** @property [predicate] The predicate lambda that decides whether or not to resume this [SuspendableStep] */ + val predicate: () -> Boolean + + /** @property [continuation] The [Continuation] to step over */ + val continuation: Continuation + + /** + * Decides whether or not to resume [continuation] + * @return [Boolean] + */ + fun canResume(): Boolean = predicate() + + /** Resumes the next step of [continuation] */ + fun resume() = continuation.resume(Unit) +} \ No newline at end of file From abb45ba4a6b4db91b4f7dd691ac700ef8305b43c Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 18:34:52 -0700 Subject: [PATCH 2/9] Added `SuspendableInterface` to `game.model.coroutine.suspendable.condition` --- .../condition/SuspendableCondition.kt | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/SuspendableCondition.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/SuspendableCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/SuspendableCondition.kt new file mode 100644 index 0000000000..1052cbbe59 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/SuspendableCondition.kt @@ -0,0 +1,23 @@ +package gg.rsmod.game.model.coroutine.suspendable.condition + +/** + * Represents a condition that can be invoked. + * Meant to be used as a condition for suspending something. + * + * @author Curtis Woodard + */ +interface SuspendableCondition: () -> Boolean { + /** + * Should be called to decide whether or not to continue the thing this is inside. + * + * @return [Boolean] + */ + fun canResume(): Boolean + + /** + * Emulates the boolean predicate lambda functionality that this "implements" + * + * @return [Boolean] + */ + override fun invoke(): Boolean = canResume() +} From 42346afddb3da21bf2b54ce2c4cfd23e568f11b1 Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 18:40:06 -0700 Subject: [PATCH 3/9] Added a simple `SuspendableStep` implementation, `SuspendableStepImpl`, to `game.model.coroutine.suspendable.step.impl` --- .../step/impl/SuspendableStepImpl.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/impl/SuspendableStepImpl.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/impl/SuspendableStepImpl.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/impl/SuspendableStepImpl.kt new file mode 100644 index 0000000000..feec12ccb2 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/step/impl/SuspendableStepImpl.kt @@ -0,0 +1,20 @@ +package gg.rsmod.game.model.coroutine.suspendable.step.impl + +import gg.rsmod.game.model.coroutine.suspendable.condition.SuspendableCondition +import gg.rsmod.game.model.coroutine.suspendable.step.SuspendableStep +import kotlin.coroutines.Continuation + +/** + * A simple sample implementation of [SuspendableStep] + * + * @property [predicate] A [SuspendableCondition] (which extends `() -> Unit`) that decides whether or not to step + * on to the next step of this [SuspendableStep] + * @property [continuation] The [Continuation] that is being stepped over + * and the [Continuation] to which [SuspendableStepImpl]'s [Continuation] functionality is delegated to. + * + * @author Curtis Woodard + */ +class SuspendableStepImpl( + override val predicate: SuspendableCondition, + override val continuation: Continuation +): SuspendableStep, Continuation by continuation From 3887d909cbe80ae366184c53fdb3a16602e6698a Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 18:48:20 -0700 Subject: [PATCH 4/9] Added `CyclableTask` to `game.model.coroutine.cyclable.task` --- .../coroutine/cyclable/task/CyclableTask.kt | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/task/CyclableTask.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/task/CyclableTask.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/task/CyclableTask.kt new file mode 100644 index 0000000000..9c30caf77c --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/task/CyclableTask.kt @@ -0,0 +1,117 @@ +package gg.rsmod.game.model.coroutine.cyclable.task + +import gg.rsmod.game.model.coroutine.suspendable.step.SuspendableStep +import gg.rsmod.game.model.queue.TaskPriority +import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.Continuation +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +/** + * Represents a [Continuation] that can be stepped over in cycles according to an outside source. + * These can potentially never finish their logic + * + * @author Curtis Woodard + */ +interface CyclableTask: Continuation { + /** + * @property [ctx] + * An object that should be casted to a certain type depending on where it is used. + */ + val ctx: Any + + /** + * @property [priority] + * The execution priority of this task. + */ + val priority: TaskPriority + + /** + * @property [context] + * The [CoroutineContext] implementation for this task. + */ + override val context: CoroutineContext + get() = EmptyCoroutineContext + + /** + * @property [terminateAction] + * Represents an action that should be executed only if this action was terminated via [terminate] + */ + val terminateAction: Function + + /** + * @property [onResultException] + * Represents an action that should be executed only when [resumeWith] `result` paramteter is an exception or `null` + */ + var onResultException: (Throwable) -> Unit + + /** + * @property [coroutine] + * The [Continuation] that will be cycled over + */ + var coroutine: Continuation + + /** + * @property [invoked] + * Whether or not this task's logic has been invoked during the cycle. + */ + var invoked: Boolean + + /** + * @property [nextStep] + * The next [SuspendableStep] that will be executed once it's [SuspendableCondition] returns `true` + */ + var nextStep: SuspendableStep? + + /** + * @property [currentCycle] + * The cycle of the current task + */ + val currentCycle: AtomicInteger + + /** + * @property [lastcycle] + * The last cycle used for cycle-based logic in the current task + */ + val lastCycle: AtomicInteger + + /** + * @property [requestReturnValue] + * Value requested by a task e.g. an input for a dialogue. + */ + var requestReturnValue: Any? + + /** + * Terminate the execution of the rest of this task. + * Call [terminateAction] if applicable. + */ + fun terminate() + + /** + * Whether or not this task is suspended. + * + * @return [Boolean] + */ + fun suspended(): Boolean = nextStep is SuspendableStep + + /** + * Synchronizes the logic with the rest of the game thread. + */ + fun cycle() { + val next = nextStep ?: return + + if (next.canResume()) { + next.resume() + } + + currentCycle.incrementAndGet() + } + + /** + * Resumes [coroutine] with [result] + */ + override fun resumeWith(result: Result) { + nextStep = null + onResultException(result.exceptionOrNull() ?: return) + } +} \ No newline at end of file From a4a2d6d7d47a7fe9d52971cf07e1375af3f3d904 Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 18:54:08 -0700 Subject: [PATCH 5/9] Added `CyclableTaskSet` to `game.model.coroutine.cyclable.set` --- .../coroutine/cyclable/set/CyclableTaskSet.kt | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/set/CyclableTaskSet.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/set/CyclableTaskSet.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/set/CyclableTaskSet.kt new file mode 100644 index 0000000000..561d29a497 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/cyclable/set/CyclableTaskSet.kt @@ -0,0 +1,61 @@ +package gg.rsmod.game.model.coroutine.cyclable.set + +import gg.rsmod.game.model.coroutine.cyclable.task.CyclableTask +import gg.rsmod.game.model.queue.TaskPriority +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import java.util.LinkedList + +/** + * The data structure created to queue up [T], cycle over them and get rid of them. + * + * @author Curtis Woodard + */ +interface CyclableTaskSet { + /** + * @property [queue] + * Holds all of the [T] that will be cycled over when [cycle] is called + */ + val queue: LinkedList + + val size: Int get() = queue.size + + /** + * Cycles over each [T] in [queue] + */ + fun cycle() + + /** + * The function that lets you queue up logic to be cycled over + * + * @param [ctx] The object to use in this logic + * @param [dispatcher] The [CoroutineDispatcher] that dispatches this logic + * @param [priority] The priority given to this logic + * @param [block] The logic to be run + */ + fun queue( + ctx: Any, + dispatcher: CoroutineDispatcher = Dispatchers.Default, + priority: TaskPriority = TaskPriority.STANDARD, + block: suspend T.(CoroutineScope) -> Unit + ) + + /** + * Submits a return value for the first [T] in [queue] + * + * @param [value] The value to submit to the first [T] in [queue] + */ + fun submitReturnValue(value: Any) { + val task = queue.peek() ?: return + task.requestReturnValue = value + } + + /** + * Terminates all [T] in [queue] + */ + fun terminateTasks() { + queue.forEach(CyclableTask::terminate) + queue.clear() + } +} From ec34aa9d36fbe7429216344ba430e7f9b39fee26 Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 19:03:07 -0700 Subject: [PATCH 6/9] Added re-implementations of the old `SuspendableCondition`s to `game.model.coroutine.suspendable.condition.impl` --- .../condition/impl/AsynchronousCondition.kt | 24 +++++++ .../condition/impl/PredicateCondition.kt | 17 +++++ .../condition/impl/SynchronousCondition.kt | 22 +++++++ .../condition/impl/TileCondition.kt | 23 +++++++ .../queue/coroutine/SuspendableCondition.kt | 62 ------------------- 5 files changed, 86 insertions(+), 62 deletions(-) create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/AsynchronousCondition.kt create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/PredicateCondition.kt create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/SynchronousCondition.kt create mode 100644 game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/TileCondition.kt delete mode 100644 game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableCondition.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/AsynchronousCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/AsynchronousCondition.kt new file mode 100644 index 0000000000..21d1964028 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/AsynchronousCondition.kt @@ -0,0 +1,24 @@ +package gg.rsmod.game.model.coroutine.suspendable.condition.impl + +import gg.rsmod.game.model.coroutine.suspendable.condition.SuspendableCondition +import java.util.concurrent.atomic.AtomicInteger + +/** + * A [SuspendableCondition] that waits until the given [cycleCounter] is equal to [myCycle] before + * permitting the coroutine to continue it's logic. + * + * @param [myCycle] + * The cycle on which to permit the coroutine to continue it's logic + * + * @param [cycleCounter] + * The counter that decides whether or not the coroutine gets to continue + * + * @author Curtis Woodard + */ + +class AsynchronousCondition(private val myCycle: Int, private val cycleCounter: AtomicInteger): SuspendableCondition { + + override fun canResume(): Boolean = myCycle == cycleCounter.get() + + override fun toString(): String = "AsynchronousCondition(myCycle=$myCycle, currentCycle=$cycleCounter)" +} diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/PredicateCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/PredicateCondition.kt new file mode 100644 index 0000000000..fda39faf5c --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/PredicateCondition.kt @@ -0,0 +1,17 @@ +package gg.rsmod.game.model.coroutine.suspendable.condition.impl + +import gg.rsmod.game.model.coroutine.suspendable.condition.SuspendableCondition + +/** + * A [SuspendableCondition] that waits for [predicate] to return true before + * permitting the coroutine to continue its logic. + * + * @param [predicate] + * The function used to determine the result of [canResume] + * + * @author Curtis Woodard + */ +class PredicateCondition(private inline val predicate: () -> Boolean) : SuspendableCondition { + + override fun canResume(): Boolean = predicate.invoke() +} diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/SynchronousCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/SynchronousCondition.kt new file mode 100644 index 0000000000..491e83ecf7 --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/SynchronousCondition.kt @@ -0,0 +1,22 @@ +package gg.rsmod.game.model.coroutine.suspendable.condition.impl + +import gg.rsmod.game.model.coroutine.suspendable.condition.SuspendableCondition +import java.util.concurrent.atomic.AtomicInteger + +/** + * A [SuspendableCondition] that waits for the given amount of cycles before permitting the + * coroutine to continue its logic. + * + * @param [cycles] + * The amount of game cycles that must pass before the coroutine can continue. + * + * @author Curtis Woodard + */ +class SynchronousCondition(cycles: Int) : SuspendableCondition { + + private val cyclesLeft = AtomicInteger(cycles) + + override fun canResume(): Boolean = cyclesLeft.decrementAndGet() <= 0 + + override fun toString(): String = "SynchronousCondition($cyclesLeft)" +} \ No newline at end of file diff --git a/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/TileCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/TileCondition.kt new file mode 100644 index 0000000000..86bc22281e --- /dev/null +++ b/game/src/main/kotlin/gg/rsmod/game/model/coroutine/suspendable/condition/impl/TileCondition.kt @@ -0,0 +1,23 @@ +package gg.rsmod.game.model.coroutine.suspendable.condition.impl + +import gg.rsmod.game.model.Tile +import gg.rsmod.game.model.coroutine.suspendable.condition.SuspendableCondition +import gg.rsmod.game.model.entity.Pawn + +/** + * A [SuspendableCondition] that waits until [srcPawn]'s coordinates match [destTile]'s coordinates exactly before + * returing `true` with [canResume] + * + * @param [srcPawn] The [Pawn] whose [tile] must match [destTile] + * + * @param [destTile] The tile that is being compared to [srcPawn] + */ +class TileCondition( + private val srcPawn: Pawn, + private val destTile: Tile +): SuspendableCondition { + + override fun canResume(): Boolean = srcPawn.tile.sameAs(destTile) + + override fun toString(): String = "TileCondition(srcPawn=$srcPawn, destTile=$destTile)" +} \ No newline at end of file diff --git a/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableCondition.kt b/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableCondition.kt deleted file mode 100644 index 9c11b1af74..0000000000 --- a/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableCondition.kt +++ /dev/null @@ -1,62 +0,0 @@ -package gg.rsmod.game.model.queue.coroutine - -import com.google.common.base.MoreObjects -import gg.rsmod.game.model.Tile -import java.util.concurrent.atomic.AtomicInteger - -/** - * A condition that must be met for a suspended coroutine to continue. - * - * @author Tom - */ -abstract class SuspendableCondition { - /** - * Whether or not the coroutine can continue its logic. - */ - abstract fun resume(): Boolean -} - -/** - * A [SuspendableCondition] that waits for the given amount of cycles before - * permitting the coroutine to continue its logic. - * - * @param cycles - * The amount of game cycles that must pass before the coroutine can continue. - */ -class WaitCondition(cycles: Int) : SuspendableCondition() { - - private val cyclesLeft = AtomicInteger(cycles) - - override fun resume(): Boolean = cyclesLeft.decrementAndGet() <= 0 - - override fun toString(): String = MoreObjects.toStringHelper(this).add("cycles", cyclesLeft).toString() -} - -/** - * A [SuspendableCondition] that waits for [src] to possess the exact same - * coordinates as [dst] before permitting the coroutine to continue its logic. - * - * Note that the [src] and [dst] can't be the same coordinates if their height - * does not match as well as their x and z coordinates. - * - * @param src - * The tile that must reach [dst] before the condition returns true. - * - * @param dst - * The tile that must be reached by [dst]. - */ -class TileCondition(private val src: Tile, private val dst: Tile) : SuspendableCondition() { - - override fun resume(): Boolean = src.sameAs(dst) - - override fun toString(): String = MoreObjects.toStringHelper(this).add("src", src).add("dst", dst).toString() -} - -/** - * A [SuspendableCondition] that waits for [predicate] to return true before - * permitting the coroutine to continue its logic. - */ -class PredicateCondition(private val predicate: () -> Boolean) : SuspendableCondition() { - - override fun resume(): Boolean = predicate.invoke() -} \ No newline at end of file From 62485a2b6202a14456a2a877cde5e49aab162c18 Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 19:08:41 -0700 Subject: [PATCH 7/9] Old `SuspendableStep` removed --- .../game/model/queue/coroutine/SuspendableStep.kt | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableStep.kt diff --git a/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableStep.kt b/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableStep.kt deleted file mode 100644 index 178ee57816..0000000000 --- a/game/src/main/kotlin/gg/rsmod/game/model/queue/coroutine/SuspendableStep.kt +++ /dev/null @@ -1,10 +0,0 @@ -package gg.rsmod.game.model.queue.coroutine - -import kotlin.coroutines.Continuation - -/** - * A step in suspendable logic that can be used to step through plugin logic. - * - * @author Tom - */ -data class SuspendableStep(val condition: SuspendableCondition, val continuation: Continuation) \ No newline at end of file From 661b76125093b17aef5526e8321223114a1e2bd8 Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 19:16:57 -0700 Subject: [PATCH 8/9] Re-implemented `QueueTask` as a `CyclableTask` --- .../gg/rsmod/game/model/queue/QueueTask.kt | 113 ++++++------------ 1 file changed, 35 insertions(+), 78 deletions(-) diff --git a/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTask.kt b/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTask.kt index 09dae77c8b..a4bc0f6a19 100644 --- a/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTask.kt +++ b/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTask.kt @@ -1,124 +1,81 @@ package gg.rsmod.game.model.queue import gg.rsmod.game.model.Tile +import gg.rsmod.game.model.coroutine.cyclable.task.CyclableTask +import gg.rsmod.game.model.coroutine.suspendable.step.SuspendableStep +import gg.rsmod.game.model.coroutine.suspendable.step.impl.SuspendableStepImpl +import gg.rsmod.game.model.coroutine.suspendable.condition.impl.* import gg.rsmod.game.model.entity.Pawn import gg.rsmod.game.model.entity.Player -import gg.rsmod.game.model.queue.coroutine.* import mu.KLogging +import java.util.concurrent.atomic.AtomicInteger import kotlin.coroutines.* /** - * Represents a task that can be paused, or suspended, and resumed at any point - * in the future. + * Re-implementation of the old [QueueTask] to implement the new [CyclableTask] * - * @author Tom + * @author Curtis Woodard */ -data class QueueTask(val ctx: Any, val priority: TaskPriority) : Continuation { - - lateinit var coroutine: Continuation - - /** - * If the task's logic has already been invoked. - */ - var invoked = false - - /** - * A value that can be requested by a task, such as an input for dialogs. - */ - var requestReturnValue: Any? = null - - /** - * Represents an action that should be executed if, and only if, this task - * was terminated via [terminate]. - */ - var terminateAction: ((QueueTask).() -> Unit)? = null - - /** - * The next [SuspendableStep], if any, that must be handled once a [SuspendableCondition] - * returns [SuspendableCondition.resume] as true. - */ - private var nextStep: SuspendableStep? = null - - /** - * The [CoroutineContext] implementation for our task. - */ +data class QueueTask(override val ctx: Any, override val priority: TaskPriority) : CyclableTask { + override var onResultException: (Throwable) -> Unit = { } + override val currentCycle: AtomicInteger = AtomicInteger(0) + override val lastCycle: AtomicInteger = AtomicInteger(0) + override lateinit var coroutine: Continuation + override var invoked = false + override var requestReturnValue: Any? = null + override var terminateAction: (QueueTask).() -> Unit = { } + override var nextStep: SuspendableStep? = null override val context: CoroutineContext = EmptyCoroutineContext - /** - * When the [nextStep] [SuspendableCondition.resume] returns true, this - * method is called. - */ override fun resumeWith(result: Result) { nextStep = null result.exceptionOrNull()?.let { e -> logger.error("Error with plugin!", e) } } - /** - * The logic in each [SuspendableStep] must be game-thread-safe, so we use - * this method to keep them in-sync. - */ - internal fun cycle() { + override fun cycle() { val next = nextStep ?: return - if (next.condition.resume()) { + if (next.predicate()) { next.continuation.resume(Unit) requestReturnValue = null } } - /** - * Terminate any further execution of this task, during any state, - * and invoke [terminateAction] if applicable (not null). - */ - fun terminate() { + override fun terminate() { nextStep = null requestReturnValue = null - terminateAction?.invoke(this) + terminateAction(this) } - /** - * If the task has been "paused" (aka suspended). - */ - fun suspended(): Boolean = nextStep != null + override fun suspended(): Boolean = nextStep != null + + suspend fun onCycle(cycle: Int): Unit = suspendCoroutine { + check(cycle > 0) { "Execution cycle must be greater than 0." } + check(cycle > lastCycle.get()) { "Execution cycle must be after the previous cycle: ${lastCycle.get()}" } + lastCycle.set(cycle) + nextStep = SuspendableStepImpl(AsynchronousCondition(cycle, currentCycle), it) + } - /** - * Wait for the specified amount of game cycles [cycles] before - * continuing the logic associated with this task. - */ suspend fun wait(cycles: Int): Unit = suspendCoroutine { check(cycles > 0) { "Wait cycles must be greater than 0." } - nextStep = SuspendableStep(WaitCondition(cycles), it) + lastCycle.set(currentCycle.get() + cycles) + nextStep = SuspendableStepImpl(SynchronousCondition(cycles), it) } - /** - * Wait for [predicate] to return true. - */ suspend fun wait(predicate: () -> Boolean): Unit = suspendCoroutine { - nextStep = SuspendableStep(PredicateCondition { predicate() }, it) + nextStep = SuspendableStepImpl(PredicateCondition { predicate() }, it) } - /** - * Wait for our [ctx] to reach [tile]. Note that [ctx] MUST be an instance - * of [Pawn] and that the height of the [tile] and [Pawn.tile] must be equal, - * as well as the x and z coordinates. - */ - suspend fun waitTile(tile: Tile): Unit = suspendCoroutine { - nextStep = SuspendableStep(TileCondition((ctx as Pawn).tile, tile), it) + suspend fun waitTile(tile: Tile, pawn: Pawn = ctx as Pawn): Unit = suspendCoroutine { + nextStep = SuspendableStepImpl(TileCondition(pawn, tile), it) } - /** - * Wait for our [ctx] as [Player] to close the [interfaceId]. - */ suspend fun waitInterfaceClose(interfaceId: Int): Unit = suspendCoroutine { - nextStep = SuspendableStep(PredicateCondition { !(ctx as Player).interfaces.isVisible(interfaceId) }, it) + nextStep = SuspendableStepImpl(PredicateCondition { !(ctx as Player).interfaces.isVisible(interfaceId) }, it) } - /** - * Wait for any return value to be available before - * continuing. - */ suspend fun waitReturnValue(): Unit = suspendCoroutine { - nextStep = SuspendableStep(PredicateCondition { requestReturnValue != null }, it) + nextStep = SuspendableStepImpl(PredicateCondition { requestReturnValue != null }, it) } override fun equals(other: Any?): Boolean { From a0881d6a3701eceff262cf8c2a220eb0f5b713cd Mon Sep 17 00:00:00 2001 From: Curtis Woodard Date: Tue, 12 Nov 2019 19:22:31 -0700 Subject: [PATCH 9/9] Re-implemented `QueueTaskSet` as a `CyclableTaskSet` --- .../gg/rsmod/game/model/queue/QueueTaskSet.kt | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTaskSet.kt b/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTaskSet.kt index 1b899c1eaf..bc84b52a2c 100644 --- a/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTaskSet.kt +++ b/game/src/main/kotlin/gg/rsmod/game/model/queue/QueueTaskSet.kt @@ -1,24 +1,26 @@ package gg.rsmod.game.model.queue +import gg.rsmod.game.model.coroutine.cyclable.set.CyclableTaskSet import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import java.util.* import kotlin.coroutines.createCoroutine /** - * A system responsible for task coroutine logic. + * A Re-implementation of [QueueTaskSet] as a [CyclableTaskSet]<[QueueTask]> to preserve functionality and backwards compatibility. * - * @author Tom + * @author Curtis Woodard */ -abstract class QueueTaskSet { +abstract class QueueTaskSet: CyclableTaskSet { - protected val queue: LinkedList = LinkedList() + override val queue: LinkedList = LinkedList() - val size: Int get() = queue.size - - abstract fun cycle() - - fun queue(ctx: Any, dispatcher: CoroutineDispatcher, priority: TaskPriority, block: suspend QueueTask.(CoroutineScope) -> Unit) { + override fun queue( + ctx: Any, + dispatcher: CoroutineDispatcher, + priority: TaskPriority, + block: suspend QueueTask.(CoroutineScope) -> Unit + ) { val task = QueueTask(ctx, priority) val suspendBlock = suspend { block(task, CoroutineScope(dispatcher)) } @@ -30,25 +32,4 @@ abstract class QueueTaskSet { queue.addFirst(task) } - - /** - * In-game events sometimes must return a value to a plugin. An example are - * dialogs which must return values such as input, button click, etc. - * - * @param value - * The return value that the plugin has asked for. - */ - fun submitReturnValue(value: Any) { - val task = queue.peek() ?: return // Shouldn't call this method without a queued task. - task.requestReturnValue = value - } - - /** - * Remove all [QueueTask] from our [queue], invoking each task's [QueueTask.terminate] - * before-hand. - */ - fun terminateTasks() { - queue.forEach { it.terminate() } - queue.clear() - } }