From eef6350c45bff095f0eaf4346fbbc5714c9d66c3 Mon Sep 17 00:00:00 2001 From: momania Date: Thu, 28 Oct 2010 16:52:50 +0200 Subject: [PATCH] More sugar on the syntax --- akka-actor/src/main/scala/actor/FSM.scala | 19 +++++++++++++++--- .../test/scala/actor/actor/FSMActorSpec.scala | 13 +++++++++--- .../src/main/scala/DiningHakkersOnFsm.scala | 20 +++++++++---------- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/akka-actor/src/main/scala/actor/FSM.scala b/akka-actor/src/main/scala/actor/FSM.scala index eac861d358..07ea1fc047 100644 --- a/akka-actor/src/main/scala/actor/FSM.scala +++ b/akka-actor/src/main/scala/actor/FSM.scala @@ -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) } diff --git a/akka-actor/src/test/scala/actor/actor/FSMActorSpec.scala b/akka-actor/src/test/scala/actor/actor/FSMActorSpec.scala index dc6893c820..692c4cf807 100644 --- a/akka-actor/src/test/scala/actor/actor/FSMActorSpec.scala +++ b/akka-actor/src/test/scala/actor/actor/FSMActorSpec.scala @@ -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" diff --git a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala index ecb4d82ba0..19ae5e02fb 100644 --- a/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala +++ b/akka-samples/akka-sample-fsm/src/main/scala/DiningHakkersOnFsm.scala @@ -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)) } /*