This page is no longer maintained — Please continue to the home page at www.scala-lang.org

Actors and Swing

2 replies
Patrick Roemer
Joined: 2009-02-18,
User offline. Last seen 42 years 45 weeks ago.

Hi,

for a simple, synchronous static game loop

while(!gameHasEnded) {
players.foreach(p => processMove(p, p.nextMove))
}

I'm trying to model a (human) player that asynchronously gets moves from
a Swing UI. In Java I would go for wait/notify, in Scala it looked like
actors would be the weapon of choice.

Here's what I have come up with at first try:

case object MoveRequest
case object GameEnded

class AsyncHumanPlayer(timeout: Long) extends Player {
private var listener: Actor = null

def nextMove() =
listener !? MoveRequest match {
case move: Move => move
}

override def gameEnded() {
listener ! GameEnded
}
}

object HumanPlayerGUI {
def createGUI(player: AsyncHumanPlayer): JPanel = {
val text = new JTextField(10)
text.setEnabled(false)

val enablingActor = actor {
Actor.loop {
receive {
case MoveRequest => {
val syncSender = sender
SwingUtilities.invokeLater(new Runnable() {
def run() {
text.addActionListener(new ActionListener() {
def actionPerformed(event: ActionEvent) {
val guess = Integer.parseInt(text.getText().trim())
text.setText("")
text.setEnabled(false)
text.removeActionListener(this)
syncSender ! Move(guess)
}
})
text.setEnabled(true)
}
})
}
case GameEnded => {
exit('stop)
}
}
}
}
enablingActor.start
player.listener(enablingActor)
// ...
}
}

This seems to work (apart from the fact that the application doesn't end
due to a remaining non-daemon thread running an FJTaskScheduler). But it
feels pretty fishy. In particular, I thought that "!?" in the player
should require to be served by a "reply" from the GUI, but I haven't
figured a way to do that from within the action listener. Surprisingly,
keeping a reference to the sender and using "!" seems to work, but it
doesn't feel right, and creating a dedicated action listener per
invocation looks pretty clumsy, too.

Any ideas how to improve this? Or is the general approach (using a
synchronous actor message) already doomed? General links on combining
Swing with actors would be appreciated, too. Thanks in advance.

Best regards,
Patrick

Philipp Haller
Joined: 2009-01-13,
User offline. Last seen 42 years 45 weeks ago.
Re: Actors and Swing

Patrick Roemer wrote:
> Hi,
>
> for a simple, synchronous static game loop
>
> while(!gameHasEnded) {
> players.foreach(p => processMove(p, p.nextMove))
> }
>
> I'm trying to model a (human) player that asynchronously gets moves from
> a Swing UI. In Java I would go for wait/notify, in Scala it looked like
> actors would be the weapon of choice.
>
> Here's what I have come up with at first try:
>
> case object MoveRequest
> case object GameEnded
>
> class AsyncHumanPlayer(timeout: Long) extends Player {
> private var listener: Actor = null
>
> def nextMove() =
> listener !? MoveRequest match {
> case move: Move => move
> }
>
> override def gameEnded() {
> listener ! GameEnded
> }
> }
>
> object HumanPlayerGUI {
> def createGUI(player: AsyncHumanPlayer): JPanel = {
> val text = new JTextField(10)
> text.setEnabled(false)
>
> val enablingActor = actor {
> Actor.loop {
> receive {
> case MoveRequest => {
> val syncSender = sender
> SwingUtilities.invokeLater(new Runnable() {
> def run() {
> text.addActionListener(new ActionListener() {
> def actionPerformed(event: ActionEvent) {
> val guess = Integer.parseInt(text.getText().trim())
> text.setText("")
> text.setEnabled(false)
> text.removeActionListener(this)
> syncSender ! Move(guess)
> }
> })
> text.setEnabled(true)
> }
> })
> }
> case GameEnded => {
> exit('stop)
> }
> }
> }
> }
> enablingActor.start
> player.listener(enablingActor)
> // ...
> }
> }
>
> This seems to work (apart from the fact that the application doesn't end
> due to a remaining non-daemon thread running an FJTaskScheduler). But it
> feels pretty fishy. In particular, I thought that "!?" in the player
> should require to be served by a "reply" from the GUI, but I haven't
> figured a way to do that from within the action listener. Surprisingly,
> keeping a reference to the sender and using "!" seems to work, but it
> doesn't feel right, and creating a dedicated action listener per
> invocation looks pretty clumsy, too.

Actually, `sender ! msg` is equivalent to `reply(msg)`. If you wanted to
use `reply` you would have to define `enablingActor` by subclassing
`Actor` and then call its `reply` method.

Also, it looks to me as if you can replace `receive` inside the `loop`
by `react`.

If you could provide a small self-contained example, we could find out
why it doesn't terminate automatically.

Cheers,
Philipp

Patrick Roemer
Joined: 2009-02-18,
User offline. Last seen 42 years 45 weeks ago.
Re: Actors and Swing

Responding to Philipp Haller:

Thanks for your reply!

> Actually, `sender ! msg` is equivalent to `reply(msg)`. If you wanted to
> use `reply` you would have to define `enablingActor` by subclassing
> `Actor` and then call its `reply` method.

I see. So the idiom of keeping making reference to the sender available
for use inside the listener and invoking "!" there is ok?

> Also, it looks to me as if you can replace `receive` inside the `loop`
> by `react`.

Oh, right, I think I had it that way and the 'receive' was just a
leftover from experimenting.

> If you could provide a small self-contained example, we could find out
> why it doesn't terminate automatically.

Sure. I've reduced code and attached it below. Thanks again.

Best regards,
Patrick

---

[Game.scala]
package gameloop

case class Move() {
}

trait Player {
def nextMove(): Move

def gameEnded() {
}
}

class Game(player: Player) {
def gameLoop() {
for (i <- 0 until 3) {
println(player.nextMove)
}
player.gameEnded
}
}

[AsyncHumanPlayer.scala]
package gameloop

import scala.actors._

case object MoveRequest
case object GameEnded

class AsyncHumanPlayer(timeout: Long) extends Player {
private var listener: Actor = null

def listener(listener: Actor) {
this.listener = listener;
}

def nextMove() =
listener !? MoveRequest match {
case m: Move => m
}

override def gameEnded() {
listener ! GameEnded
}
}

object HumanPlayerGUI {
import javax.swing._
import java.awt.event._
import Actor._

def createGUI(player: AsyncHumanPlayer): JPanel = {
val button = new JButton("click")
button.setEnabled(false)
val enablingActor = actor {
loop {
react {
case MoveRequest => {
val syncSender = sender
SwingUtilities.invokeLater(new Runnable() {
def run() {
button.addActionListener(new ActionListener() {
def actionPerformed(event: ActionEvent) {
button.setEnabled(false)
button.removeActionListener(this)
syncSender ! Move()
}
})
button.setEnabled(true)
}
})
}
case GameEnded => {
exit('stop)
}
}
}
}
enablingActor.start
player.listener(enablingActor)
val panel = new JPanel()
panel.add(button)
panel
}
}

[GameMain.scala]
package gameloop

object GameMain {

import javax.swing._

def main(args: Array[String]) {
val humanPlayer = new AsyncHumanPlayer(30000)
val gui = HumanPlayerGUI.createGUI(humanPlayer)
val frame = new JFrame("Game")
frame.getContentPane().add(gui)
frame.pack()
frame.setVisible(true)
val game = new Game(humanPlayer)
game.gameLoop()
frame.dispose()
}
}

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland