lila/modules/fishnet/src/main/Analyser.scala

127 lines
4.5 KiB
Scala

package lila.fishnet
import org.joda.time.DateTime
import chess.format.Forsyth
import scala.concurrent.duration._
import lila.analyse.AnalysisRepo
import lila.game.{ Game, UciMemo }
final class Analyser(
repo: FishnetRepo,
analysisRepo: AnalysisRepo,
gameRepo: lila.game.GameRepo,
uciMemo: UciMemo,
evalCache: FishnetEvalCache,
limiter: Limiter
)(implicit
ec: scala.concurrent.ExecutionContext,
system: akka.actor.ActorSystem
) {
val maxPlies = 200
private val workQueue = new lila.hub.DuctSequencer(maxSize = 256, timeout = 5 seconds, "fishnetAnalyser")
def apply(game: Game, sender: Work.Sender): Fu[Boolean] =
(game.metadata.analysed ?? analysisRepo.exists(game.id)) flatMap {
case true => fuFalse
case _ if Game.isOldHorde(game) => fuFalse
case _ =>
limiter(sender, ignoreConcurrentCheck = false) flatMap { accepted =>
accepted ?? {
makeWork(game, sender) flatMap { work =>
workQueue {
repo getSimilarAnalysis work flatMap {
// already in progress, do nothing
case Some(similar) if similar.isAcquired => funit
// queued by system, reschedule for the human sender
case Some(similar) if similar.sender.system && !sender.system =>
repo.updateAnalysis(similar.copy(sender = sender))
// queued for someone else, do nothing
case Some(_) => funit
// first request, store
case _ =>
lila.mon.fishnet.analysis.requestCount("game").increment()
evalCache skipPositions work.game flatMap { skipPositions =>
lila.mon.fishnet.analysis.evalCacheHits.record(skipPositions.size)
repo addAnalysis work.copy(skipPositions = skipPositions)
}
}
}
}
} inject accepted
}
}
def apply(gameId: String, sender: Work.Sender): Fu[Boolean] =
gameRepo game gameId flatMap { _ ?? { apply(_, sender) } }
def study(req: lila.hub.actorApi.fishnet.StudyChapterRequest): Fu[Boolean] =
analysisRepo exists req.chapterId flatMap {
case true => fuFalse
case _ => {
import req._
val sender =
Work.Sender(req.userId.some, none, mod = false, system = lila.user.User isOfficial req.userId)
limiter(sender, ignoreConcurrentCheck = true) flatMap { accepted =>
if (!accepted) logger.info(s"Study request declined: ${req.studyId}/${req.chapterId} by $sender")
accepted ?? {
val work = makeWork(
game = Work.Game(
id = chapterId,
initialFen = initialFen,
studyId = studyId.some,
variant = variant,
moves = moves take maxPlies map (_.uci) mkString " "
),
// if black moves first, use 1 as startPly so the analysis doesn't get reversed
startPly = initialFen.map(_.value).flatMap(Forsyth.getColor).fold(0)(_.fold(0, 1)),
sender = sender
)
workQueue {
repo getSimilarAnalysis work flatMap {
_.isEmpty ?? {
lila.mon.fishnet.analysis.requestCount("study").increment()
evalCache skipPositions work.game flatMap { skipPositions =>
lila.mon.fishnet.analysis.evalCacheHits.record(skipPositions.size)
repo addAnalysis work.copy(skipPositions = skipPositions)
}
}
}
}
} inject accepted
}
}
}
private def makeWork(game: Game, sender: Work.Sender): Fu[Work.Analysis] =
gameRepo.initialFen(game) zip uciMemo.get(game) map {
case (initialFen, moves) =>
makeWork(
game = Work.Game(
id = game.id,
initialFen = initialFen,
studyId = none,
variant = game.variant,
moves = moves take maxPlies mkString " "
),
startPly = game.chess.startedAtTurn,
sender = sender
)
}
private def makeWork(game: Work.Game, startPly: Int, sender: Work.Sender): Work.Analysis =
Work.Analysis(
_id = Work.makeId,
sender = sender,
game = game,
startPly = startPly,
tries = 0,
lastTryByKey = none,
acquired = none,
skipPositions = Nil,
createdAt = DateTime.now
)
}