-
-
Save garyttierney/ffe55d2e35d41a510f52222bb7ac03ce to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.apollo.game.action | |
import java.util.* | |
import kotlin.coroutines.experimental.Continuation | |
import kotlin.coroutines.experimental.CoroutineContext | |
import kotlin.coroutines.experimental.EmptyCoroutineContext | |
import kotlin.coroutines.experimental.RestrictsSuspension | |
import kotlin.coroutines.experimental.intrinsics.COROUTINE_SUSPENDED | |
import kotlin.coroutines.experimental.intrinsics.createCoroutineUnchecked | |
import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn | |
typealias ActionPredicate = () -> Boolean | |
typealias ActionBlock = suspend ActionCoroutine.() -> Unit | |
interface ActionCoroutineCondition { | |
/** | |
* Called once every tick to check if `Continuation` associated with this condition should be resumed. | |
*/ | |
fun resume(): Boolean | |
} | |
/** | |
* A coroutine condition that waits on a given number of pulses. | |
*/ | |
class AwaitPulses(var pulses: Int) : ActionCoroutineCondition { | |
override fun resume(): Boolean { | |
return --pulses <= 0 | |
} | |
} | |
/** | |
* A coroutine condition that waits until a predicate is fufilled. | |
*/ | |
class AwaitPredicate(val predicate: ActionPredicate) : ActionCoroutineCondition { | |
override fun resume(): Boolean { | |
return predicate.invoke() | |
} | |
} | |
/** | |
* A suspend point in an `ActionCoroutine` that has its `coroutine` resumed whenever the `condition` evaluates | |
* to `true`. | |
*/ | |
data class ActionCoroutineStep(val condition: ActionCoroutineCondition, internal val coroutine: Continuation<Unit>) | |
@RestrictsSuspension | |
class ActionCoroutine : Continuation<Unit> { | |
companion object { | |
/** | |
* Create a new `ActionCoroutine` and immediately execute the given `block`, returning a coroutine that | |
* can be resumed. | |
*/ | |
fun start(block: ActionBlock) : ActionCoroutine { | |
val coroutine = ActionCoroutine() | |
block.createCoroutineUnchecked(coroutine, coroutine).resume(Unit) | |
return coroutine | |
} | |
} | |
override val context: CoroutineContext = EmptyCoroutineContext | |
override fun resume(value: Unit) {} | |
override fun resumeWithException(exception: Throwable) = throw exception | |
/** | |
* The next `step` in this `ActionCoroutine` saved as a resume point. | |
*/ | |
private var next: ActionCoroutineStep? = null | |
/** | |
* Check if this coroutine has no more steps to execute. | |
*/ | |
fun stopped(): Boolean { | |
return next == null | |
} | |
/** | |
* Update this coroutine and check if the condition for the next step to be resumed is satisfied. | |
*/ | |
fun pulse() { | |
val nextStep = next | |
if (nextStep == null) { | |
return | |
} | |
val condition = nextStep.condition | |
val coroutine = nextStep.coroutine | |
if (condition.resume()) { | |
next = null | |
coroutine.resume(Unit) | |
} | |
} | |
private suspend fun awaitCondition(condition: ActionCoroutineCondition) { | |
return suspendCoroutineOrReturn { cont -> | |
next = ActionCoroutineStep(condition, cont) | |
COROUTINE_SUSPENDED | |
} | |
} | |
/** | |
* Wait `pulses` game updates before resuming this coroutine. | |
*/ | |
suspend fun wait(pulses: Int) = awaitCondition(AwaitPulses(pulses)) | |
/** | |
* Wait until the `predicate` returns `true` before resuming this coroutine. | |
*/ | |
suspend fun wait(predicate: ActionPredicate) = awaitCondition(AwaitPredicate(predicate)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.Assert.* | |
import org.junit.Test | |
class ActionCoroutineTest { | |
@Test | |
fun `Coroutine execution resumes after a pulse() call`() { | |
var num = 0 | |
val coroutine = ActionCoroutine.start { | |
wait(1) | |
num = 1 | |
} | |
coroutine.pulse() | |
// Check that the data was modified and the continuation finished. | |
assertEquals(1, num) | |
assertTrue(coroutine.stopped()) | |
} | |
@Test | |
fun `Coroutine suspends on wait() calls`() { | |
var data = 0 | |
val coroutine = ActionCoroutine.start { | |
wait(1) | |
data = 1 | |
} | |
// Check that the data was unmodified and the continuation is unfinished. | |
assertEquals(0, data) | |
assertFalse(coroutine.stopped()) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment