lila/modules/racer/src/main/RacerApi.scala

122 lines
3.7 KiB
Scala

package lila.racer
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext
import lila.memo.CacheApi
import lila.storm.StormSelector
import lila.user.{ User, UserRepo }
import lila.common.Bus
final class RacerApi(colls: RacerColls, selector: StormSelector, userRepo: UserRepo, cacheApi: CacheApi)(
implicit
ec: ExecutionContext,
system: akka.actor.ActorSystem
) {
import RacerRace.Id
private val store = cacheApi.notLoadingSync[RacerRace.Id, RacerRace](2048, "racer.race")(
_.expireAfterAccess(30 minutes).build()
)
def get(id: Id): Option[RacerRace] = store getIfPresent id
def playerId(sessionId: String, user: Option[User]) = user match {
case Some(u) => RacerPlayer.Id.User(u.id)
case None => RacerPlayer.Id.Anon(sessionId)
}
def createAndJoin(player: RacerPlayer.Id): Fu[RacerRace.Id] =
create(player, 10).map { id =>
join(id, player)
id
}
def create(player: RacerPlayer.Id, countdownSeconds: Int): Fu[RacerRace.Id] =
selector.apply map { puzzles =>
val race = RacerRace
.make(
owner = player,
puzzles = puzzles.grouped(2).flatMap(_.headOption).toList,
countdownSeconds = 5
)
store.put(race.id, race)
lila.mon.racer.race(lobby = race.isLobby).increment()
race.id
}
private val rematchQueue =
new lila.hub.AsyncActorSequencer(
maxSize = 32,
timeout = 20 seconds,
name = "racer.rematch"
)
def rematch(race: RacerRace, player: RacerPlayer.Id): Fu[RacerRace.Id] = race.rematch.flatMap(get) match {
case Some(found) if found.finished => rematch(found, player)
case Some(found) =>
join(found.id, player)
fuccess(found.id)
case None =>
rematchQueue {
createAndJoin(player) map { rematchId =>
save(race.copy(rematch = rematchId.some))
rematchId
}
}
}
def join(id: RacerRace.Id, player: RacerPlayer.Id): Option[RacerRace] =
get(id).flatMap(_ join player) map { r =>
val race = (r.isLobby ?? doStart(r)) | r
saveAndPublish(race)
race
}
private[racer] def manualStart(race: RacerRace): Unit = !race.isLobby ?? {
doStart(race) foreach saveAndPublish
}
private def doStart(race: RacerRace): Option[RacerRace] =
race.startCountdown.map { starting =>
system.scheduler.scheduleOnce(RacerRace.duration.seconds + race.countdownSeconds.seconds + 50.millis) {
finish(race.id)
}
starting
}
private def finish(id: RacerRace.Id): Unit =
get(id) foreach { race =>
lila.mon.racer.players(lobby = race.isLobby).record(race.players.size)
race.players foreach { player =>
lila.mon.racer.score(lobby = race.isLobby, auth = player.userId.isDefined).record(player.score)
player.userId.ifTrue(player.score > 0) foreach { userId =>
Bus.publish(lila.hub.actorApi.puzzle.RacerRun(userId, player.score), "racerRun")
userRepo.addRacerRun(userId, player.score)
}
}
publish(race)
}
def registerPlayerScore(id: RacerRace.Id, player: RacerPlayer.Id, score: Int): Unit = {
if (score >= 130) logger.warn(s"$id $player score: $score")
else get(id).flatMap(_.registerScore(player, score)) foreach saveAndPublish
}
private def save(race: RacerRace): Unit =
store.put(race.id, race)
private def saveAndPublish(race: RacerRace): Unit = {
save(race)
publish(race)
}
private def publish(race: RacerRace): Unit = {
socket.foreach(_ publishState race)
}
// work around circular dependency
private var socket: Option[RacerSocket] = None
private[racer] def registerSocket(s: RacerSocket) = { socket = s.some }
}