diff --git a/jvm/src/main/scala/cs214/webapp/server/web/ServerApp.scala b/jvm/src/main/scala/cs214/webapp/server/web/ServerApp.scala index 4b2313b1e53b623e28b8dd5f1d6d8ff4462f87ea..c468c3d588ad1c4f7862ba75b93fece96b127b89 100644 --- a/jvm/src/main/scala/cs214/webapp/server/web/ServerApp.scala +++ b/jvm/src/main/scala/cs214/webapp/server/web/ServerApp.scala @@ -10,8 +10,6 @@ import scala.collection.mutable import scala.util.{Success, Failure, Try} import scala.concurrent.duration.Duration -import cask.endpoints.WsChannelActor - case class BroadcastException(ex: Throwable) extends Throwable /** Server-side apps definition and abstractions */ @@ -86,8 +84,7 @@ private[web] abstract class ServerApp: handleMessage(userId, ujson.read(data)) } - def disconnect(userId: UserId, channel: WsChannelActor): Unit = instanceLock.synchronized: - recordActivity() + def disconnect(userId: UserId, channel: cask.endpoints.WsChannelActor): Unit = instanceLock.synchronized: channels(userId).remove(channel) if channels(userId).isEmpty then channels.remove(userId) println(f"[${appInfo.id}/$instanceId] client \"$userId\" disconnected") @@ -96,11 +93,14 @@ private[web] abstract class ServerApp: def connectedClients: Seq[UserId] = instanceLock.synchronized: channels.keysIterator.toSeq + def hasClients = instanceLock.synchronized: + channels.nonEmpty + def shutdown() = instanceLock.synchronized: - for channels <- channels.values - channel <- channels - do - channel.send(cask.Ws.Close()) + for channels <- channels.values + channel <- channels + do + channel.send(cask.Ws.Close()) /** Sends a message to a specific client. */ private def send(userId: UserId)(message: ujson.Value): Unit = instanceLock.synchronized: @@ -178,8 +178,6 @@ private[web] final class ClockDrivenStateMachineServerApp[E, S, V]( val name = f"${appInfo.id}/$instanceId/clock" Clock(name, sm.clockPeriodMs)(handleClockTick) - clock.start() - def handleClockTick: Unit = instanceLock.synchronized: try val event = Left(Tick(System.currentTimeMillis)) @@ -189,6 +187,17 @@ private[web] final class ClockDrivenStateMachineServerApp[E, S, V]( catch ex => println(f"[${appInfo.id}/$instanceId] Uncaught exception in clock action: $ex") + override def connect(userId: UserId) + (implicit cc: castor.Context, log: cask.Logger) + : cask.WebsocketResult = instanceLock.synchronized: + if !hasClients then clock.start() + super.connect(userId) + + override def disconnect(userId: UserId, channel: cask.endpoints.WsChannelActor) + : Unit = instanceLock.synchronized: + super.disconnect(userId, channel) + if !hasClients then clock.shutdown() + override def shutdown(): Unit = clock.shutdown() super.shutdown() diff --git a/jvm/src/main/scala/cs214/webapp/server/web/WebServer.scala b/jvm/src/main/scala/cs214/webapp/server/web/WebServer.scala index 88e1e6118b056a8cd6ec1d0575fac05ac70a6795..69e96b5077c92891bfec207b0dbb1dd8094d20a6 100644 --- a/jvm/src/main/scala/cs214/webapp/server/web/WebServer.scala +++ b/jvm/src/main/scala/cs214/webapp/server/web/WebServer.scala @@ -60,12 +60,12 @@ object WebServer: private[web] def createInstance(appId: AppId, registeredUserIds: Seq[UserId]): InstanceId = withFreshInstanceId: instanceId => instances(instanceId) = appDirectory(appId).init(instanceId, registeredUserIds) - println(f"[$appId] instance created $instanceId") + println(f"[$appId/$instanceId] instance created") instanceId private[web] def shutdownApp(instanceId: InstanceId): Unit = instances.remove(instanceId).map: app => app.shutdown() - println(s"[${app.appInfo.id}][$instanceId] shut down") + println(f"[${app.appInfo.id}/$instanceId] instance shut down") GarbageCollector.start()