Skip to content

Commit

Permalink
Remove UglyHack, replace with Deferred
Browse files Browse the repository at this point in the history
  • Loading branch information
Zomis committed Jun 28, 2022
1 parent 57b2b50 commit 4dddedc
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.zomis.games.dsl.impl

import kotlinx.coroutines.CompletableDeferred
import net.zomis.games.PlayerEliminationsWrite
import net.zomis.games.common.PlayerIndex
import net.zomis.games.dsl.*
Expand Down Expand Up @@ -127,18 +128,18 @@ class StateKeeper {
val moveState = this.lastMoveState()
val preSetup = FlowStep.PreSetup(game, moveState.toMutableMap())
function.invoke(preSetup)
function.invoke(FlowStep.UglyHack)
preSetup.deferred.await()
this.setState(preSetup.state)
this.replayMode = (preSetup.state != moveState)
}

suspend fun preMove(action: Actionable<*, *>, function: suspend (FlowStep) -> Unit) {
val moveState = this.lastMoveState()
val preSetup = FlowStep.PreMove(action, moveState.toMutableMap())
function.invoke(preSetup)
function.invoke(FlowStep.UglyHack)
this.setState(preSetup.state)
this.replayMode = (preSetup.state != moveState)
val preMove = FlowStep.PreMove(action, moveState.toMutableMap())
function.invoke(preMove)
preMove.deferred.await()
this.setState(preMove.state)
this.replayMode = (preMove.state != moveState)
}
}
class ReplayState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class GameSetupImpl<T : Any>(gameSpec: GameSpec<T>) {
listener.handle(coroutineScope, flowStep)
println("Listener $listener finished $flowStep")
}
if (flowStep is FlowStep.Completable) {
flowStep.complete()
}
if (flowStep is FlowStep.GameEnd) {
println("Game end, no more listeners")
break
Expand All @@ -81,9 +84,19 @@ annotation class GameMarker
sealed class FlowStep {
interface ProceedStep
interface ActionResult
interface Completable {
fun complete()
}
object GameEnd: FlowStep(), ProceedStep
data class Elimination(val elimination: PlayerElimination): FlowStep()
data class PreMove(val action: Actionable<*, *>, val state: MutableMap<String, Any>): FlowStep() {
data class PreMove(
val action: Actionable<*, *>,
val state: MutableMap<String, Any>
): FlowStep(), Completable {
private val completable = CompletableDeferred<Map<String, Any>>()
val deferred: Deferred<Map<String, Any>> = completable
override fun complete() { completable.complete(state) }

fun setState(newState: Map<String, Any>) {
state.clear()
state.putAll(newState)
Expand All @@ -105,11 +118,19 @@ sealed class FlowStep {
data class IllegalAction(val actionType: String, val playerIndex: Int, val parameter: Any): FlowStep(), ActionResult
data class Log(val log: ActionLogEntry): FlowStep()
data class RuleExecution(val ruleName: String, val values: Any): FlowStep()
data class PreSetup<T: Any>(val game: Game<T>, val state: MutableMap<String, Any>) : FlowStep()
// Use Deferred for PreSetup and PreMove, see https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html#actors
data class PreSetup<T: Any>(
val game: Game<T>,
val state: MutableMap<String, Any>
): FlowStep(), Completable {
private val completable = CompletableDeferred<Map<String, Any>>()
val deferred: Deferred<Map<String, Any>> = completable
override fun complete() { completable.complete(state) }
}
data class GameSetup<T: Any>(val game: Game<T>, val config: GameConfigs, val state: Map<String, Any>): FlowStep()
// class AwaitInput<T: Any>(val game: Game<T>, var deferred: Deferred<Actionable<T, out Any>?>? = null): FlowStep(), ProceedStep
object AwaitInput: FlowStep(), ProceedStep
object NextView : FlowStep(), ProceedStep
object UglyHack: FlowStep() // Only to make sure that consumers have finished consuming previous message
}

interface Game<T: Any> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.zomis.games.listeners

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.sync.Mutex
import net.zomis.games.dsl.GameListener
import net.zomis.games.dsl.impl.FlowStep

class SteppingGameListener: GameListener {

private val internalChannel = Channel<FlowStep>()

override suspend fun handle(coroutineScope: CoroutineScope, step: FlowStep) {
internalChannel.send(step)
}

suspend fun takeUntil(function: (FlowStep) -> Boolean): FlowStep {
while (true) {
val output = next()
if (function.invoke(output)) return output
}
}

suspend fun next(): FlowStep = internalChannel.receive()

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ class DslGameSystem<T : Any>(val dsl: GameSpec<T>, private val dbIntegration: ()
is FlowStep.RuleExecution -> logger.debug { "Rule Execution: $feedback" }
is FlowStep.IllegalAction -> logger.error { "Illegal action in feedback: $feedback" }
is FlowStep.NextView -> logger.debug { "NextView: $feedback" } // TODO: Not implemented yet, should pause a bit and then continue
is FlowStep.UglyHack -> {}
is FlowStep.PreMove -> {}
else -> {
logger.warn(IllegalArgumentException("Unsupported feedback: $feedback"))
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,33 +1,31 @@
package net.zomis.games.dsl.flow

import klog.KLoggers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import net.zomis.games.dsl.GamesImpl
import net.zomis.games.dsl.impl.FlowStep
import net.zomis.games.impl.DslUR
import net.zomis.games.ur.RoyalGameOfUr
import net.zomis.games.listeners.BlockingGameListener
import net.zomis.games.listeners.SteppingGameListener
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test

class RoyalGameOfUrFlowTest {
val logger = KLoggers.logger(this)

@Test
fun test() {
fun test() = runTest {
val entryPoint = GamesImpl.game(DslUR.gameUR)
val config = entryPoint.setup().configs().also { it.set("piecesPerPlayer", 1) }
val gameFlowImpl = entryPoint.setup().createGame(2, config) as GameFlowImpl<RoyalGameOfUr>
val test = GameFlowTestHelper(gameFlowImpl)
val test = SteppingGameListener()
val gameFlowImpl = entryPoint.setup().startGameWithConfig(this, 2, config) {
listOf(test)
}
val model = gameFlowImpl.model
runBlocking {
val j = launch {
test.takeUntil { it is FlowStep.AwaitInput }
}
gameFlowImpl.start(this)
if (true) {
Assertions.assertEquals(1, model.piecesCopy[0].count { it == 0 })
j.join()
test.takeUntil { it is FlowStep.AwaitInput }

val view = gameFlowImpl.view(0)
logger.info(view)
Expand Down

0 comments on commit 4dddedc

Please sign in to comment.