Skip to content
Snippets Groups Projects
Forked from LARA / CS-206 Demos
6 commits behind the upstream repository.
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)