day 5 

Yesterday we put in an Akka actor to manage concurrent access to the game state. Let’s look at the abstract UI again:

package com.eed3si9n.tetrix

class AbstractUI {
  // skipping imports...
  implicit val timeout = Timeout(1 second)
  import ExecutionContext.Implicits.global

  private[this] val initialState = Stage.newState(Block((0, 0), TKind) :: Nil,
    randomStream(new scala.util.Random))
  private[this] val system = ActorSystem("TetrixSystem")
  private[this] val playerActor = system.actorOf(Props(new StageActor(
    initialState)), name = "playerActor")
  private[this] val timer = system.scheduler.schedule(
    0 millisecond, 1000 millisecond, playerActor, Tick)
  private[this] def randomStream(random: scala.util.Random): Stream[PieceKind] =
    PieceKind(random.nextInt % 7) #:: randomStream(random)

  def left()  { playerActor ! MoveLeft }
  def right() { playerActor ! MoveRight }
  def up()    { playerActor ! RotateCW }
  def down()  { playerActor ! Tick }
  def space() { playerActor ! Drop }
  def view: GameView =
    Await.result((playerActor ? View).mapTo[GameView], timeout.duration)
}

too much locking 

Looking back, the above implementation does not look good. I’ve turned the program into embarrassingly serial one. In the previous implementation using synchronized the swing UI was able to query for a view 10 times a second. It continues to do so with this implementation, but because it’s now in the same mailbox as other messages, it could flood the mailbox if any operation takes longer than 100 milisecond.

Ideally, the game state should not be locked until the very moment the new state is being written to it. Because the user operation and the scheduled ticking is never compatible, processing one of them at a time I think is ok for now.

Let’s design the second actor by defining the message types:

sealed trait StateMessage
case object GetState extends StateMessage
case case SetState(s: GameState) extends StateMessage
case object GetView extends StateMessage

The actor implementation should be straight forward:

class StateActor(s0: GameState) extends Actor {
  private[this] var state: GameState = s0
  
  def receive = {
    case GetState    => sender ! state
    case SetState(s) => state = s
    case GetView     => sender ! state.view
  }
}

Next we need to rewrite the StageActor based on StateActor.

class StageActor(stateActor: ActorRef) extends Actor {
  import Stage._

  def receive = {
    case MoveLeft  => updateState {moveLeft}
    case MoveRight => updateState {moveRight}
    case RotateCW  => updateState {rotateCW}
    case Tick      => updateState {tick}
    case Drop      => updateState {drop}
  }

  private[this] def updateState(trans: GameState => GameState) {
    val future = (stateActor ? GetState)(1 second).mapTo[GameState]
    val s1 = Await.result(future, 1 second)
    val s2 = trans(s1)
    stateActor ! SetState(s2)
  }
}

We need to update the abstract UI slightly to create stateActor:

package com.eed3si9n.tetrix

class AbstractUI {
  // skipping imports...
  implicit val timeout = Timeout(100 millisecond)
  
  private[this] val initialState = Stage.newState(Block((0, 0), TKind) :: Nil,
    (10, 23), randomStream(new scala.util.Random))
  private[this] val system = ActorSystem("TetrixSystem")
  private[this] val stateActor = system.actorOf(Props(new StateActor(
    initialState)), name = "stateActor")
  private[this] val playerActor = system.actorOf(Props(new StageActor(
    stateActor)), name = "playerActor")
  private[this] val timer = system.scheduler.schedule(
    0 millisecond, 700 millisecond, playerActor, Tick)
  private[this] def randomStream(random: scala.util.Random): Stream[PieceKind] =
    PieceKind(random.nextInt % 7) #:: randomStream(random)

  def left()  { playerActor ! MoveLeft }
  def right() { playerActor ! MoveRight }
  def up()    { playerActor ! RotateCW }
  def down()  { playerActor ! Tick }
  def space() { playerActor ! Drop }
  def view: GameView =
    Await.result((stateActor ? GetView).mapTo[GameView], timeout.duration)
}

The concurrency of timer ticking and the player moving the current piece left continues to be protected using playerActor. However, now the swing UI can access the view frequently without waiting on the others.