Wednesday, September 1, 2010

Creating a simple simulation - Producers

Now that the project has been set up, it's time to start creating the first steps in our simulation.

Every city simulation game I've played begins the same: getting workers to obtain raw materials. So let's  create a text-only simulation that does just that.

This post is about Scala syntax, Scala Actors, Scala messages, and examples of pattern matching.

Simulation Concepts

Each simulation starts out by creating a number of  Providers, objects that can provide a number of raw materials such as wood, iron, corn etc. In some games, these resources are limited; in others they are unlimited. For now, each Provider has an infinite number of raw materials to provide.

Next to the Providers we have the Harvesters. These objects are built close to or on top of Providers and are required to harvest and store the raw materials. Examples of such Harvesters are Woodcutter's Houses, Mines, Farms etc.

And finally of course, there are the Citizens, the small people walking around in your city and doing all the actual work. For starters, Harvesters require a citizen to operate it.

Implementation

Messages

The simulation will be built around Scala Actors, a special construct that allows for autonomous objects that communicate by passing messages. The way Scala has implemented Actors makes it possible to have thousands of Actors running at the same time.

In order for Actors to work, we need to define a number of Messages. For this post, I defined the following messages:

/** Base class for all messages in scalasim. */
abstract class Message
/** Ask a producer to produce goods. */
case class Produce extends Message
/** Inform a Harvester goods have been produced. */
case class Produced(name: String, amount: Int) extends Message
/** Ask an actor to stop running. */
case class Stop extends Message

I have started by creating an abstract case class called Message. For now, this class has no behavior; instead, it functions as a supertype for all messages. The actual messages are straightforward:

  • Produce : used by Harvesters to ask a Producer to produce goods of type 'name'.
  • Produced: used by Producers to inform a Harvester that 'amount' goods of type 'name' have been produced.
  • Stop: used by the simulations to inform agents to stop.

Please note that it is not required to define message classes – you can send anything to an actor, including for example plain strings.

Producers

The first actor I created is the Producer. Have a look at the following code:

class Producer(val name: String, val goods: String) extends Actor {
  def act = {
    loop {
      react {
        case Produce => {
          val amount = 1
          println("[%s] Producing %d %s".format(name, amount, goods))
          reply(Produced(goods, amount))
        }
        case Stop => exit
        case msg => println("[%s] Ignoring %s".format(name, msg))
      }
    }
  }
}

That's all.

But what does it mean? The first line defines a new class called Producer. It's constructor takes two arguments, name and goods, and the val keyword ensures that objects of this class expose these arguments as immutable properties:

object Simulation extends Application {
  val producer = new Producer("Candy repository", "candy")
  println("[Sim] Constructed a producer called %s that produces %s"
         .format(producer.name, producer.goods))
}

Lines 2–14 define the method act which is the default entry point for Scala Actors. This loop contains the construct loop/react/case*. This is standard idiom for non-blocking actors, and uses Scala's pattern matching capabilities on the incoming messages.

The Producer matches three kinds of messages: Produce, Stop and msg, which in this case is a catch-all variable, ensuring that messages do not get lost. Let's have a look at the complete 'Simulation' I've written so far:

object Simulation extends Application {
  val producer = new Producer("Candy repository", "candy")
  println("[Sim] Constructed a producer called %s that produces %s"
         .format(producer.name, producer.goods))

  producer.start()

  producer ! "hello world" 

  producer !? Produce match {
    case Produced(goods, amount) =>
      println("[Sim] Harvested %d of type %s".format(amount, goods))
  }

  producer ! Stop
}

Lines 1–4 are the same as before. On line 6 I start the producer by calling start. This is important, as actors will not respond to message unless they have been started.

The next line uses ! to asynchronously send the string "hello world" to the producer. This in turn is matched by the msg pattern in the producer, causing it to print [Candy repository] Ignoring hello world.

The line after that sends a Produce message to the producer, synchronously waits for a reply and matches against the reply. Note that using !? results in easy-to-read code that is not at all concurrent. But more on this in a later post.

The last line sends a Stop message to the producer. Omitting this message will cause the producer to keep on running in the background, preventing the program from exiting. I've learned this the hard way.

And with just these 15 lines I've written a full-fledged Scala actor.

Lessons learned

The first steps are always the most impressive, even if I didn't accomplish much. Some highlights:

  • Syntax - yes, a boring point, but learning a new language first requires mastering the syntax. I still have a way to go;
  • Basic actors, messages, the act/loop/react pattern - based on the article Scala Actors: A Short Tutorial I was able to quickly grasp these concepts;
  • Case classes and pattern matching - having some experience with functional programming, I am thrilled with the pattern matching support in Scala. The associated syntax, case classes and case objects seem to be very elegant.

Of course, when learning a new language you make a lot of errors. Some lowlights:

  • Syntax errors / type errors - these errors are all reported by the compiler, but having no experience with the compiler, these messages could sometimes as well have been written in Klingon. I have worked around this by using Google to find related examples, and by using very short code/compile cycles. Being a newbie, I expect these errors to fade away over time;
  • Start semantics - if you do not start your Actor, it does not react to your message;
  • Exit semantics - my first version of the program so far did not contain the Stop message. Not calling exit on all your actors means your program will not finish;
  • More exit semantics - after learning about exit, I also added an exit call to the end of my main loop. This will result in the following error:
    Exception in thread "main" java.lang.ExceptionInInitializerError
      at nl.scalasim.Simulation.main(Simulation.scala)
    Caused by: java.lang.InterruptedException
      at scala.actors.ActorProxy.exit(ActorProxy.scala:36)
      at scala.actors.Actor$.exit(Actor.scala:332)
      at nl.scalasim.Simulation$.<init>(Simulation.scala:68)
      at nl.scalasim.Simulation$.<clinit>(Simulation.scala)
    The solution is simple: do not add an exit statement to your main loop.

Coming up next:

  • Creating unit tests
  • Adding Harvesters
  • Adding Citizens

Get the code

The full code for this post can be found at code.google.com.

No comments:

Post a Comment