tournament socket WIP

pull/5590/head
Thibault Duplessis 2019-10-18 21:45:47 +02:00
parent 60fdd8898b
commit d774f0e8a7
11 changed files with 178 additions and 106 deletions

View File

@ -144,6 +144,10 @@ lazy val chat = module("chat", Seq(common, db, user, security, i18n, socket)).se
libraryDependencies ++= provided(play.api, scalatags, reactivemongo.driver)
)
lazy val room = module("room", Seq(common, socket, chat)).settings(
libraryDependencies ++= provided(play.api, reactivemongo.driver, lettuce)
)
lazy val timeline = module("timeline", Seq(common, db, game, user, hub, security, relation)).settings(
libraryDependencies ++= provided(play.api, play.test, reactivemongo.driver)
)
@ -228,15 +232,15 @@ lazy val insight = module(
)
lazy val tournament = module("tournament", Seq(
common, hub, socket, game, round, security, chat, memo, quote, history, notifyModule, i18n
common, hub, socket, game, round, security, chat, memo, quote, history, notifyModule, i18n, room
)).settings(
libraryDependencies ++= provided(
play.api, scalatags, reactivemongo.driver, reactivemongo.iteratees
play.api, scalatags, reactivemongo.driver, reactivemongo.iteratees, lettuce
)
)
lazy val simul = module("simul", Seq(
common, hub, socket, game, round, chat, memo, quote
common, hub, socket, game, round, chat, memo, quote, room
)).settings(
libraryDependencies ++= provided(play.api, reactivemongo.driver, lettuce)
)

View File

@ -126,7 +126,7 @@ final class ChatApi(
coll.update($id(chat.id), chat).void >>
chatTimeout.add(c, mod, user, reason) >>- {
cached invalidate chat.id
publish(chat.id, actorApi.OnTimeout(chat.id, user.username))
publish(chat.id, actorApi.OnTimeout(user.username))
publish(chat.id, actorApi.ChatLine(chat.id, line))
if (isMod(mod)) modLog ! lila.hub.actorApi.mod.ChatTimeout(
mod = mod.id, user = user.id, reason = reason.key
@ -139,20 +139,14 @@ final class ChatApi(
val chat = c.markDeleted(user)
coll.update($id(chat.id), chat).void >>- {
cached invalidate chat.id
publish(chat.id, actorApi.OnTimeout(chat.id, user.username))
publish(chat.id, actorApi.OnTimeout(user.username))
}
}
private def isMod(user: User) = lila.security.Granter(_.ChatTimeout)(user)
def reinstate(list: List[ChatTimeout.Reinstate]) = list.foreach { r =>
val chatId = Chat.Id(r.chat)
publish(chatId, actorApi.OnReinstate(chatId, r.user))
}
private def publish(chatId: Chat.Id, msg: Any): Unit = {
lilaBus.publish(msg, classify(chatId))
lilaBus.publish(msg, 'remoteSocketChat)
publish(Chat.Id(r.chat), actorApi.OnReinstate(r.user))
}
private[ChatApi] def makeLine(chatId: Chat.Id, userId: String, t1: String): Fu[Option[UserLine]] =
@ -186,7 +180,7 @@ final class ChatApi(
def write(chatId: Chat.Id, color: Color, text: String): Funit =
makeLine(chatId, color, text) ?? { line =>
pushLine(chatId, line) >>-
lilaBus.publish(actorApi.ChatLine(chatId, line), classify(chatId))
publish(chatId, actorApi.ChatLine(chatId, line))
}
private def makeLine(chatId: Chat.Id, color: Color, t1: String): Option[Line] =
@ -196,6 +190,9 @@ final class ChatApi(
}
}
private def publish(chatId: Chat.Id, msg: Any): Unit =
lilaBus.publish(msg, classify(chatId))
private[chat] def remove(chatId: Chat.Id) = coll.remove($id(chatId)).void
private[chat] def removeAll(chatIds: List[Chat.Id]) = coll.remove($inIds(chatIds)).void

View File

@ -41,13 +41,10 @@ object Socket {
def out(send: Send): Actor.Receive = {
case actorApi.ChatLine(_, line) => line match {
case line: UserLine => send("message", JsonView(line), line.troll)
case _ =>
}
case line: UserLine => send("message", JsonView(line), line.troll)
case actorApi.OnTimeout(_, username) => send("chat_timeout", JsString(username), false)
case actorApi.OnTimeout(username) => send("chat_timeout", JsString(username), false)
case actorApi.OnReinstate(_, userId) => send("chat_reinstate", JsString(userId), false)
case actorApi.OnReinstate(userId) => send("chat_reinstate", JsString(userId), false)
}
}

View File

@ -9,7 +9,7 @@ case class SystemTalk(chatId: Chat.Id, text: String)
case class ChatLine(chatId: Chat.Id, line: Line)
case class Timeout(chatId: Chat.Id, mod: String, userId: String, reason: ChatTimeout.Reason, local: Boolean)
case class OnTimeout(chatId: Chat.Id, username: String)
case class OnReinstate(chatId: Chat.Id, userId: String)
case class OnTimeout(username: String)
case class OnReinstate(userId: String)
case class Remove(chatId: Chat.Id)
case class RemoveAll(chatIds: List[Chat.Id])

View File

@ -0,0 +1,100 @@
package lila.room
import lila.chat.{ Chat, UserLine, actorApi => chatApi }
import lila.common.Bus
import lila.hub.actorApi.shutup.PublicSource
import lila.hub.{ Trouper, TrouperMap }
import lila.socket.RemoteSocket.{ Protocol => P, _ }
import lila.socket.Socket.{ makeMessage, GetVersion, SocketVersion }
import play.api.libs.json._
import scala.concurrent.duration._
object RoomSocket {
case class RoomId(value: String) extends AnyVal with StringValue
case class NotifyVersion[A: Writes](tpe: String, data: A, troll: Boolean = false) {
def msg = makeMessage(tpe, data)
}
final class RoomState(roomId: RoomId, send: Send, bus: Bus) extends Trouper {
private val chatId = Chat.Id(roomId.value)
private def chatClassifier = Chat classify chatId
private var version = SocketVersion(0)
val process: Trouper.Receive = {
case GetVersion(promise) => promise success version
case nv: NotifyVersion[_] =>
version = version.inc
send(Protocol.Out.tellVersion(roomId, nv.msg, version, nv.troll))
case chatApi.ChatLine(_, line: UserLine) =>
this ! NotifyVersion("message", lila.chat.JsonView(line), line.troll)
case chatApi.OnTimeout(username) =>
this ! NotifyVersion("chat_timeout", username, false)
case chatApi.OnReinstate(userId) =>
this ! NotifyVersion("chat_reinstate", userId, false)
}
override def stop() {
super.stop()
send(Protocol.Out.stop(roomId))
bus.unsubscribe(this, chatClassifier)
}
send(Protocol.Out.start(roomId))
bus.subscribe(this, chatClassifier)
}
def makeRoomMap(send: Send, bus: Bus) = new TrouperMap(
mkTrouper = roomId => new RoomState(RoomId(roomId), send, bus),
accessTimeout = 5 minutes
)
def roomHandler(
rooms: TrouperMap[RoomState],
chat: akka.actor.ActorSelection,
publicSource: RoomId => Option[PublicSource]
): Handler = {
case Protocol.In.ChatSay(roomId, userId, msg) =>
chat ! lila.chat.actorApi.UserTalk(Chat.Id(roomId.value), userId, msg, publicSource(roomId))
case Protocol.In.ChatTimeout(roomId, modId, suspect, reason) => lila.chat.ChatTimeout.Reason(reason) foreach { r =>
chat ! lila.chat.actorApi.Timeout(Chat.Id(roomId.value), modId, suspect, r, local = false)
}
case Protocol.In.KeepAlive(roomId) => rooms touch roomId.value
case P.In.DisconnectAll => rooms.killAll
}
object Protocol {
object In {
// room
case class ChatSay(roomId: RoomId, userId: String, msg: String) extends P.In
case class ChatTimeout(roomId: RoomId, userId: String, suspect: String, reason: String) extends P.In
case class KeepAlive(roomId: RoomId) extends P.In
val reader: P.In.Reader = raw => raw.path match {
case "room/alive" => KeepAlive(RoomId(raw.args)).some
case "chat/say" => raw.args.split(" ", 3) match {
case Array(roomId, userId, msg) => ChatSay(RoomId(roomId), userId, msg).some
case _ => none
}
case "chat/timeout" => raw.args.split(" ", 4) match {
case Array(roomId, userId, suspect, reason) => ChatTimeout(RoomId(roomId), userId, suspect, reason).some
case _ => none
}
case _ => none
}
}
object Out {
def tellVersion(roomId: RoomId, payload: JsObject, version: SocketVersion, isTroll: Boolean) =
s"tell/version $roomId $version $isTroll ${Json stringify payload}"
def tellRoomUser(roomId: RoomId, userId: String, payload: JsObject) =
s"tell/room/user $roomId $userId ${Json stringify payload}"
def start(roomId: RoomId) =
s"room/start $roomId"
def stop(roomId: RoomId) =
s"room/stop $roomId"
}
}
}

View File

@ -0,0 +1,3 @@
package lila
package object room extends PackageObject

View File

@ -7,6 +7,7 @@ import scala.concurrent.duration._
import lila.game.Game
import lila.hub.{ Duct, DuctMap }
import lila.socket.History
import lila.socket.Socket.{ GetVersion, SocketVersion }
final class Env(
config: Config,
@ -98,7 +99,8 @@ final class Env(
log = false
)
def version(simulId: String) = simulSocket versionOf simulId
def version(simulId: Simul.ID) =
simulSocket.rooms.ask[SocketVersion](simulId)(GetVersion)
private[simul] val simulColl = db(CollectionSimul)

View File

@ -5,13 +5,10 @@ import play.api.libs.json._
import scala.concurrent.duration._
import actorApi._
import lila.chat.ChatTimeout.Reason
import lila.chat.{ Chat, UserLine, actorApi => chatApi }
import lila.game.{ Game, Pov }
import lila.hub.{ TrouperMap, Trouper }
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.RemoteSocket.{ Protocol => P, _ }
import lila.socket.Socket.{ makeMessage, GetVersion, SocketVersion }
import lila.user.User
import lila.socket.Socket.makeMessage
private final class SimulSocket(
getSimul: Simul.ID => Fu[Option[Simul]],
@ -21,43 +18,20 @@ private final class SimulSocket(
system: ActorSystem
) {
private final class SimulState(roomId: P.RoomId) extends Trouper {
private var version = SocketVersion(0)
val process: Trouper.Receive = {
case GetVersion(promise) => promise success version
case nv: P.NotifyVersion[_] =>
version = version.inc
send(P.Out.tellVersion(roomId, nv.msg, version, nv.troll))
}
override def stop() {
super.stop()
send(P.Out.stop(roomId))
}
send(P.Out.start(roomId))
}
private val sockets = new TrouperMap(
mkTrouper = simulId => new SimulState(P.RoomId(simulId)),
accessTimeout = 5 minutes
)
def versionOf(simulId: Simul.ID): Fu[SocketVersion] =
sockets.ask[SocketVersion](simulId)(GetVersion)
def hostIsOn(simulId: Simul.ID, gameId: Game.ID): Unit =
sockets.tell(simulId, P.NotifyVersion("hostGame", gameId))
rooms.tell(simulId, NotifyVersion("hostGame", gameId))
def reload(simulId: Simul.ID): Unit =
getSimul(simulId) foreach {
_ foreach { simul =>
jsonView(simul, none) foreach { obj =>
sockets.tell(simulId, P.NotifyVersion("reload", obj))
rooms.tell(simulId, NotifyVersion("reload", obj))
}
}
}
def aborted(simulId: Simul.ID): Unit =
sockets.tell(simulId, P.NotifyVersion("aborted", Json.obj()))
rooms.tell(simulId, NotifyVersion("aborted", Json.obj()))
def startSimul(simul: Simul, firstGame: Game): Unit =
firstGame.playerByUserId(simul.hostId) foreach { player =>
@ -71,31 +45,15 @@ private final class SimulSocket(
private def redirectPlayer(simul: Simul, pov: Pov): Unit =
pov.player.userId foreach { userId =>
send(P.Out.tellRoomUser(P.RoomId(simul.id), userId, makeMessage("redirect", pov.fullId)))
send(RP.Out.tellRoomUser(RoomId(simul.id), userId, makeMessage("redirect", pov.fullId)))
}
private val handler: Handler = {
case P.In.ChatSay(roomId, userId, msg) =>
val publicSource = lila.hub.actorApi.shutup.PublicSource.Simul(roomId.value).some
chat ! lila.chat.actorApi.UserTalk(Chat.Id(roomId.value), userId, msg, publicSource)
case P.In.ChatTimeout(roomId, modId, suspect, reason) => lila.chat.ChatTimeout.Reason(reason) foreach { r =>
chat ! lila.chat.actorApi.Timeout(Chat.Id(roomId.value), modId, suspect, r, local = false)
}
case P.In.DisconnectAll =>
sockets.killAll
}
lazy val rooms = makeRoomMap(send, system.lilaBus)
remoteSocketApi.subscribe("simul-in", P.In.baseReader)(handler orElse remoteSocketApi.baseHandler)
private lazy val handler: Handler = roomHandler(rooms, chat,
roomId => lila.hub.actorApi.shutup.PublicSource.Simul(roomId.value).some)
private lazy val send: String => Unit = remoteSocketApi.makeSender("simul-out").apply _
system.lilaBus.subscribeFun('remoteSocketChat) {
case chatApi.ChatLine(chatId, line: UserLine) =>
sockets.tell(chatId.value, P.NotifyVersion("message", lila.chat.JsonView(line), line.troll))
case chatApi.OnTimeout(chatId, username) =>
sockets.tell(chatId.value, P.NotifyVersion("chat_timeout", username, false))
case chatApi.OnReinstate(chatId, userId) =>
sockets.tell(chatId.value, P.NotifyVersion("chat_reinstate", userId, false))
case a => println(s"remote socket chat unhandled $a")
}
remoteSocketApi.subscribe("simul-in", P.In.baseReader)(handler orElse remoteSocketApi.baseHandler)
}

View File

@ -5,17 +5,19 @@ import io.lettuce.core._
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection
import java.util.concurrent.atomic.AtomicReference
import play.api.libs.json._
import scala.concurrent.duration._
import scala.concurrent.Future
import lila.common.Chronometer
import lila.common.{ Bus, Chronometer }
import lila.hub.actorApi.relation.ReloadOnlineFriends
import lila.hub.actorApi.round.{ MoveEvent, Mlat }
import lila.hub.actorApi.security.CloseAccount
import lila.hub.actorApi.socket.remote.{ TellSriIn, TellSriOut }
import lila.hub.actorApi.socket.{ SendTo, SendTos, WithUserIds }
import lila.hub.actorApi.{ Deploy, Announce }
import lila.hub.{ TrouperMap, Trouper }
import lila.socket.actorApi.SendToFlag
import Socket.Sri
import Socket.{ SocketVersion, GetVersion, Sri }
final class RemoteSocket(
redisClient: RedisClient,
@ -92,7 +94,7 @@ final class RemoteSocket(
def makeSender(channel: Channel): Sender = new Sender(connectToPubSub, channel)
private val send: String => Unit = makeSender("site-out").apply _
private val send: Send = makeSender("site-out").apply _
def subscribe(channel: Channel, reader: In.Reader)(handler: Handler): Unit = {
val conn = connectToPubSub
@ -121,6 +123,8 @@ final class RemoteSocket(
object RemoteSocket {
type Send = String => Unit
final class Sender(conn: StatefulRedisPubSubConnection[String, String], channel: Channel) {
private val mon = lila.mon.socket.remote
@ -140,12 +144,6 @@ object RemoteSocket {
object Protocol {
case class RoomId(value: String) extends AnyVal with StringValue
case class NotifyVersion[A: Writes](tpe: String, data: A, troll: Boolean = false) {
def msg = Socket.makeMessage(tpe, data)
}
case class RawMsg(path: Path, args: Args)
def RawMsg(msg: String): RawMsg = {
val parts = msg.split(" ", 2)
@ -173,10 +171,6 @@ object RemoteSocket {
case class FriendsBatch(userIds: Iterable[String]) extends In
case class TellSri(sri: Sri, userId: Option[String], typ: String, msg: JsObject) extends In
// room
case class ChatSay(roomId: RoomId, userId: String, msg: String) extends In
case class ChatTimeout(roomId: RoomId, userId: String, suspect: String, reason: String) extends In
val baseReader: Reader = raw => raw.path match {
case "connect/user" => ConnectUser(raw.args).some
case "disconnect/users" => DisconnectUsers(raw.args split ',').some
@ -209,14 +203,6 @@ object RemoteSocket {
} yield TellSri(Sri(sri), userId, typ, obj)
case _ => none
}
case "chat/say" => raw.args.split(" ", 3) match {
case Array(roomId, userId, msg) => ChatSay(RoomId(roomId), userId, msg).some
case _ => none
}
case "chat/timeout" => raw.args.split(" ", 4) match {
case Array(roomId, userId, suspect, reason) => ChatTimeout(RoomId(roomId), userId, suspect, reason).some
case _ => none
}
case _ => none
}
}
@ -241,16 +227,6 @@ object RemoteSocket {
def disconnectUser(userId: String) =
s"disconnect/user $userId"
// room
def tellVersion(roomId: RoomId, payload: JsObject, version: Socket.SocketVersion, isTroll: Boolean) =
s"tell/version $roomId $version $isTroll ${Json stringify payload}"
def tellRoomUser(roomId: RoomId, userId: String, payload: JsObject) =
s"tell/room/user $roomId $userId ${Json stringify payload}"
def start(roomId: RoomId) =
s"room/start $roomId"
def stop(roomId: RoomId) =
s"room/stop $roomId"
def commaList(strs: Iterable[Any]) = if (strs.isEmpty) "-" else strs mkString ","
}
}

View File

@ -5,8 +5,8 @@ import com.typesafe.config.Config
import scala.concurrent.duration._
import scala.concurrent.Promise
import lila.hub.{ Duct, DuctMap, TrouperMap }
import lila.game.Game
import lila.hub.{ Duct, DuctMap, TrouperMap }
import lila.socket.History
import lila.socket.Socket.{ GetVersion, SocketVersion }
import lila.user.User
@ -28,6 +28,7 @@ final class Env(
historyApi: lila.history.HistoryApi,
trophyApi: lila.user.TrophyApi,
notifyApi: lila.notify.NotifyApi,
remoteSocketApi: lila.socket.RemoteSocket,
scheduler: lila.common.Scheduler,
startedSinceSeconds: Int => Boolean
) {
@ -156,6 +157,12 @@ final class Env(
broomFrequency = 3701 millis
)
private val tournamentSocket = new RemoteTournamentSocket(
remoteSocketApi = remoteSocketApi,
chat = hub.chat,
system = system
)
private val sequencerMap = new DuctMap(
mkDuct = _ => Duct.extra.lazyFu(5.seconds)(system),
accessTimeout = SequencerTimeout
@ -224,6 +231,7 @@ object Env {
historyApi = lila.history.Env.current.api,
trophyApi = lila.user.Env.current.trophyApi,
notifyApi = lila.notify.Env.current.api,
remoteSocketApi = lila.socket.Env.current.remoteSocket,
scheduler = lila.common.PlayApp.scheduler,
startedSinceSeconds = lila.common.PlayApp.startedSinceSeconds
)

View File

@ -0,0 +1,27 @@
package lila.tournament
import akka.actor._
import play.api.libs.json._
import scala.concurrent.duration._
import actorApi._
import lila.game.{ Game, Pov }
import lila.room.RoomSocket.{ Protocol => RP, _ }
import lila.socket.RemoteSocket.{ Protocol => P, _ }
import lila.socket.Socket.makeMessage
private final class RemoteTournamentSocket(
remoteSocketApi: lila.socket.RemoteSocket,
chat: ActorSelection,
system: ActorSystem
) {
lazy val rooms = makeRoomMap(send, system.lilaBus)
private lazy val handler: Handler = roomHandler(rooms, chat,
roomId => lila.hub.actorApi.shutup.PublicSource.Tournament(roomId.value).some)
private lazy val send: String => Unit = remoteSocketApi.makeSender("tour-out").apply _
remoteSocketApi.subscribe("tour-in", P.In.baseReader)(handler orElse remoteSocketApi.baseHandler)
}