Here’s a 30-second “ping-pong” demo using Akka Actors:
The source code
If you want the source code, you can get it from the GitHub link shown at the end of this post. First, here’s a quick description of it.
The code is in two files, PingPong.scala and PingPongPanel.scala.
PingPong.scala contains three actors:
Ping
Pong
MainFrameActor
It also kicks the action off in the PingPongTest
object, which extends the App
trait. Here’s the source code for PingPong.scala:
package com.alvinalexander.akkademos import akka.actor._ import java.awt._ import javax.swing._ import scala.util.Random /** * Case objects used by the actors. */ case object PingMessage case object PongMessage case object StartMessage case object StopMessage case object DisplayMainFrame case object DoPing case object DoPong /** * The Ping actor. Notice that its constructor takes an ActorRef. */ class Ping(pong: ActorRef) extends Actor { var count = 0 val MAX_NUM_PINGS = 100 val mainFrameActor = context.actorSelection("../mainFrameActor") def incrementCounter { count += 1 } def receive = { case StartMessage => incrementCounter pong ! PingMessage case PongMessage => incrementCounter if (count > MAX_NUM_PINGS) { sender ! StopMessage println("ping stopped") context.stop(self) } else { sender ! PingMessage mainFrameActor ! DoPing Thread.sleep(125) // blocking! bad! } } } /** * The Pong actor. */ class Pong extends Actor { val mainFrameActor = context.actorSelection("../mainFrameActor") def receive = { case PingMessage => sender ! PongMessage mainFrameActor ! DoPong Thread.sleep(125) // blocking! bad! case StopMessage => println("pong stopped") context.stop(self) } } /** * An Actor to handle all the interactions with the JFrame. */ class MainFrameActor extends Actor { val WIDTH = 600 val HEIGHT = 400 val pingPongPanel = new PingPongPanel val mainFrame = new JFrame { setMinimumSize(new Dimension(WIDTH, HEIGHT)) setPreferredSize(new Dimension(WIDTH, HEIGHT)) } configureMainFrame def receive = { case DisplayMainFrame => showMainFrame case DoPing => doAction("PING") case DoPong => doAction("PONG") case _ => } def doAction (action: String) { SwingUtilities.invokeLater(new Runnable { def run { action match { case "PING" => pingPongPanel.doPing case "PONG" => pingPongPanel.doPong } } }) } def configureMainFrame { mainFrame.setTitle("Akka Ping Pong Demo") mainFrame.setBackground(Color.BLACK) mainFrame.getContentPane.add(pingPongPanel) mainFrame.setLocationRelativeTo(null) } def showMainFrame { SwingUtilities.invokeLater(new Runnable { def run { mainFrame.setVisible(true) } }) } } /** * The "main" part of the application. */ object PingPongTest extends App { // create the actor system val actorSystem = ActorSystem("PingPongSystem") // create the actors val mainFrameActor = actorSystem.actorOf(Props[MainFrameActor], name = "mainFrameActor") val pong = actorSystem.actorOf(Props[Pong], name = "pong") val ping = actorSystem.actorOf(Props(new Ping(pong)), name = "ping") // display the main frame (jframe) mainFrameActor ! DisplayMainFrame Thread.sleep(5*1000) // start the action ping ! StartMessage // shut down the actor system //actorSystem.shutdown }
Notice that there’s some blocking in that actor code. That’s bad, don’t do that! I had to do it to slow down the actors so I could see the circles. Otherwise the code finishes almost instantly. (I can improve the painting algorithm shown below, but the code still finishes almost instantly without the intentional delays.)
Without much discussion at this time, here’s the code from the PingPongPanel.scala file:
package com.alvinalexander.akkademos import javax.swing.JPanel import java.awt.{Color, Graphics} import scala.util.Random class PingPongPanel extends JPanel { val panelWidth = 600 val panelHeight = 400 val ballDiameter = 40 val halfPanelWidth = panelWidth / 2 val halfPanelHeight = panelHeight / 2 var currentX = 0 var currentY = 0 var color = Color.GREEN private val r = new Random def doPing { var x1 = r.nextInt(halfPanelWidth) if (x1 < ballDiameter) x1 = ballDiameter if (x1 > (halfPanelWidth-ballDiameter)) x1 = halfPanelWidth-ballDiameter currentX = x1 currentY = getRandomY color = Color.GREEN repaint() } def doPong { var x1 = r.nextInt(halfPanelWidth) + halfPanelWidth if (x1 > (panelWidth-ballDiameter)) x1 = panelWidth-ballDiameter currentX = x1 currentY = getRandomY color = Color.YELLOW repaint() } def getRandomY = { var y = r.nextInt(panelHeight) if (y < ballDiameter) y = ballDiameter / 2 if (y > (panelHeight-ballDiameter)) y = panelHeight-2*ballDiameter y } override def paintComponent(g: Graphics) { g.clearRect(0, 0, panelWidth, panelHeight) // draw mid-court line g.setColor(Color.WHITE) g.drawLine(halfPanelWidth, 0, halfPanelWidth, panelHeight) // draw circle (ball) g.setColor(color) g.fillOval(currentX, currentY, ballDiameter, ballDiameter) } }
More information
Sorry for the rush, I’m a little short on time today, but you can clone the source code from this GitHub project:
I’ll add more discussion here over time, but right now I’m really running late for something else.