Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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)