+sam #3689 Make activator template of the fsm sample

This commit is contained in:
Patrik Nordwall 2014-02-10 17:23:19 +01:00
parent 23b9fad153
commit e6f679fe8b
12 changed files with 138 additions and 157 deletions

View file

@ -754,7 +754,7 @@ Hakkers`_). It will replace the current behavior (i.e. the top of the behavior
stack), which means that you do not use :meth:`unbecome`, instead always the stack), which means that you do not use :meth:`unbecome`, instead always the
next behavior is explicitly installed. next behavior is explicitly installed.
.. _Dining Hakkers: @github@/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala .. _Dining Hakkers: http://typesafe.com/activator/template/akka-sample-fsm-scala
The other way of using :meth:`become` does not replace but add to the top of The other way of using :meth:`become` does not replace but add to the top of
the behavior stack. In this case care must be taken to ensure that the number the behavior stack. In this case care must be taken to ensure that the number

View file

@ -479,7 +479,6 @@ zero.
Examples Examples
======== ========
A bigger FSM example contrasted with Actor's :meth:`become`/:meth:`unbecome` can be found in the sources: A bigger FSM example contrasted with Actor's :meth:`become`/:meth:`unbecome` can be found in
the `Typesafe Activator <http://typesafe.com/platform/getstarted>`_ template named
* `Dining Hakkers using FSM <@github@/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala>`_ `Akka FSM in Scala <http://typesafe.com/activator/template/akka-sample-fsm-scala>`_
* `Dining Hakkers using become <@github@/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnBecome.scala>`_

View file

@ -0,0 +1,13 @@
Copyright 2013-2014 Typesafe, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1,7 @@
name=akka-sample-fsm-scala
title=Akka FSM in Scala
description=Illustrating how to implement a finite state machine in actors.
tags=akka,scala,sample
authorName=Akka Team
authorLink=http://akka.io/
sourceLink=https://github.com/akka/akka

View file

@ -0,0 +1,10 @@
name := "akka-sample-main-scala"
version := "1.0"
scalaVersion := "2.10.3"
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % "2.3-SNAPSHOT"
)

View file

@ -0,0 +1 @@
sbt.version=0.13.1

View file

@ -1,16 +1,14 @@
/** /**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>. * Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>.
*/ */
package sample.fsm.dining.become package sample.become
import language.postfixOps
//Akka adaptation of
//http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/
import akka.actor._ import akka.actor._
import scala.concurrent.duration._ import scala.concurrent.duration._
// Akka adaptation of
// http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/
/* /*
* First we define our messages, they basically speak for themselves * First we define our messages, they basically speak for themselves
*/ */
@ -86,12 +84,11 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor {
case Taken(`chopstickToWaitFor`) => case Taken(`chopstickToWaitFor`) =>
println("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name)) println("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name))
become(eating) become(eating)
system.scheduler.scheduleOnce(5 seconds, self, Think) system.scheduler.scheduleOnce(5.seconds, self, Think)
case Busy(chopstick) => case Busy(chopstick) =>
become(thinking)
otherChopstick ! Put(self) otherChopstick ! Put(self)
self ! Eat startThinking(10.milliseconds)
} }
//When the results of the other grab comes back, //When the results of the other grab comes back,
@ -99,38 +96,39 @@ class Hakker(name: String, left: ActorRef, right: ActorRef) extends Actor {
//Then go back and think and try to grab the chopsticks again //Then go back and think and try to grab the chopsticks again
def denied_a_chopstick: Receive = { def denied_a_chopstick: Receive = {
case Taken(chopstick) => case Taken(chopstick) =>
become(thinking)
chopstick ! Put(self) chopstick ! Put(self)
self ! Eat startThinking(10.milliseconds)
case Busy(chopstick) => case Busy(chopstick) =>
become(thinking) startThinking(10.milliseconds)
self ! Eat
} }
//When a hakker is eating, he can decide to start to think, //When a hakker is eating, he can decide to start to think,
//then he puts down his chopsticks and starts to think //then he puts down his chopsticks and starts to think
def eating: Receive = { def eating: Receive = {
case Think => case Think =>
become(thinking)
left ! Put(self) left ! Put(self)
right ! Put(self) right ! Put(self)
println("%s puts down his chopsticks and starts to think".format(name)) println("%s puts down his chopsticks and starts to think".format(name))
system.scheduler.scheduleOnce(5 seconds, self, Eat) startThinking(5.seconds)
} }
//All hakkers start in a non-eating state //All hakkers start in a non-eating state
def receive = { def receive = {
case Think => case Think =>
println("%s starts to think".format(name)) println("%s starts to think".format(name))
startThinking(5.seconds)
}
private def startThinking(duration: FiniteDuration): Unit = {
become(thinking) become(thinking)
system.scheduler.scheduleOnce(5 seconds, self, Eat) system.scheduler.scheduleOnce(duration, self, Eat)
} }
} }
/* /*
* Alright, here's our test-harness * Alright, here's our test-harness
*/ */
object DiningHakkers { object DiningHakkersOnBecome {
val system = ActorSystem() val system = ActorSystem()
def main(args: Array[String]): Unit = run() def main(args: Array[String]): Unit = run()

View file

@ -1,13 +1,15 @@
/** /**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>. * Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>.
*/ */
package sample.fsm.dining.fsm package sample.fsm
import language.postfixOps
import akka.actor._ import akka.actor._
import akka.actor.FSM._ import akka.actor.FSM._
import scala.concurrent.duration._ import scala.concurrent.duration._
// Akka adaptation of
// http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/
/* /*
* Some messages for the chopstick * Some messages for the chopstick
*/ */
@ -91,7 +93,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
when(Waiting) { when(Waiting) {
case Event(Think, _) => case Event(Think, _) =>
println("%s starts to think".format(name)) println("%s starts to think".format(name))
startThinking(5 seconds) startThinking(5.seconds)
} }
//When a hakker is thinking it can become hungry //When a hakker is thinking it can become hungry
@ -125,12 +127,12 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
case Event(Busy(chopstick), TakenChopsticks(leftOption, rightOption)) => case Event(Busy(chopstick), TakenChopsticks(leftOption, rightOption)) =>
leftOption.foreach(_ ! Put) leftOption.foreach(_ ! Put)
rightOption.foreach(_ ! Put) rightOption.foreach(_ ! Put)
startThinking(10 milliseconds) startThinking(10.milliseconds)
} }
private def startEating(left: ActorRef, right: ActorRef): State = { private def startEating(left: ActorRef, right: ActorRef): State = {
println("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name)) println("%s has picked up %s and %s and starts to eat".format(name, left.path.name, right.path.name))
goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5 seconds) goto(Eating) using TakenChopsticks(Some(left), Some(right)) forMax (5.seconds)
} }
// When the results of the other grab comes back, // When the results of the other grab comes back,
@ -139,9 +141,9 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
when(FirstChopstickDenied) { when(FirstChopstickDenied) {
case Event(Taken(secondChopstick), _) => case Event(Taken(secondChopstick), _) =>
secondChopstick ! Put secondChopstick ! Put
startThinking(10 milliseconds) startThinking(10.milliseconds)
case Event(Busy(chopstick), _) => case Event(Busy(chopstick), _) =>
startThinking(10 milliseconds) startThinking(10.milliseconds)
} }
// When a hakker is eating, he can decide to start to think, // When a hakker is eating, he can decide to start to think,
@ -151,7 +153,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
println("%s puts down his chopsticks and starts to think".format(name)) println("%s puts down his chopsticks and starts to think".format(name))
left ! Put left ! Put
right ! Put right ! Put
startThinking(5 seconds) startThinking(5.seconds)
} }
// Initialize the hakker // Initialize the hakker

View file

@ -0,0 +1,73 @@
<!-- <html> -->
<head>
<title>Akka FSM in Scala</title>
</head>
<body>
<div>
<h2>Finite State Machine in Actors</h2>
<p>
This sample is an adaptation of
<a href="http://www.dalnefre.com/wp/2010/08/dining-philosophers-in-humus/" target="_blank">Dining Hakkers</a>.
It illustrates how state and behavior can be managed within
an Actor with two different approaches; using <code>become</code> and using
the <code>FSM</code> trait.
</p>
</div>
<div>
<h2>Dining Hakkers with Become</h2>
<p>
Open <a href="#code/src/main/scala/sample/become/DiningHakkersOnBecome.scala" class="shortcut">DiningHakkersOnBecome.scala</a>.
</p>
<p>
It illustrates how current behavior can be replaced with <code>context.become</code>.
Note that no <code>var</code> members are used, instead the state is encoded in the current
behavior and its parameters.
</p>
<p>
Go to the <a href="#run" class="shortcut">Run</a> tab, and start the application main class
<code>sample.become.DiningHakkersOnBecome</code>.
In the log output you can see the actions of the <code>Hakker</code> actors.
</p>
<p>
Read more about <code>become</code> in
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/scala/actors.html#Become_Unbecome" target="_blank">the documentation</a>.
</p>
</div>
<div>
<h2>Dining Hakkers with FSM</h2>
<p>
Open <a href="#code/src/main/scala/sample/fsm/DiningHakkersOnFsm.scala" class="shortcut">DiningHakkersOnFsm.scala</a>.
</p>
<p>
It illustrates how the states and transitions can be defined with the <code>akka.actor.FSM</code> trait.
</p>
<p>
Go to the <a href="#run" class="shortcut">Run</a> tab, and start the application main class
<code>sample.fsm.DiningHakkersOnFsm</code>.
In the log output you can see the actions of the <code>Hakker</code> actors.
</p>
<p>
Read more about <code>akka.actor.FSM</code> in
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/scala/fsm.html" target="_blank">the documentation</a>.
</p>
</div>
</body>
</html>

View file

@ -1,15 +0,0 @@
FSM Sample
==========
This sample is meant to be used by studying the code; it does not perform any
astounding functions when running it. If you want to run it, check out the akka
sources on your local hard drive, follow the [instructions for setting up Akka
with SBT](http://doc.akka.io/docs/akka/current/intro/getting-started.html).
When you start SBT within the checked-out akka source directory, you can run
this sample by typing
akka-sample-fsm/run
and then choose one of the two ways (one using `context.become` and one using FSM).
You can read more in the [Akka docs](http://akka.io/docs).

View file

@ -1,107 +0,0 @@
/**
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>.
*/
package sample.fsm.buncher
import akka.actor.ActorRefFactory
import scala.reflect.ClassTag
import scala.concurrent.duration.Duration
import akka.actor.{ FSM, Actor, ActorRef }
import scala.concurrent.duration.FiniteDuration
/*
* generic typed object buncher.
*
* To instantiate it, use the factory method like so:
* Buncher(100, 500)(x : List[AnyRef] => x foreach println)
* which will yield a fully functional ActorRef.
* The type of messages allowed is strongly typed to match the
* supplied processing method; other messages are discarded (and
* possibly logged).
*/
object GenericBuncher {
trait State
case object Idle extends State
case object Active extends State
case object Flush // send out current queue immediately
case object Stop // poison pill
class MsgExtractor[A: ClassTag] {
def unapply(m: AnyRef): Option[A] =
if (implicitly[ClassTag[A]].runtimeClass isAssignableFrom m.getClass)
Some(m.asInstanceOf[A])
else
None
}
}
abstract class GenericBuncher[A: ClassTag, B](val singleTimeout: FiniteDuration, val multiTimeout: FiniteDuration)
extends Actor with FSM[GenericBuncher.State, B] {
import GenericBuncher._
import FSM._
protected def empty: B
protected def merge(acc: B, elem: A): B
protected def send(acc: B): Unit
protected def flush(acc: B) = {
send(acc)
cancelTimer("multi")
goto(Idle) using empty
}
val Msg = new MsgExtractor[A]
startWith(Idle, empty)
when(Idle) {
case Event(Msg(m), acc) =>
setTimer("multi", StateTimeout, multiTimeout, false)
goto(Active) using merge(acc, m)
case Event(Flush, _) => stay
case Event(Stop, _) => stop
}
when(Active, stateTimeout = singleTimeout) {
case Event(Msg(m), acc) =>
stay using merge(acc, m)
case Event(StateTimeout, acc) =>
flush(acc)
case Event(Flush, acc) =>
flush(acc)
case Event(Stop, acc) =>
send(acc)
cancelTimer("multi")
stop
}
initialize()
}
object Buncher {
case class Target(target: ActorRef) // for setting the target for default send action
val Stop = GenericBuncher.Stop // make special message objects visible for Buncher clients
val Flush = GenericBuncher.Flush
}
class Buncher[A: ClassTag](singleTimeout: FiniteDuration, multiTimeout: FiniteDuration)
extends GenericBuncher[A, List[A]](singleTimeout, multiTimeout) {
import Buncher._
private var target: Option[ActorRef] = None
protected def send(acc: List[A]): Unit = if (target.isDefined) target.get ! acc.reverse
protected def empty: List[A] = Nil
protected def merge(l: List[A], elem: A) = elem :: l
whenUnhandled {
case Event(Target(t), _) =>
target = Some(t)
stay
}
}

View file

@ -369,7 +369,7 @@ object AkkaBuild extends Build {
settings = parentSettings ++ ActivatorDist.settings, settings = parentSettings ++ ActivatorDist.settings,
aggregate = Seq(camelSampleJava, camelSampleScala, mainSampleJava, mainSampleScala, aggregate = Seq(camelSampleJava, camelSampleScala, mainSampleJava, mainSampleScala,
remoteSampleJava, remoteSampleScala, clusterSampleJava, clusterSampleScala, remoteSampleJava, remoteSampleScala, clusterSampleJava, clusterSampleScala,
fsmSample, persistenceSample, fsmSampleScala, persistenceSample,
multiNodeSample, helloKernelSample, osgiDiningHakkersSample) multiNodeSample, helloKernelSample, osgiDiningHakkersSample)
) )
@ -387,9 +387,9 @@ object AkkaBuild extends Build {
settings = sampleSettings ++ Seq(libraryDependencies ++= Dependencies.camelSample) settings = sampleSettings ++ Seq(libraryDependencies ++= Dependencies.camelSample)
) )
lazy val fsmSample = Project( lazy val fsmSampleScala = Project(
id = "akka-sample-fsm", id = "akka-sample-fsm-scala",
base = file("akka-samples/akka-sample-fsm"), base = file("akka-samples/akka-sample-fsm-scala"),
dependencies = Seq(actor), dependencies = Seq(actor),
settings = sampleSettings settings = sampleSettings
) )