Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[✅API BACK-COMPAT] Re-write QueueTask and QueueTaskSet as CyclableTask and CyclableTaskSet #178

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
interface CyclableTaskSet<T: CyclableTask> {
/**
* @property [queue]
* Holds all of the [T] that will be cycled over when [cycle] is called
*/
val queue: LinkedList<T>

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()
}
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
interface CyclableTask: Continuation<Unit> {
/**
* @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<Unit>

/**
* @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<Unit>

/**
* @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<Unit>) {
nextStep = null
onResultException(result.exceptionOrNull() ?: return)
}
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
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()
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/

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)"
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
class PredicateCondition(private inline val predicate: () -> Boolean) : SuspendableCondition {

override fun canResume(): Boolean = predicate.invoke()
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
class SynchronousCondition(cycles: Int) : SuspendableCondition {

private val cyclesLeft = AtomicInteger(cycles)

override fun canResume(): Boolean = cyclesLeft.decrementAndGet() <= 0

override fun toString(): String = "SynchronousCondition($cyclesLeft)"
}
Original file line number Diff line number Diff line change
@@ -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)"
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
interface SuspendableStep: Continuation<Unit> {
/** @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<Unit>

/**
* Decides whether or not to resume [continuation]
* @return [Boolean]
*/
fun canResume(): Boolean = predicate()

/** Resumes the next step of [continuation] */
fun resume() = continuation.resume(Unit)
}
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
*/
class SuspendableStepImpl(
override val predicate: SuspendableCondition,
override val continuation: Continuation<Unit>
): SuspendableStep, Continuation<Unit> by continuation
Loading