Forked from
LARA / CS-206 Demos
6 commits behind the upstream repository.
-
Matt Bovel authoredMatt Bovel authored
Problem2.scala 3.77 KiB
package concpar21final02
import akka.actor.*
import scala.collection.mutable
import akka.testkit.*
object Problem2:
//////////////////////////////
// NOTIFICATION SERVICE //
//////////////////////////////
object NotificationService:
enum Protocol:
/** Notify all registered actors */
case NotifyAll
/** Register the actor that sent the `Register` request */
case Register //
/** Un-register the actor that sent the `Register` request */
case UnRegister
enum Responses:
/** Message sent to an actor when it is notified */
case Notification
/** Response sent to an actor after a `Register` or `UnRegister` */
case Registered(registered: Boolean)
class NotificationService extends Actor:
import NotificationService.Protocol.*
import NotificationService.Responses.*
private val registeredUsers = mutable.Set.empty[ActorRef]
def receive: Receive = {
case Register =>
registeredUsers += sender()
sender() ! Registered(true)
case UnRegister =>
registeredUsers -= sender()
sender() ! Registered(false)
case NotifyAll =>
for user <- registeredUsers do user ! Notification
}
/////////////////////////
// DISCORD CHANNEL //
/////////////////////////
object DiscordChannel:
enum Protocol:
/** Post a message in the channel */
case Post(msg: String)
/** Ask for the list of most recent posts starting from the most recent
* one. The list must have at most `limit` posts.
*/
case GetLastPosts(limit: Int)
/** Activates the service channel using the provided notification service.
*/
case Init(notificationService: ActorRef)
enum Responses:
/** Response to `GetLastPosts` if active */
case Posts(msgs: List[String])
/** Response after `Init` if non-active */
case Active
/** Response `Post` and `GetLastPosts` if non-active */
case NotActive
/** Response after `Init` if active */
case AlreadyActive
class DiscordChannel extends Actor:
import DiscordChannel.Protocol.*
import DiscordChannel.Responses.*
import NotificationService.Protocol.*
private var messages: List[String] = Nil
def receive: Receive = nonActive
def nonActive: Receive = {
case Init(service) =>
context.become(active(service))
sender() ! Active
case Post(_) | GetLastPosts(_) =>
sender() ! NotActive
}
def active(notificationService: ActorRef): Receive = {
case Post(msg) =>
messages = msg :: messages
notificationService ! NotifyAll
case GetLastPosts(limit) =>
sender() ! Posts(messages.take(limit))
case Init(_) =>
sender() ! AlreadyActive
}
/////////////////////////
// DEBUG //
/////////////////////////
/** Infrastructure to help debugging. In sbt use `run` to execute this code. The
* TestKit is an actor that can send messages and check the messages it
* receives (or not).
*/
@main def debug() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import Problem2.*
import DiscordChannel.Protocol.*
import DiscordChannel.Responses.*
import NotificationService.Protocol.*
import NotificationService.Responses.*
import concurrent.duration.*
try
val notificationService = system.actorOf(Props[NotificationService]())
val channel = system.actorOf(Props[DiscordChannel]())
notificationService ! NotifyAll
expectNoMessage(
200.millis
) // expects no message is received in the next 200 milliseconds
notificationService ! Register
expectMsg(
200.millis,
Registered(true)
) // expects to receive `Registered(true)` in the next 200 milliseconds
finally shutdown(system)