round remote socket WIP

round-remote-socket
Thibault Duplessis 2019-10-31 19:04:37 +01:00
parent 4d9b5bfa48
commit fdc746cd11
13 changed files with 79 additions and 50 deletions

View File

@ -620,7 +620,7 @@ object Game {
val gameIdSize = 8
val playerIdSize = 4
val fullIdSize = gameIdSize + playerIdSize
val fullIdSize = 12
val tokenSize = 4
val unplayedHours = 24
@ -635,7 +635,7 @@ object Game {
def takeGameId(fullId: String) = fullId take gameIdSize
def takePlayerId(fullId: String) = fullId drop gameIdSize
val idRegex = s"""[\w-]{$gameIdSize}""".r
val idRegex = """[\w-]{8}""".r
def validId(id: ID) = idRegex matches id
private[game] val emptyCheckCount = CheckCount(0, 0)

View File

@ -6,7 +6,7 @@ import scala.collection.JavaConverters._
import scala.concurrent.duration.FiniteDuration
import java.util.concurrent.TimeUnit
final class DuctMap[D <: Duct](
final class DuctMap[+D <: Duct](
mkDuct: String => D,
accessTimeout: FiniteDuration
) {

View File

@ -105,7 +105,7 @@ object RoomSocket {
def tellRoom(roomId: RoomId, payload: JsObject) =
s"tell/room $roomId ${Json stringify payload}"
def tellRoomVersion(roomId: RoomId, payload: JsObject, version: SocketVersion, isTroll: Boolean) =
s"tell/room/version $roomId $version ${P.Out.bool(isTroll)} ${Json stringify payload}"
s"tell/room/version $roomId $version ${P.Out.boolean(isTroll)} ${Json stringify payload}"
def tellRoomUser(roomId: RoomId, userId: User.ID, payload: JsObject) =
s"tell/room/user $roomId $userId ${Json stringify payload}"
def tellRoomUsers(roomId: RoomId, userIds: Iterable[User.ID], payload: JsObject) =

View File

@ -75,7 +75,7 @@ final class Env(
forecastApi = forecastApi,
socketMap = socketMap
)
val roundMap = new lila.hub.DuctMap[RoundDuct](
private val roundMap = new lila.hub.DuctMap[RoundDuct](
mkDuct = id => {
val duct = new RoundDuct(
dependencies = roundDependencies,
@ -93,7 +93,7 @@ final class Env(
game.whitePlayer.userId.fold(defaultGoneWeight)(goneWeight) zip
game.blackPlayer.userId.fold(defaultGoneWeight)(goneWeight)
val roundSocket = new RoundRemoteSocket(
lazy val roundSocket = new RoundRemoteSocket(
remoteSocketApi = remoteSocketApi,
roundDependencies = RoundRemoteDuct.Dependencies(
messenger = messenger,
@ -116,7 +116,7 @@ final class Env(
bus.subscribeFuns(
'roundMapTell -> {
case Tell(id, msg) => selectRoundMap(id).tell(id, msg)
case Tell(id, msg) => tellRound(id, msg)
},
'roundMapTellAll -> {
case msg =>
@ -129,7 +129,7 @@ final class Env(
'accountClose -> {
case lila.hub.actorApi.security.CloseAccount(userId) => GameRepo.allPlaying(userId) map {
_ foreach { pov =>
selectRoundMap(pov.gameId).tell(pov.gameId, Resign(pov.playerId))
tellRound(pov.gameId, Resign(pov.playerId))
}
}
},
@ -153,8 +153,8 @@ final class Env(
text = "Remote socket game ID regex".some
)
def useRemoteSocket(gameId: Game.ID) = remoteSocketSetting.get().matches(gameId)
def selectRoundMap(gameId: Game.ID) =
if (useRemoteSocket(gameId)) roundSocket.rounds else roundMap
def selectRoundMap(gameId: Game.ID) = if (useRemoteSocket(gameId)) roundSocket.rounds else roundMap
def tellRound(gameId: Game.ID, msg: Any): Unit = selectRoundMap(gameId).tell(gameId, msg)
object proxy {
@ -205,7 +205,7 @@ final class Env(
private def scheduleExpiration(game: Game): Unit = game.timeBeforeExpiration foreach { centis =>
system.scheduler.scheduleOnce((centis.millis + 1000).millis) {
selectRoundMap(game.id).tell(game.id, actorApi.round.NoStart)
tellRound(game.id, actorApi.round.NoStart)
}
}
@ -221,7 +221,7 @@ final class Env(
playban = playban
)
lazy val selfReport = new SelfReport(roundMap, slackApi, proxy.pov)
lazy val selfReport = new SelfReport(tellRound, slackApi, proxy.pov)
lazy val recentTvGames = new {
val fast = new lila.memo.ExpireSetMemo(7 minutes)
@ -250,7 +250,7 @@ final class Env(
lazy val forecastApi: ForecastApi = new ForecastApi(
coll = db(CollectionForecast),
roundMap = roundMap
tellRound = tellRound
)
private lazy val notifier = new RoundNotifier(
@ -333,7 +333,7 @@ final class Env(
MoveMonitor.start(system, moveTimeChannel)
system.actorOf(
Props(new Titivate(roundMap, hub.bookmark, hub.chat)),
Props(new Titivate(tellRound, hub.bookmark, hub.chat)),
name = "titivate"
)
@ -355,12 +355,12 @@ final class Env(
def checkOutoftime(game: Game): Unit = {
if (game.playable && game.started && !game.isUnlimited)
roundMap.tell(game.id, actorApi.round.QuietFlag)
tellRound(game.id, actorApi.round.QuietFlag)
}
def resign(pov: Pov): Unit =
if (pov.game.abortable) roundMap.tell(pov.gameId, Abort(pov.playerId))
else if (pov.game.resignable) roundMap.tell(pov.gameId, Resign(pov.playerId))
if (pov.game.abortable) tellRound(pov.gameId, Abort(pov.playerId))
else if (pov.game.resignable) tellRound(pov.gameId, Resign(pov.playerId))
}
object Env {

View File

@ -13,7 +13,7 @@ import Forecast.Step
import lila.game.{ Pov, Game }
import lila.hub.DuctMap
final class ForecastApi(coll: Coll, roundMap: DuctMap[RoundDuct]) {
final class ForecastApi(coll: Coll, tellRound: TellRound) {
private implicit val PosBSONHandler = new BSONHandler[BSONString, Pos] {
def read(bsonStr: BSONString): Pos = Pos.posAt(bsonStr.value) err s"No such pos: ${bsonStr.value}"
@ -50,7 +50,7 @@ final class ForecastApi(coll: Coll, roundMap: DuctMap[RoundDuct]) {
if (!pov.isMyTurn) funit
else Uci.Move(uciMove).fold[Funit](fufail(s"Invalid move $uciMove on $pov")) { uci =>
val promise = Promise[Unit]
roundMap.tell(pov.gameId, actorApi.round.HumanPlay(
tellRound(pov.gameId, actorApi.round.HumanPlay(
playerId = pov.playerId,
uci = uci,
blur = true,

View File

@ -135,25 +135,30 @@ private[round] final class RoundRemoteDuct(
blackPlayer.goneWeight = blackGoneWeight
}
case lila.chat.actorApi.ChatLine(chatId, line) => fuccess {
publish(List(line match {
case l: lila.chat.UserLine => Event.UserMessage(l, chatId == chatIds.pub)
case l: lila.chat.PlayerLine => Event.PlayerMessage(l)
}))
}
// round stuff
case p: HumanPlay =>
handleHumanPlay(p) { pov =>
if (pov.game.outoftime(withGrace = true)) finisher.outOfTime(pov.game)
else {
recordLag(pov)
player.human(p, this)(pov)
}
} >>- {
p.trace.finish()
lila.mon.round.move.full.count()
case p: HumanPlay => handleHumanPlay(p) { pov =>
if (pov.game.outoftime(withGrace = true)) finisher.outOfTime(pov.game)
else {
recordLag(pov)
player.human(p, this)(pov)
}
} >>- {
p.trace.finish()
lila.mon.round.move.full.count()
}
case p: BotPlay =>
handleBotPlay(p) { pov =>
if (pov.game.outoftime(withGrace = true)) finisher.outOfTime(pov.game)
else player.bot(p, this)(pov)
}
case p: BotPlay => handleBotPlay(p) { pov =>
if (pov.game.outoftime(withGrace = true)) finisher.outOfTime(pov.game)
else player.bot(p, this)(pov)
}
case FishnetPlay(uci, ply) => handle { game =>
player.fishnet(game, ply, uci, this)

View File

@ -2,10 +2,12 @@ package lila.round
import play.api.libs.json._
import scala.concurrent.duration._
import scala.concurrent.Promise
import actorApi._
import actorApi.round._
import chess.{ Color, White, Black, Speed }
import chess.format.Uci
import chess.{ Color, White, Black, Speed, Centis, MoveMetrics }
import lila.chat.Chat
import lila.common.Bus
import lila.game.{ Game, Event }
@ -64,6 +66,11 @@ final class RoundRemoteSocket(
case "moretime" => tellRound(fullId.gameId, Moretime(fullId.playerId.value))
case t => logger.warn(s"Unhandled round socket message: $t")
}
case Protocol.In.PlayerMove(fullId, uci, blur, lag) =>
// TODO remove promise, resync from remote round duct
val promise = Promise[Unit]
promise.future onFailure { case _: Exception => send(Protocol.Out.resyncPlayer(fullId)) }
tellRound(fullId.gameId, HumanPlay(fullId.playerId.value, uci, blur, lag, promise.some))
case ping: Protocol.In.PlayerPing => tellRound(ping.gameId, ping)
case RP.In.KeepAlives(roomIds) => roomIds foreach { roomId =>
rounds touchOrMake roomId.value
@ -94,6 +101,7 @@ object RoundRemoteSocket {
case class PlayerPing(gameId: GameId, color: Color) extends P.In
case class PlayerDo(fullId: FullId, tpe: String, msg: JsObject) extends P.In
case class PlayerMove(fullId: FullId, uci: Uci, blur: Boolean, lag: MoveMetrics) extends P.In
val reader: P.In.Reader = raw => raw.path match {
case "round/w" => PlayerPing(GameId(raw.args), chess.White).some
@ -104,8 +112,22 @@ object RoundRemoteSocket {
tpe <- obj str "t"
} yield PlayerDo(FullId(fullId), tpe, obj)
}
case "round/move" => raw.get(5) {
case Array(fullId, uciS, blurS, lagS, mtS) => Uci(uciS) map { uci =>
PlayerMove(FullId(fullId), uci, P.In.boolean(blurS), MoveMetrics(centis(lagS), centis(mtS)))
}
}
case _ => RP.In.reader(raw)
}
private def centis(s: String): Option[Centis] =
if (s == "-") none
else parseIntOption(s) map Centis.apply
}
object Out {
def resyncPlayer(fullId: FullId) = s"round/resync/player $fullId"
}
}
}

View File

@ -3,12 +3,12 @@ package lila.round
import scala.concurrent.duration._
import lila.common.IpAddress
import lila.game.{ Game, Pov }
import lila.hub.DuctMap
import lila.game.Pov
import lila.user.{ User, UserRepo }
final class SelfReport(
roundMap: DuctMap[RoundDuct],
tellRound: TellRound,
slackApi: lila.slack.SlackApi,
proxyPov: String => Fu[Option[Pov]]
) {
@ -55,7 +55,7 @@ final class SelfReport(
_ ?? { pov =>
if (!known) doLog
if (Set("ceval", "rcb", "ccs")(name)) fuccess {
roundMap.tell(
tellRound(
pov.gameId,
lila.round.actorApi.round.Cheat(pov.color)
)

View File

@ -16,7 +16,7 @@ import lila.round.actorApi.round.{ QuietFlag, Abandon }
* and flagged games when no one is around
*/
private[round] final class Titivate(
roundMap: DuctMap[RoundDuct],
tellRound: TellRound,
bookmark: ActorSelection,
chat: ActorSelection
) extends Actor {
@ -50,11 +50,11 @@ private[round] final class Titivate(
GameRepo unsetCheckAt game
else if (game.outoftime(withGrace = true)) fuccess {
roundMap.tell(game.id, QuietFlag)
tellRound(game.id, QuietFlag)
}
else if (game.abandoned) fuccess {
roundMap.tell(game.id, Abandon)
tellRound(game.id, Abandon)
}
else if (game.unplayed) {

View File

@ -10,6 +10,8 @@ package object round extends PackageObject with WithSocket {
private[round] type Events = List[Event]
private[round] def logger = lila.log("round")
type TellRound = (lila.game.Game.ID, Any) => Unit
}
package round {

View File

@ -227,8 +227,8 @@ object RemoteSocket {
} yield TellSri(Sri(sri), userId, typ, obj)
}
def commas(str: String): Array[String] =
if (str == "-") Array.empty else str split ','
def commas(str: String): Array[String] = if (str == "-") Array.empty else str split ','
def boolean(str: String): Boolean = str == "+"
}
object Out {
@ -252,7 +252,7 @@ object RemoteSocket {
s"disconnect/user $userId"
def commas(strs: Iterable[Any]): String = if (strs.isEmpty) "-" else strs mkString ","
def bool(v: Boolean): String = if (v) "true" else "-"
def boolean(v: Boolean): String = if (v) "+" else "-"
}
}

View File

@ -21,7 +21,7 @@ final class Env(
proxyGame: Game.ID => Fu[Option[Game]],
flood: lila.security.Flood,
hub: lila.hub.Env,
roundMap: DuctMap[_],
tellRound: lila.round.TellRound,
lightUserApi: lila.user.LightUserApi,
isOnline: User.ID => Boolean,
onStart: String => Unit,
@ -107,7 +107,7 @@ final class Env(
trophyApi = trophyApi,
verify = verify,
indexLeaderboard = leaderboardIndexer.indexOne _,
roundMap = roundMap,
tellRound = tellRound,
asyncCache = asyncCache,
duelStore = duelStore,
pause = pause,
@ -193,7 +193,7 @@ object Env {
proxyGame = lila.round.Env.current.proxy.game _,
flood = lila.security.Env.current.flood,
hub = lila.hub.Env.current,
roundMap = lila.round.Env.current.roundMap,
tellRound = lila.round.Env.current.tellRound,
lightUserApi = lila.user.Env.current.lightUserApi,
isOnline = lila.user.Env.current.isOnline,
onStart = lila.round.Env.current.onStart,

View File

@ -32,7 +32,7 @@ final class TournamentApi(
renderer: ActorSelection,
timeline: ActorSelection,
socket: TournamentSocket,
roundMap: lila.hub.DuctMap[_],
tellRound: lila.round.TellRound,
trophyApi: lila.user.TrophyApi,
verify: Condition.Verify,
indexLeaderboard: Tournament => Funit,
@ -329,7 +329,7 @@ final class TournamentApi(
case Some(pairing) if !pairing.berserkOf(userId) =>
(pairing colorOf userId) ?? { color =>
PairingRepo.setBerserk(pairing, userId) >>-
roundMap.tell(gameId, GoBerserk(color))
tellRound(gameId, GoBerserk(color))
}
case _ => funit
}
@ -418,7 +418,7 @@ final class TournamentApi(
if (tour.isStarted)
PairingRepo.findPlaying(tour.id, userId).map {
_ foreach { currentPairing =>
roundMap.tell(currentPairing.gameId, AbortForce)
tellRound(currentPairing.gameId, AbortForce)
}
} >> PairingRepo.opponentsOf(tour.id, userId).flatMap { uids =>
PairingRepo.removeByTourAndUserId(tour.id, userId) >>