When I
posted about actors last month I didn't really provide many code examples. That's mainly because I didn't really know a good actor library for the languages that I like to use. So I decided to dig a bit deeper into Scala to use the built-in actor library. It's pretty good!
So before I get started, I'll describe how it works. You have these classes that inherit from
Actor. In these classes you define an act() method. This is similar to the run() method in the Java Thread class.
When an actor receives a message, it goes into the actor's mailbox. You can check the mailbox with the receive() method.
How are we going to represent these messages? Well, a rather convenient approach is to use a feature of Scala: case classes. These are lightweight classes which basically just have a constructor and some public attributes. Kinda like structs from C, but with a constructor and inheritance.
So for my example, I'm coding a basic simulation environment. We want this simulation to be multi-threaded, so that we can take advantage of multiple CPUs in a machine. And we've decided to use Scala actors for it. This will be an agent-based simulation, so we will obviously need an Agent class. We will also have a Simulator class which drives the simulation.
Let's start with two types of messages. For each time step, the Simulator sends an Act message to each Agent. When the agent is done processing, it sends a DoneAct message back to the Simulator saying it is done. Since the number of Agents is fixed, let's just use a counter to keep track of the finished agents.
Here is the Simulator class:
class Simulator extends Actor {
// need to override start to tell all our agents to start as well
override def start() : Actor = {
agents.foreach(agent => agent.start)
return super.start()
}
// something to add agents
def add(agent : Agent) = agents += agent
def act(){
// loop indefinitely
loop {
// check the mailbox
receive {
case Act => {
// this keeps track of how many agents we are waiting for
agents_left = agents.size
// the binary ! operator is what is used to send a message
agents.foreach(agent => agent ! Act)
}
case DoneAct = {
agents_left -= 1
// if we've gotten messages back from all agents, let's start
// the next simulation step
if (agents_left == 0)
this ! Act
}
}
}
}
var agents_left = 0
var agents = new LinkedList[Agent]()
}
Our Agent class:
class Agent(simulator : Simulator) extends Actor {
def act() {
loop {
receive {
case Act => {
// do some actions
simulator ! DoneAct
}
}
}
}
}
This class is a lot simpler, mainly because we haven't actually defined anything to do yet.
For completeness, here are the Event classes:
abstract case class Event()
case class Act() extends Event
case class DoneAct() extends Event
And a main function:
object Sim {
def main(args : Array[String]) {
var simulator = new Simulator()
// add a bunch of agents
simulator.add(new Agent(simulator))
simulator.add(new Agent(simulator))
simulator.add(new Agent(simulator))
simulator.add(new Agent(simulator))
// start the simulator, and send it an Act message
simulator.start
simulator ! Act
}
}
So this simulation is pretty basic. In fact, it's pretty useless. It doesn't actually do anything. However this gives us a framework for creating a simulation. Let's tweak the agent class a little:
abstract class Agent(simulator : Simulator) extends Actor {
def act() {
loop {
receive {
case Act => perform()
}
}
}
def perform() {
// do nothing
}
}
We can now use our Agent class as a base class for other agents. I will end this post now, however next time I will make some subclasses of Agent to actually do something.