From 23c306a5f2ddeb15bb9ac4aaf348652eac8663e4 Mon Sep 17 00:00:00 2001 From: Thibault Duplessis Date: Fri, 20 Sep 2024 21:00:39 +0200 Subject: [PATCH] send fewer `crowd` websocket events in very large rooms we have broadcasts with 10k+ members. the most frequent message sent to all members is `crowd`, once per second. if the room contains more than 100 members, we only send `crowd` 10% of the time. if the room has 1000 members, we send 1% of the time. importantly, if the named users of the crowd change, then we always send the message. That's necessary to know which contributors and streamers are present. --- src/main/scala/actor/RoomActor.scala | 16 ++++++++++------ src/main/scala/ipc/ClientIn.scala | 4 ++-- src/main/scala/ipc/CrowdJson.scala | 6 ++++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/scala/actor/RoomActor.scala b/src/main/scala/actor/RoomActor.scala index d43cbf53..c182cea7 100644 --- a/src/main/scala/actor/RoomActor.scala +++ b/src/main/scala/actor/RoomActor.scala @@ -51,12 +51,16 @@ object RoomActor: None -> None case crowd: ClientIn.Crowd => - if crowd == state.lastCrowd then None -> None - else - Some { - deps.clientIn(crowd) - state.copy(lastCrowd = crowd) - } -> None + val shouldSend = + if crowd == state.lastCrowd then false + else if crowd.users != state.lastCrowd.users then true + else if crowd.members > 1000 && crowd.members % 100 != 0 then false + else if crowd.members > 100 && crowd.members % 10 != 0 then false + else true + if shouldSend then + deps.clientIn(crowd) + Some(state.copy(lastCrowd = crowd)) -> None + else None -> None case SetTroll(v) => Some(state.copy(isTroll = v)) -> None diff --git a/src/main/scala/ipc/ClientIn.scala b/src/main/scala/ipc/ClientIn.scala index 656a2495..3e7a7fa2 100644 --- a/src/main/scala/ipc/ClientIn.scala +++ b/src/main/scala/ipc/ClientIn.scala @@ -68,9 +68,9 @@ object ClientIn: def payload(js: JsValue) = Payload(JsonString(Json.stringify(js))) def payload(tpe: String, js: JsonString) = Payload(JsonString(cliMsg(tpe, js))) - case class Crowd(doc: JsObject) extends ClientIn: + case class Crowd(doc: JsObject, members: Int, users: Set[User.Id]) extends ClientIn: lazy val write = cliMsg("crowd", doc) - val emptyCrowd = Crowd(Json.obj()) + val emptyCrowd = Crowd(Json.obj(), 0, Set.empty) case class LobbyPairing(fullId: Game.FullId) extends ClientIn: def write = diff --git a/src/main/scala/ipc/CrowdJson.scala b/src/main/scala/ipc/CrowdJson.scala index 89356868..14c4aa8d 100644 --- a/src/main/scala/ipc/CrowdJson.scala +++ b/src/main/scala/ipc/CrowdJson.scala @@ -11,7 +11,7 @@ final class CrowdJson(inquirers: Inquirers, mongo: Mongo, lightUserApi: LightUse keepOnlyStudyMembers(crowd).map: users => crowd.copy(users = users, anons = 0) else Future.successful(crowd) - }.flatMap(spectatorsOf(_, crowd.users)).map(ClientIn.Crowd.apply) + }.flatMap(spectatorsOf(_, crowd.users)).map(ClientIn.Crowd(_, crowd.members, crowd.users.toSet)) def round(crowd: RoundCrowd.Output): Future[ClientIn.Crowd] = spectatorsOf( @@ -26,7 +26,9 @@ final class CrowdJson(inquirers: Inquirers, mongo: Mongo, lightUserApi: LightUse "white" -> (crowd.players.white > 0), "black" -> (crowd.players.black > 0), "watchers" -> spectators - ) + ), + crowd.room.members, + Set.empty ) }