Merge branch 'master' of https://github.com/jboner/akka into ticket-438
This commit is contained in:
commit
b4dc22bb66
3 changed files with 36 additions and 16 deletions
|
|
@ -13,11 +13,15 @@ trait FSM[S, D] {
|
|||
type StateFunction = scala.PartialFunction[Event, State]
|
||||
|
||||
/** DSL */
|
||||
protected final def inState(stateName: S)(stateFunction: StateFunction) = {
|
||||
protected final def notifying(transitionHandler: PartialFunction[Transition, Unit]) = {
|
||||
transitionEvent = transitionHandler
|
||||
}
|
||||
|
||||
protected final def when(stateName: S)(stateFunction: StateFunction) = {
|
||||
register(stateName, stateFunction)
|
||||
}
|
||||
|
||||
protected final def setInitialState(stateName: S, stateData: D, timeout: Option[Long] = None) = {
|
||||
protected final def startWith(stateName: S, stateData: D, timeout: Option[Long] = None) = {
|
||||
setState(State(stateName, stateData, timeout))
|
||||
}
|
||||
|
||||
|
|
@ -74,6 +78,10 @@ trait FSM[S, D] {
|
|||
case reason => log.info("Stopping because of reason: %s", reason)
|
||||
}
|
||||
|
||||
private var transitionEvent: PartialFunction[Transition, Unit] = {
|
||||
case Transition(from, to) => log.debug("Transitioning from state %s to %s", from, to)
|
||||
}
|
||||
|
||||
override final protected def receive: Receive = {
|
||||
case Stop(reason, stateData) =>
|
||||
terminateEvent.apply(reason)
|
||||
|
|
@ -93,6 +101,9 @@ trait FSM[S, D] {
|
|||
if (!transitions.contains(nextState.stateName)) {
|
||||
stop(Failure("Next state %s does not exist".format(nextState.stateName)))
|
||||
} else {
|
||||
if (currentState != null && currentState.stateName != nextState.stateName) {
|
||||
transitionEvent.apply(Transition(currentState.stateName, nextState.stateName))
|
||||
}
|
||||
currentState = nextState
|
||||
currentState.timeout.foreach {
|
||||
t =>
|
||||
|
|
@ -128,6 +139,8 @@ trait FSM[S, D] {
|
|||
case class Failure(cause: Any) extends Reason
|
||||
|
||||
case object StateTimeout
|
||||
|
||||
|
||||
case class Transition(from: S, to: S)
|
||||
|
||||
private case class Stop(reason: Reason, stateData: D)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ object FSMActorSpec {
|
|||
val lockedLatch = new StandardLatch
|
||||
val unhandledLatch = new StandardLatch
|
||||
val terminatedLatch = new StandardLatch
|
||||
val transitionLatch = new StandardLatch
|
||||
|
||||
sealed trait LockState
|
||||
case object Locked extends LockState
|
||||
|
|
@ -24,7 +25,12 @@ object FSMActorSpec {
|
|||
|
||||
class Lock(code: String, timeout: Int) extends Actor with FSM[LockState, CodeState] {
|
||||
|
||||
inState(Locked) {
|
||||
notifying {
|
||||
case Transition(Locked, Open) => transitionLatch.open
|
||||
case Transition(_, _) => ()
|
||||
}
|
||||
|
||||
when(Locked) {
|
||||
case Event(digit: Char, CodeState(soFar, code)) => {
|
||||
soFar + digit match {
|
||||
case incomplete if incomplete.length < code.length =>
|
||||
|
|
@ -43,14 +49,14 @@ object FSMActorSpec {
|
|||
case Event("bye", _) => stop(Shutdown)
|
||||
}
|
||||
|
||||
inState(Open) {
|
||||
when(Open) {
|
||||
case Event(StateTimeout, stateData) => {
|
||||
doLock
|
||||
goto(Locked)
|
||||
}
|
||||
}
|
||||
|
||||
setInitialState(Locked, CodeState("", code))
|
||||
startWith(Locked, CodeState("", code))
|
||||
|
||||
whenUnhandled {
|
||||
case Event(_, stateData) => {
|
||||
|
|
@ -94,6 +100,7 @@ class FSMActorSpec extends JUnitSuite {
|
|||
lock ! '1'
|
||||
|
||||
assert(unlockedLatch.tryAwait(1, TimeUnit.SECONDS))
|
||||
assert(transitionLatch.tryAwait(1, TimeUnit.SECONDS))
|
||||
assert(lockedLatch.tryAwait(2, TimeUnit.SECONDS))
|
||||
|
||||
lock ! "not_handled"
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ class Chopstick(name: String) extends Actor with FSM[ChopstickState, TakenBy] {
|
|||
self.id = name
|
||||
|
||||
// When a chopstick is available, it can be taken by a some hakker
|
||||
inState(Available) {
|
||||
when(Available) {
|
||||
case Event(Take, _) =>
|
||||
goto(Taken) using TakenBy(self.sender) replying Taken(self)
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ class Chopstick(name: String) extends Actor with FSM[ChopstickState, TakenBy] {
|
|||
// When a chopstick is taken by a hakker
|
||||
// It will refuse to be taken by other hakkers
|
||||
// But the owning hakker can put it back
|
||||
inState(Taken) {
|
||||
when(Taken) {
|
||||
case Event(Take, currentState) =>
|
||||
stay replying Busy(self)
|
||||
case Event(Put, TakenBy(hakker)) if self.sender == hakker =>
|
||||
|
|
@ -47,7 +47,7 @@ class Chopstick(name: String) extends Actor with FSM[ChopstickState, TakenBy] {
|
|||
}
|
||||
|
||||
// A chopstick begins its existence as available and taken by no one
|
||||
setInitialState(Available, TakenBy(None))
|
||||
startWith(Available, TakenBy(None))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +78,7 @@ case class TakenChopsticks(left: Option[ActorRef], right: Option[ActorRef])
|
|||
class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor with FSM[FSMHakkerState, TakenChopsticks] {
|
||||
self.id = name
|
||||
|
||||
inState(Waiting) {
|
||||
when(Waiting) {
|
||||
case Event(Think, _) =>
|
||||
log.info("%s starts to think", name)
|
||||
startThinking(5000)
|
||||
|
|
@ -86,7 +86,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
|
||||
//When a hakker is thinking it can become hungry
|
||||
//and try to pick up its chopsticks and eat
|
||||
inState(Thinking) {
|
||||
when(Thinking) {
|
||||
case Event(StateTimeout, _) =>
|
||||
left ! Take
|
||||
right ! Take
|
||||
|
|
@ -97,7 +97,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
// When it picks one up, it goes into wait for the other
|
||||
// If the hakkers first attempt at grabbing a chopstick fails,
|
||||
// it starts to wait for the response of the other grab
|
||||
inState(Hungry) {
|
||||
when(Hungry) {
|
||||
case Event(Taken(`left`), _) =>
|
||||
goto(WaitForOtherChopstick) using TakenChopsticks(Some(left), None)
|
||||
case Event(Taken(`right`), _) =>
|
||||
|
|
@ -109,7 +109,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
// When a hakker is waiting for the last chopstick it can either obtain it
|
||||
// and start eating, or the other chopstick was busy, and the hakker goes
|
||||
// back to think about how he should obtain his chopsticks :-)
|
||||
inState(WaitForOtherChopstick) {
|
||||
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)) =>
|
||||
|
|
@ -126,7 +126,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
// When the results of the other grab comes back,
|
||||
// he needs to put it back if he got the other one.
|
||||
// Then go back and think and try to grab the chopsticks again
|
||||
inState(FirstChopstickDenied) {
|
||||
when(FirstChopstickDenied) {
|
||||
case Event(Taken(secondChopstick), _) =>
|
||||
secondChopstick ! Put
|
||||
startThinking(10)
|
||||
|
|
@ -136,7 +136,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
|
||||
// When a hakker is eating, he can decide to start to think,
|
||||
// then he puts down his chopsticks and starts to think
|
||||
inState(Eating) {
|
||||
when(Eating) {
|
||||
case Event(StateTimeout, _) =>
|
||||
log.info("%s puts down his chopsticks and starts to think", name)
|
||||
left ! Put
|
||||
|
|
@ -149,7 +149,7 @@ class FSMHakker(name: String, left: ActorRef, right: ActorRef) extends Actor wit
|
|||
}
|
||||
|
||||
//All hakkers start waiting
|
||||
setInitialState(Waiting, TakenChopsticks(None, None))
|
||||
startWith(Waiting, TakenChopsticks(None, None))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue