cache UCI data to speed up AI
This commit is contained in:
parent
3087cd05d4
commit
4e6b45dc90
|
@ -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 (
|
||||
|
|
|
@ -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]
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 ⇒
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
39
modules/game/src/main/UciMemo.scala
Normal file
39
modules/game/src/main/UciMemo.scala
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue