add FSMDocSpec and improve FSM API
- don’t require Option[Duration] and its implicit conversion - provider “->” extractor inside the FSM trait
This commit is contained in:
parent
1333700c0d
commit
5e11b2df9d
3 changed files with 109 additions and 18 deletions
|
|
@ -49,21 +49,6 @@ object FSM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* This extractor is just convenience for matching a (S, S) pair, including a
|
|
||||||
* reminder what the new state is.
|
|
||||||
*/
|
|
||||||
object -> {
|
|
||||||
def unapply[S](in: (S, S)) = Some(in)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* With these implicits in scope, you can write "5 seconds" anywhere a
|
|
||||||
* Duration or Option[Duration] is expected. This is conveniently true
|
|
||||||
* for derived classes.
|
|
||||||
*/
|
|
||||||
implicit def d2od(d: Duration): Option[Duration] = Some(d)
|
|
||||||
|
|
||||||
case class LogEntry[S, D](stateName: S, stateData: D, event: Any)
|
case class LogEntry[S, D](stateName: S, stateData: D, event: Any)
|
||||||
|
|
||||||
case class State[S, D](stateName: S, stateData: D, timeout: Option[Duration] = None, stopReason: Option[Reason] = None, replies: List[Any] = Nil) {
|
case class State[S, D](stateName: S, stateData: D, timeout: Option[Duration] = None, stopReason: Option[Reason] = None, replies: List[Any] = Nil) {
|
||||||
|
|
@ -208,7 +193,12 @@ trait FSM[S, D] extends Listeners {
|
||||||
* @param stateTimeout default state timeout for this state
|
* @param stateTimeout default state timeout for this state
|
||||||
* @param stateFunction partial function describing response to input
|
* @param stateFunction partial function describing response to input
|
||||||
*/
|
*/
|
||||||
protected final def when(stateName: S, stateTimeout: Timeout = None)(stateFunction: StateFunction) = {
|
protected final def when(stateName: S, stateTimeout: Duration = null)(stateFunction: StateFunction) = {
|
||||||
|
register(stateName, stateFunction, Option(stateTimeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
@deprecated("use the more import-friendly variant taking a Duration", "2.0")
|
||||||
|
protected final def when(stateName: S, stateTimeout: Timeout)(stateFunction: StateFunction) = {
|
||||||
register(stateName, stateFunction, stateTimeout)
|
register(stateName, stateFunction, stateTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,6 +302,14 @@ trait FSM[S, D] extends Listeners {
|
||||||
stateTimeouts(state) = timeout
|
stateTimeouts(state) = timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extractor is just convenience for matching a (S, S) pair, including a
|
||||||
|
* reminder what the new state is.
|
||||||
|
*/
|
||||||
|
object -> {
|
||||||
|
def unapply[S](in: (S, S)) = Some(in)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set handler which is called upon each state transition, i.e. not when
|
* Set handler which is called upon each state transition, i.e. not when
|
||||||
* staying in the same state. This may use the pair extractor defined in the
|
* staying in the same state. This may use the pair extractor defined in the
|
||||||
|
|
|
||||||
93
akka-docs/scala/code/akka/docs/actor/FSMDocSpec.scala
Normal file
93
akka-docs/scala/code/akka/docs/actor/FSMDocSpec.scala
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||||
|
*/
|
||||||
|
package akka.docs.actor
|
||||||
|
|
||||||
|
import akka.testkit.AkkaSpec
|
||||||
|
|
||||||
|
class FSMDocSpec extends AkkaSpec {
|
||||||
|
|
||||||
|
"simple finite state machine" must {
|
||||||
|
//#simple-imports
|
||||||
|
import akka.actor.{ Actor, ActorRef, FSM, Props }
|
||||||
|
import akka.util.duration._
|
||||||
|
//#simple-imports
|
||||||
|
//#simple-events
|
||||||
|
// received events
|
||||||
|
case class SetTarget(ref: ActorRef)
|
||||||
|
case class Queue(obj: Any)
|
||||||
|
case object Flush
|
||||||
|
|
||||||
|
// sent events
|
||||||
|
case class Batch(obj: Seq[Any])
|
||||||
|
//#simple-events
|
||||||
|
//#simple-state
|
||||||
|
// states
|
||||||
|
trait State
|
||||||
|
case object Idle extends State
|
||||||
|
case object Active extends State
|
||||||
|
|
||||||
|
trait Data
|
||||||
|
case object Uninitialized extends Data
|
||||||
|
case class Todo(target: ActorRef, queue: Seq[Any]) extends Data
|
||||||
|
//#simple-state
|
||||||
|
//#simple-fsm
|
||||||
|
class Buncher extends Actor with FSM[State, Data] {
|
||||||
|
|
||||||
|
startWith(Idle, Uninitialized)
|
||||||
|
|
||||||
|
when(Idle) {
|
||||||
|
case Event(SetTarget(ref), Uninitialized) ⇒ stay using Todo(ref, Vector.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-transition
|
||||||
|
onTransition {
|
||||||
|
case Active -> Idle ⇒
|
||||||
|
stateData match {
|
||||||
|
case Todo(ref, queue) ⇒ ref ! Batch(queue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#simple-transition
|
||||||
|
|
||||||
|
when(Active, stateTimeout = 1 second) {
|
||||||
|
case Event(Flush | FSM.StateTimeout, t: Todo) ⇒ goto(Idle) using t.copy(queue = Vector.empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
//#simple-unhandled
|
||||||
|
whenUnhandled {
|
||||||
|
// common code for both states
|
||||||
|
case Event(Queue(obj), t @ Todo(_, v)) ⇒
|
||||||
|
goto(Active) using t.copy(queue = v :+ obj)
|
||||||
|
|
||||||
|
case Event(e, s) ⇒
|
||||||
|
log.warning("received unhandled request {} in state {}/{}", e, stateName, s)
|
||||||
|
stay
|
||||||
|
}
|
||||||
|
//#simple-unhandled
|
||||||
|
|
||||||
|
initialize
|
||||||
|
}
|
||||||
|
//#simple-fsm
|
||||||
|
|
||||||
|
"batch correctly" in {
|
||||||
|
val buncher = system.actorOf(Props(new Buncher))
|
||||||
|
buncher ! SetTarget(testActor)
|
||||||
|
buncher ! Queue(42)
|
||||||
|
buncher ! Queue(43)
|
||||||
|
expectMsg(Batch(Seq(42, 43)))
|
||||||
|
buncher ! Queue(44)
|
||||||
|
buncher ! Flush
|
||||||
|
buncher ! Queue(45)
|
||||||
|
expectMsg(Batch(Seq(44)))
|
||||||
|
expectMsg(Batch(Seq(45)))
|
||||||
|
}
|
||||||
|
|
||||||
|
"batch not if uninitialized" in {
|
||||||
|
val buncher = system.actorOf(Props(new Buncher))
|
||||||
|
buncher ! Queue(42)
|
||||||
|
expectNoMsg
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -62,8 +62,8 @@ class TestFSMRef[S, D, T <: Actor](
|
||||||
* corresponding transition initiated from within the FSM, including timeout
|
* corresponding transition initiated from within the FSM, including timeout
|
||||||
* and stop handling.
|
* and stop handling.
|
||||||
*/
|
*/
|
||||||
def setState(stateName: S = fsm.stateName, stateData: D = fsm.stateData, timeout: Option[Duration] = None, stopReason: Option[FSM.Reason] = None) {
|
def setState(stateName: S = fsm.stateName, stateData: D = fsm.stateData, timeout: Duration = null, stopReason: Option[FSM.Reason] = None) {
|
||||||
fsm.applyState(FSM.State(stateName, stateData, timeout, stopReason))
|
fsm.applyState(FSM.State(stateName, stateData, Option(timeout), stopReason))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue