object DiningHakkersOnFsm { val system = ActorSystem() def main(args: Array[String]) { val chopsticks = for (i <- 0 to 4) yield system.actorOf( Props[Chopstick], "C" + i) val hakkers = for { (name, i) <- List("P0", "P1", "P2", "P3", "P4"). zipWithIndex } yield system.actorOf( Props(classOf[FSMHakker], name, chopsticks(i), chopsticks((i + 1) % 5))) hakkers.foreach(_ ! Think) } }
chopsticks
array of actors, indexed from 0 to 4, named C0, …,
C4.
zipWithIndex
constructs ("P0", 0), …, ("P4", 4).
hakkers
are sent the message Think
sealed trait ChopstickState case object Available extends ChopstickState case object Taken extends ChopstickState case class TakenBy(hakker: ActorRef)
ChopstickState
s for the chopsticks: Available
,
Taken
TakenBy
is a class with one constructor that requires a hakker
as its argument. The ActorRef
better be a "hakker" actor.
class Chopstick extends Actor with FSM[ChopstickState, TakenBy] { import context._ startWith(Available, TakenBy(system.deadLetters)) when(Available) ... when(Taken) ... initialize }
startWith
defines the initial state and initial data.
system.deadLetters
cf. "nobody"
when(state) ...
per state.
initialize
: now the actor is placed in the initial
state defined in startWith
. Until now, this actor was in
"limbo".
sealed trait ChopstickMessage object Take extends ChopstickMessage object Put extends ChopstickMessage case class Taken(chopstick: ActorRef) extends ChopstickMessage case class Busy(chopstick: ActorRef) extends ChopstickMessage
when(Available) { case Event(Take, _) => goto(Taken) using TakenBy(sender) replying Taken(self) }
Available
.
Take
happens, whatever the second parameter may be,
transition into state Taken
.
TakenBy
the sender actor of this event.
Taken
with itself as the chopstick.
using
and replying
are modifier words.
when(Taken) { case Event(Take, currentState) => stay replying Busy(self) case Event(Put, TakenBy(hakker)) if sender == hakker => goto(Available) using TakenBy(system.deadLetters) }
Take
event happens, the state stay
s the same.
Busy
with itself as the chopstick.
Put
happens, it better be by whoever took this chopstick.
If so, transition into Available
. Who has it now? Nobody (i.e.,
system.deadLetters
)
sealed trait FSMHakkerMessage object Think extends FSMHakkerMessage sealed trait FSMHakkerState case object Thinking extends FSMHakkerState case object Eating extends FSMHakkerState case object Hungry extends FSMHakkerState case object Waiting extends FSMHakkerState case object WaitForOtherChopstick extends FSMHakkerState case object FirstChopstickDenied extends FSMHakkerState case class TakenChopsticks( left: Option[ActorRef], right: Option[ActorRef])
Option[T]
type. It is a container for an
optional value of type T. Cf. None, null, nil, …
class FSMHakker ( name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] { startWith(Waiting, TakenChopsticks(None, None)) when(Waiting) ... when(Thinking) ... when(Hungry) ... when(WaitForOtherChopstick) ... when(FirstChopstickDenied) ... when(Eating) ... initialize }
when(Waiting) { case Event(Think, _) => println( s"$name starts to think" ) startThinking(5.seconds) } private def startThinking(duration: FiniteDuration) = { goto(Thinking) using TakenChopsticks(None, None) forMax duration }
DiningHakkersOnFsm
.
when(Thinking) { case Event(StateTimeout, _) => left ! Take right ! Take goto(Hungry) }
Thinking
state, philosopher does not have any chopsticks.
This state ends (i.e., a transition will happen) in a finite amount
of time.
left
and right
.
Hungry
state.
when(Eating) { case Event(StateTimeout, _) => println(s"$name puts down and starts to think") left ! Put right ! Put startThinking(5.seconds) }
Eating
state, philosopher does have two chopsticks. This state
ends in a finite amount of time.
when(Hungry) { case Event(Taken(`left`), _) => goto(WaitForOtherChopstick) using TakenChopsticks(Some(left), None) case Event(Taken(`right`), _) => goto(WaitForOtherChopstick) using TakenChopsticks(None, Some(right)) case Event(Busy(_), _) => goto(FirstChopstickDenied) }
Some(x)
Some[A] represents existing values of type A
FirstChopstickDenied
state.
when(WaitForOtherChopstick) { case Event(Taken(`left`), TakenChopsticks(None, Some(right))) => startEating(left, right) case Event(Taken(`right`), TakenChopsticks(Some(left), None)) => startEating(left, right) case Event(Busy(chopstick), TakenChopsticks(leftOption, rightOption)) => leftOption.foreach(_ ! Put) rightOption.foreach(_ ! Put) startThinking(10.milliseconds) }
private def startEating( left: ActorRef, right: ActorRef) = { println( s"$name has $left and $right and starts to eat") goto(Eating) using TakenChopsticks( Some(left), Some(right)) forMax (5.seconds) }
when(FirstChopstickDenied) { case Event(Taken(secondChopstick), _) => secondChopstick ! Put startThinking(10.milliseconds) case Event(Busy(chopstick), _) => startThinking(10.milliseconds) }
become
?