- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
Actors and Swing
Sun, 2009-07-26, 15:02
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
Tue, 2009-07-28, 16:57
#2
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()
}
}
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