cache UCI data to speed up AI

This commit is contained in:
Thibault Duplessis 2013-10-02 10:17:31 +02:00
parent 3087cd05d4
commit 4e6b45dc90
13 changed files with 97 additions and 41 deletions

View file

@ -13,7 +13,7 @@ object Ai extends LilaController {
def playStockfish = Action.async { req
IfServer {
stockfishServer.move(
pgn = ~get("pgn", req),
uciMoves = ~get("uciMoves", req),
initialFen = get("initialFen", req),
level = getInt("level", req) | 1
) fold (

View file

@ -1,10 +1,10 @@
package lila.ai
import chess.format.UciMove
import chess.format.{ UciMove, UciDump }
import chess.Move
import lila.analyse.AnalysisMaker
import lila.game.{ Game, Progress, GameRepo, PgnRepo }
import lila.game.{ Game, Progress, GameRepo, PgnRepo, UciMemo }
trait Ai {
@ -12,18 +12,21 @@ trait Ai {
for {
fen game.variant.exotic ?? { GameRepo initialFen game.id }
pgn PgnRepo get game.id
moveStr move(pgn, fen, level)
result (for {
uciMove UciMove(moveStr) toValid "Wrong bestmove: " + moveStr
result (game.toChess withPgnMoves pgn)(uciMove.orig, uciMove.dest)
} yield result).future
uciMoves uciMemo.get(game) map (_ mkString " ")
moveStr move(uciMoves, fen, level)
uciMove (UciMove(moveStr) toValid "Wrong bestmove: " + moveStr).future
result (game.toChess withPgnMoves pgn)(uciMove.orig, uciMove.dest).future
(c, m) = result
(progress, pgn2) = game.update(c, m)
_ (GameRepo save progress) >> PgnRepo.save(game.id, pgn2)
_ (GameRepo save progress) >>
PgnRepo.save(game.id, pgn2) >>-
uciMemo.add(game, uciMove.uci)
} yield progress
}
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String]
def uciMemo: UciMemo
def move(uciMoves: String, initialFen: Option[String], level: Int): Fu[String]
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker]

View file

@ -10,6 +10,7 @@ import lila.common.PimpedConfig._
final class Env(
config: Config,
uciMemo: lila.game.UciMemo,
system: ActorSystem) {
private val settings = new {
@ -70,11 +71,13 @@ final class Env(
scheduler = system.scheduler
)), name = "stockfish-dispatcher"),
fallback = stockfishServer,
config = stockfishConfig)
config = stockfishConfig,
uciMemo = uciMemo)
lazy val stockfishServer = new stockfish.Server(
queue = stockfishQueue,
config = stockfishConfig)
config = stockfishConfig,
uciMemo = uciMemo)
def nbStockfishRemotes = StockfishRemotes.size
@ -92,5 +95,6 @@ object Env {
lazy val current = "[boot] ai" describes new Env(
config = lila.common.PlayApp loadConfig "ai",
uciMemo = lila.game.Env.current.uciMemo,
system = lila.common.PlayApp.system)
}

View file

@ -17,16 +17,16 @@ import lila.hub.actorApi.ai.GetLoad
final class Client(
dispatcher: ActorRef,
fallback: lila.ai.Ai,
config: Config) extends lila.ai.Ai {
config: Config,
val uciMemo: lila.game.UciMemo) extends lila.ai.Ai {
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
def move(uciMoves: String, initialFen: Option[String], level: Int): Fu[String] = {
implicit val timeout = makeTimeout(config.playTimeout)
dispatcher ? Play(pgn, ~initialFen, level) mapTo manifest[String]
dispatcher ? Play(uciMoves, ~initialFen, level) mapTo manifest[String]
} recoverWith {
case e: Exception fallback.move( pgn, initialFen, level)
case e: Exception fallback.move(uciMoves, initialFen, level)
}
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] = {
implicit val timeout = makeTimeout(config.analyseTimeout)
dispatcher ? Analyse(pgn, ~initialFen) mapTo manifest[String] flatMap { str

View file

@ -5,30 +5,31 @@ import scala.concurrent.duration._
import akka.actor.{ Props, Actor, ActorRef, Kill }
import akka.pattern.{ ask, AskTimeoutException }
import chess.format.Forsyth
import chess.format.UciDump
import chess.Variant
import play.api.libs.concurrent.Akka.system
import play.api.Play.current
import actorApi._
import chess.format.Forsyth
import chess.format.UciDump
import chess.Variant.Chess960
import lila.analyse.AnalysisMaker
import lila.hub.actorApi.ai.GetLoad
private[ai] final class Server(queue: ActorRef, config: Config) extends lila.ai.Ai {
private[ai] final class Server(
queue: ActorRef,
config: Config,
val uciMemo: lila.game.UciMemo) extends lila.ai.Ai {
def move(pgn: String, initialFen: Option[String], level: Int): Fu[String] = {
def move(uciMoves: String, initialFen: Option[String], level: Int): Fu[String] = {
implicit val timeout = makeTimeout(config.playTimeout)
UciDump(pgn, initialFen, initialFen.isDefined option Chess960).future flatMap { moves
queue ? PlayReq(moves, initialFen map chess960Fen, level) mapTo
manifest[Valid[String]] flatMap (_.future)
}
queue ? PlayReq(uciMoves, initialFen map chess960Fen, level) mapTo
manifest[Valid[String]] flatMap (_.future)
}
def analyse(pgn: String, initialFen: Option[String]): Fu[AnalysisMaker] = {
implicit val timeout = makeTimeout(config.analyseTimeout)
UciDump(pgn, initialFen, initialFen.isDefined option Chess960).future flatMap { moves
queue ? FullAnalReq(moves, initialFen map chess960Fen) mapTo
UciDump(pgn, initialFen, initialFen.isDefined.fold(Variant.Chess960, Variant.Standard)).future flatMap { moves
queue ? FullAnalReq(moves mkString " ", initialFen map chess960Fen) mapTo
manifest[Valid[AnalysisMaker]] flatMap (_.future)
}
}

View file

@ -30,8 +30,8 @@ private[ai] final class Connection(
case Failure(e) load = none
}
}
case Play(pgn, fen, level) WS.url(router.play).withQueryString(
"pgn" -> pgn,
case Play(uciMoves, fen, level) WS.url(router.play).withQueryString(
"uciMoves" -> uciMoves,
"initialFen" -> fen,
"level" -> level.toString
).get() map (_.body) pipeTo sender

View file

@ -4,5 +4,5 @@ import lila.game.Game
case object CalculateLoad
case class Play(pgn: String, fen: String, level: Int)
case class Play(uciMoves: String, fen: String, level: Int)
case class Analyse(pgn: String, fen: String)

@ -1 +1 @@
Subproject commit 89cdb68fd8a6617de5ef8395fc3dc5937e2d00b2
Subproject commit 01ffb2ba6fea914327e69364e0730157047237cc

View file

@ -26,6 +26,7 @@ final class Env(
val ActorName = config getString "actor.name"
val FeaturedContinue = config duration "featured.continue"
val FeaturedDisrupt = config duration "featured.disrupt"
val UciMemoTtl = config duration "uci_memo.ttl"
}
import settings._
@ -53,6 +54,8 @@ final class Env(
lazy val gameJs = new GameJs(path = jsPath, useCache = isProd)
lazy val uciMemo = new UciMemo(UciMemoTtl)
// load captcher actor
private val captcher = system.actorOf(Props(new Captcher), name = CaptcherName)

View file

@ -0,0 +1,39 @@
package lila.game
import scala.concurrent.duration._
import chess.format.UciDump
final class UciMemo(ttl: Duration) {
private val memo = lila.memo.Builder.expiry[String, Vector[String]](ttl)
def add(game: Game, uciMove: String) {
val current = Option(memo getIfPresent game.id) | Vector.empty
memo.put(game.id, current :+ uciMove)
}
def add(game: Game, move: chess.Move) {
add(game, UciDump.move(game.variant)(move))
}
def set(game: Game, uciMoves: Seq[String]) {
memo.put(game.id, uciMoves.toVector)
}
def get(game: Game): Fu[Vector[String]] =
Option(memo getIfPresent game.id).filter(_.size == game.turns) match {
case Some(moves) fuccess(moves)
case _ compute(game) addEffect { set(game, _) }
}
def delete(game: Game) {
// TODO figure out a best way
memo.put(game.id, Vector.empty)
}
private def compute(game: Game): Fu[Vector[String]] = for {
fen game.variant.exotic ?? { GameRepo initialFen game.id }
pgn PgnRepo get game.id
uciMoves UciDump(pgn, fen, game.variant).future
} yield uciMoves.toVector
}

View file

@ -19,6 +19,7 @@ final class Env(
ai: lila.ai.Ai,
getUsername: String Fu[Option[String]],
getUsernameOrAnon: String Fu[String],
uciMemo: lila.game.UciMemo,
i18nKeys: lila.i18n.I18nKeys,
scheduler: lila.common.Scheduler) {
@ -91,7 +92,8 @@ final class Env(
notifyMove = notifyMove,
finisher = finisher,
cheatDetector = cheatDetector,
roundMap = hub.actor.roundMap)
roundMap = hub.actor.roundMap,
uciMemo = uciMemo)
private lazy val drawer = new Drawer(
messenger = messenger,
@ -166,6 +168,7 @@ object Env {
ai = lila.ai.Env.current.ai,
getUsername = lila.user.Env.current.usernameOption,
getUsernameOrAnon = lila.user.Env.current.usernameOrAnonymous,
uciMemo = lila.game.Env.current.uciMemo,
i18nKeys = lila.i18n.Env.current.keys,
scheduler = lila.common.PlayApp.scheduler)
}

View file

@ -6,7 +6,7 @@ import chess.{ Status, Role, Color }
import actorApi.round.{ HumanPlay, AiPlay, DrawNo, TakebackNo, PlayResult, Cheat }
import lila.ai.Ai
import lila.game.{ Game, GameRepo, PgnRepo, Pov, Progress }
import lila.game.{ Game, GameRepo, PgnRepo, Pov, Progress, UciMemo }
import lila.hub.actorApi.map.Tell
private[round] final class Player(
@ -14,7 +14,8 @@ private[round] final class Player(
notifyMove: (String, String, Option[String]) Unit,
finisher: Finisher,
cheatDetector: CheatDetector,
roundMap: akka.actor.ActorSelection) {
roundMap: akka.actor.ActorSelection,
uciMemo: UciMemo) {
def human(play: HumanPlay)(pov: Pov): Fu[Events] = play match {
case HumanPlay(playerId, origS, destS, promS, blur, lag, onFailure) pov match {
@ -27,10 +28,12 @@ private[round] final class Player(
chessGame = game.toChess withPgnMoves pgnString
newChessGameAndMove chessGame(orig, dest, promotion, lag)
(newChessGame, move) = newChessGameAndMove
} yield game.update(newChessGame, move, blur)).prefixFailuresWith(playerId + " - ").fold(fufail(_), {
case (progress, pgn)
} yield game.update(newChessGame, move, blur) -> move).prefixFailuresWith(playerId + " - ").future flatMap {
case ((progress, pgn), move)
(GameRepo save progress) >>
PgnRepo.save(pov.gameId, pgn) >>-
PgnRepo.save(pov.gameId, pgn) >>- {
if (pov.game.hasAi) uciMemo.add(pov.game, move)
} >>-
notifyProgress(progress) >>
progress.game.finished.fold(
moveFinish(progress.game, color) map { progress.events ::: _ }, {
@ -43,7 +46,7 @@ private[round] final class Player(
}
} inject progress.events
})
})
}
} addFailureEffect onFailure
case _ fufail("Not your turn")
}
@ -52,7 +55,7 @@ private[round] final class Player(
def ai(play: AiPlay)(game: Game): Fu[Events] =
(game.playable && game.player.isAi).fold(
engine.play(game, game.aiLevel | 1) flatMap { progress
notifyProgress(progress)
notifyProgress(progress)
moveFinish(progress.game, game.turnColor) map { progress.events ::: _ }
} addFailureEffect play.onFailure,
fufail("not AI turn")

View file

@ -111,7 +111,7 @@ object ApplicationBuild extends Build {
)
lazy val ai = project("ai", Seq(common, hub, chess, game, analyse)).settings(
libraryDependencies ++= provided(play.api)
libraryDependencies ++= provided(play.api, reactivemongo, playReactivemongo)
)
lazy val security = project("security", Seq(common, hub, db, user)).settings(