explicit lazy loading of chess.Game and clock history

Results in better performances than pre-huffman,
and is huffman-compatible.

Potential nasty bugs if lazily loaded values have side effects,
including reading current time.
lazy-decoded
Thibault Duplessis 2018-02-07 11:22:07 -05:00
parent 6f4886c604
commit ea8ded9349
6 changed files with 65 additions and 60 deletions

View File

@ -41,7 +41,7 @@ private[challenge] final class Joiner(onStart: String => Unit) {
).copy(id = c.id).|> { g =>
state.fold(g) {
case sit @ SituationPlus(Situation(board, _), _) => g.copy(
chess = g.chess.copy(
loadChess = () => g.chess.copy(
situation = g.situation.copy(
board = g.board.copy(
history = board.history,

View File

@ -72,19 +72,6 @@ object BSONHandlers {
val plies = r int F.turns atMost Game.maxPlies // unlimited can cause StackOverflowError
val turnColor = Color.fromPly(plies)
val decoded = r.bytesO(F.huffmanPgn).map { PgnStorage.Huffman.decode(_, plies) } | {
val clm = r.get[CastleLastMove](F.castleLastMove)
PgnStorage.Decoded(
pgnMoves = PgnStorage.OldBin.decode(r bytesD F.oldPgn, plies),
pieces = BinaryFormat.piece.read(r bytes F.binaryPieces, gameVariant),
positionHashes = r.getO[chess.PositionHash](F.positionHashes) | Array.empty,
unmovedRooks = r.getO[UnmovedRooks](F.unmovedRooks) | UnmovedRooks.default,
lastMove = clm.lastMove,
castles = clm.castles,
format = PgnStorage.OldBin
)
}
val winC = r boolO F.winnerColor map Color.apply
val uids = ~r.getO[List[String]](F.playerUids)
val (whiteUid, blackUid) = (uids.headOption.filter(_.nonEmpty), uids.lift(1).filter(_.nonEmpty))
@ -100,44 +87,61 @@ object BSONHandlers {
val createdAt = r date F.createdAt
val status = r.get[Status](F.status)
val chessGame = ChessGame(
situation = chess.Situation(
chess.Board(
pieces = decoded.pieces,
history = ChessHistory(
lastMove = decoded.lastMove,
castles = decoded.castles,
positionHashes = decoded.positionHashes,
unmovedRooks = decoded.unmovedRooks,
checkCount = if (gameVariant.threeCheck) {
val counts = r.intsD(F.checkCount)
CheckCount(~counts.headOption, ~counts.lastOption)
} else Game.emptyCheckCount
val pgnFormat =
if (r contains F.huffmanPgn) PgnStorage.Huffman else PgnStorage.OldBin
val loadChess: () => chess.Game = () => {
val decoded = r.bytesO(F.huffmanPgn).map { PgnStorage.Huffman.decode(_, plies) } | {
val clm = r.get[CastleLastMove](F.castleLastMove)
PgnStorage.Decoded(
pgnMoves = PgnStorage.OldBin.decode(r bytesD F.oldPgn, plies),
pieces = BinaryFormat.piece.read(r bytes F.binaryPieces, gameVariant),
positionHashes = r.getO[chess.PositionHash](F.positionHashes) | Array.empty,
unmovedRooks = r.getO[UnmovedRooks](F.unmovedRooks) | UnmovedRooks.default,
lastMove = clm.lastMove,
castles = clm.castles
)
}
ChessGame(
situation = chess.Situation(
chess.Board(
pieces = decoded.pieces,
history = ChessHistory(
lastMove = decoded.lastMove,
castles = decoded.castles,
positionHashes = decoded.positionHashes,
unmovedRooks = decoded.unmovedRooks,
checkCount = if (gameVariant.threeCheck) {
val counts = r.intsD(F.checkCount)
CheckCount(~counts.headOption, ~counts.lastOption)
} else Game.emptyCheckCount
),
variant = gameVariant,
crazyData = gameVariant.crazyhouse option r.get[Crazyhouse.Data](F.crazyData)
),
variant = gameVariant,
crazyData = gameVariant.crazyhouse option r.get[Crazyhouse.Data](F.crazyData)
color = turnColor
),
color = turnColor
),
pgnMoves = decoded.pgnMoves,
clock = r.getO[Color => Clock](F.clock) {
clockBSONReader(createdAt, wPlayer.berserk, bPlayer.berserk)
} map (_(turnColor)),
turns = plies,
startedAtTurn = r intD F.startedAtTurn
)
pgnMoves = decoded.pgnMoves,
clock = r.getO[Color => Clock](F.clock) {
clockBSONReader(createdAt, wPlayer.berserk, bPlayer.berserk)
} map (_(turnColor)),
turns = plies,
startedAtTurn = r intD F.startedAtTurn
)
}
Game(
id = r str F.id,
whitePlayer = wPlayer,
blackPlayer = bPlayer,
chess = chessGame,
pgnStorage = decoded.format,
loadChess = loadChess,
pgnStorage = pgnFormat,
status = status,
daysPerTurn = r intO F.daysPerTurn,
binaryMoveTimes = r bytesO F.moveTimes,
clockHistory = for {
clk <- chessGame.clock
loadClockHistory = () => for {
clk <- loadChess().clock
bw <- r bytesO F.whiteClockHistory
bb <- r bytesO F.blackClockHistory
history <- BinaryFormat.clockHistory.read(clk.limit, bw, bb, (status == Status.Outoftime).option(turnColor))

View File

@ -16,12 +16,12 @@ case class Game(
id: String,
whitePlayer: Player,
blackPlayer: Player,
chess: ChessGame,
loadChess: () => ChessGame,
loadClockHistory: () => Option[ClockHistory] = () => Some(ClockHistory()),
pgnStorage: PgnStorage,
status: Status,
daysPerTurn: Option[Int],
binaryMoveTimes: Option[ByteArray] = None,
clockHistory: Option[ClockHistory] = Some(ClockHistory()),
mode: Mode = Mode.default,
next: Option[String] = None,
bookmarks: Int = 0,
@ -30,6 +30,9 @@ case class Game(
metadata: Metadata
) {
lazy val chess = loadChess()
lazy val clockHistory = loadClockHistory()
def situation = chess.situation
def board = chess.situation.board
def history = chess.situation.board.history
@ -172,7 +175,7 @@ case class Game(
val updated = copy(
whitePlayer = copyPlayer(whitePlayer),
blackPlayer = copyPlayer(blackPlayer),
chess = game,
loadChess = () => game,
binaryMoveTimes = (!isPgnImport && !chess.clock.isDefined).option {
BinaryFormat.moveTime.write {
binaryMoveTimes.?? { t =>
@ -180,7 +183,7 @@ case class Game(
} :+ Centis(nowCentis - movedAt.getCentis).nonNeg
}
},
clockHistory = for {
loadClockHistory = () => for {
clk <- game.clock
ch <- clockHistory
} yield ch.record(turnColor, clk),
@ -326,8 +329,8 @@ case class Game(
clock.ifTrue(berserkable && !player(color).berserk).map { c =>
val newClock = c goBerserk color
Progress(this, copy(
chess = chess.copy(clock = Some(newClock)),
clockHistory = clockHistory.map(history => {
loadChess = () => chess.copy(clock = Some(newClock)),
loadClockHistory = () => loadClockHistory().map(history => {
if (history(color).isEmpty) history
else history.reset(color).record(color, newClock)
})
@ -350,8 +353,8 @@ case class Game(
status = status,
whitePlayer = whitePlayer.finish(winner contains White),
blackPlayer = blackPlayer.finish(winner contains Black),
chess = chess.copy(clock = newClock),
clockHistory = for {
loadChess = () => chess.copy(clock = newClock),
loadClockHistory = () => for {
clk <- clock
history <- clockHistory
} yield {
@ -438,7 +441,7 @@ case class Game(
def isClockRunning = clock ?? (_.isRunning)
def withClock(c: Clock) = Progress(this, copy(chess = chess.copy(clock = Some(c))))
def withClock(c: Clock) = Progress(this, copy(loadChess = () => chess.copy(clock = Some(c))))
def correspondenceGiveTime = Progress(this, copy(movedAt = DateTime.now))
@ -535,7 +538,7 @@ case class Game(
def isPgnImport = pgnImport.isDefined
def resetTurns = copy(
chess = chess.copy(turns = 0, startedAtTurn = 0)
loadChess = () => chess.copy(turns = 0, startedAtTurn = 0)
)
lazy val opening: Option[FullOpening.AtPly] =
@ -632,7 +635,7 @@ object Game {
id = IdGenerator.game,
whitePlayer = whitePlayer,
blackPlayer = blackPlayer,
chess = chess,
loadChess = () => chess,
pgnStorage = PgnStorage(chess.situation.board.variant, List(whitePlayer.userId, blackPlayer.userId).flatten),
status = Status.Created,
daysPerTurn = daysPerTurn,

View File

@ -48,8 +48,7 @@ private object PgnStorage {
whiteQueenSide = unmovedRooks(Pos.A1),
blackKingSide = unmovedRooks(Pos.H8),
blackQueenSide = unmovedRooks(Pos.A8)
),
format = Huffman
)
)
}
@ -71,8 +70,7 @@ private object PgnStorage {
positionHashes: PositionHash, // irrelevant after game ends
unmovedRooks: UnmovedRooks, // irrelevant after game ends
lastMove: Option[Uci],
castles: Castles, // irrelevant after game ends
format: PgnStorage
castles: Castles // irrelevant after game ends
)
private val betaTesters = Set("thibault", "revoof", "isaacly")

View File

@ -32,12 +32,12 @@ object Rewind {
val newGame = game.copy(
whitePlayer = rewindPlayer(game.whitePlayer),
blackPlayer = rewindPlayer(game.blackPlayer),
chess = rewindedGame.copy(clock = newClock),
loadChess = () => rewindedGame.copy(clock = newClock),
binaryMoveTimes = game.binaryMoveTimes.map { binary =>
val moveTimes = BinaryFormat.moveTime.read(binary, game.playedTurns)
BinaryFormat.moveTime.write(moveTimes.dropRight(1))
},
clockHistory = game.clockHistory.map(_.update(!color, _.dropRight(1))),
loadClockHistory = () => game.clockHistory.map(_.update(!color, _.dropRight(1))),
movedAt = DateTime.now
)
Progress(game, newGame)

View File

@ -74,7 +74,7 @@ trait Positional { self: Config =>
val game = builder(chessGame)
state.fold(game) {
case sit @ SituationPlus(Situation(board, _), _) => game.copy(
chess = game.chess.copy(
loadChess = () => game.chess.copy(
situation = game.situation.copy(
board = game.board.copy(
history = board.history,