Kotan Code 枯淡コード

In search of simple, elegant code

Menu Close

Adding State to Actors with the Pimp My Class Pattern

In my last blog post, I talked about how the new Akka 2.0 actor system hides an enforces the inaccessibility of the actual class of the underlying actor. For example, if you have a class called Zombie that is of type Actor, then when you use the actor system to start that actor, what you get back is an ActorRef, and (without cheating), there is no way to typecast or otherwise gain access to the Zombie type from an instance of ActorRef. While it felt limiting, once I embraced the design principle that necessitated that enforcement, I understood.

That said, I am building a MUD here and I’m going to need state. My rooms need names, descriptions, and exits. My NPCs are going to need combat stats and inventories, my players are going to need names, descriptions, races, genders, etc. This is all basic stuff but – if I can’t get to a game object reference from an ActorRef, how am I going to get at their internal state?

Easy! Don’t use internal state.

Remembering back to my first post where I upgraded my old Scala MUD to use Akka actors, I noted that every actor in a system has a unique path string that denotes its position within a hierarchy of supervisory actors much like the way supervisors and processes exist in an Erlang system. That gave me the idea to store the state for my actors in a big in-memory hash (which I could then upgrade to some kind of distributed or CouchDB-type store later).

With that problem solved, the only real problem now was that my ActorRef instances didn’t have concrete properties. One of my favorite features of Scala, which is also used in building DSLs, is the pimp my class or pimp my library pattern. This works very much like class extensions in C#. I can create a class that encapsulates or enriches an underlying class type by providing methods that operate on or with that class. In my case, I wanted strongly typed accessors like name and description that actually deferred the backing store to this singleton, in-memory hash.

Here’s my scala file that contains an Implicits object that enriches the ActorRef type with the wrapper class and the wrapper class provides a getter and setter for name.

package com.kotancode.scalamud.core

import akka.actor.ActorRef
import scala.collection.mutable.{Map,
    SynchronizedMap, HashMap}

object Implicits {
	implicit def enrichActorRef(value: ActorRef) = new WrappedActorRef(value)
}

/*
 * Singleton containing a hash map keyed on the Actor Ref path
 * which contains additional hash maps.
 * There is one hash map for every actorref that needs state
 */
object StateMap {

   // Creates a thread-safe hash map
   def makeMap: Map[String, HashMap[String,String]] = {
       new HashMap[String, HashMap[String,String]] with
           SynchronizedMap[String, HashMap[String,String]]
   }

   private var stateMap = makeMap

   def getState(key:String):HashMap[String,String] = {
   	 if (!stateMap.contains(key))
	     stateMap.put(key, new HashMap[String,String])
	 stateMap(key)
   }

   def setState(key:String, propertyName:String, value:String) = {
		var state: HashMap[String,String] = null
		val result = stateMap.get(key)
		result match {
			case Some(x) => state = x
			case None => state = new HashMap[String,String]
		}
		state.put(propertyName, value)
		stateMap.put(key, state)
   }
}

class WrappedActorRef(val actorRef: ActorRef) {

	def name:String = StateMap.getState(actorRef.path.toString)("name")
	def name_=(value:String) { StateMap.setState(actorRef.path.toString, "name", value) }
}

An obvious refactoring opportunity here would be to move the StateMap class out to its own separate Scala file but I’ve always been a huge fan of the agile process of getting something working first and refactoring afterward.

Now, to use my newly enhanced ActorRef (with state) class all I have to do is add the following import

import com.kotancode.scalamud.core.Implicits._

To the top of my Scala file and below anywhere the type ActorRef is known, I can read and write the name property, as shown here:

	private def handlePlayerLogin(player:ActorRef) = {
		println("Player logged in: " + player)
		allPlayers ::= player
		for (p: ActorRef <- allPlayers) {
			p ! TextMessage(player.name + " logged in.")
	    }

Because ActorRef has been pimped to use the external state provider, I can use player.name in my code and it works!