Skip to content

Commit

Permalink
Merge branch 'master' into scalachess-pgn
Browse files Browse the repository at this point in the history
* master:
  fix/improve puzzle batch mobile API
  fix logging buffering
  add glicko data to puzzle select/batch API
  • Loading branch information
ornicar committed Mar 15, 2023
2 parents e13cde6 + 9a75ca2 commit c3da0ac
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 59 deletions.
35 changes: 16 additions & 19 deletions app/controllers/Puzzle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ final class Puzzle(env: Env, apiC: => Api) extends LilaController(env):
case Some((puzzle, replay)) =>
renderJson(puzzle, angle, replay.some) map { nextJson =>
Json.obj(
"round" -> env.puzzle.jsonView.roundJson(me, round, perf),
"round" -> env.puzzle.jsonView.roundJson.web(me, round, perf),
"next" -> nextJson
)
}
Expand All @@ -210,7 +210,7 @@ final class Puzzle(env: Env, apiC: => Api) extends LilaController(env):
next <- nextPuzzleForMe(angle, none)
nextJson <- next.?? { renderJson(_, angle, none, newUser.some) dmap some }
yield Json.obj(
"round" -> env.puzzle.jsonView.roundJson(me, round, perf),
"round" -> env.puzzle.jsonView.roundJson.web(me, round, perf),
"next" -> nextJson
)
}
Expand Down Expand Up @@ -449,15 +449,13 @@ final class Puzzle(env: Env, apiC: => Api) extends LilaController(env):
}

def apiBatchSelect(angleStr: String) = AnonOrScoped(_.Puzzle.Read) { implicit req => me =>
batchSelect(me, PuzzleAngle findOrMix angleStr, reqDifficulty, getInt("nb", req) | 15)
batchSelect(me, PuzzleAngle findOrMix angleStr, reqDifficulty, getInt("nb", req) | 15).dmap(Ok.apply)
}

private def reqDifficulty(using req: RequestHeader) = PuzzleDifficulty.orDefault(~get("difficulty", req))
private def batchSelect(me: Option[UserModel], angle: PuzzleAngle, difficulty: PuzzleDifficulty, nb: Int)(
using req: RequestHeader
): Fu[Result] =
env.puzzle.batch.nextFor(me, angle, difficulty, nb atLeast 1 atMost 50) flatMap
env.puzzle.jsonView.batch dmap { Ok(_) }
private def batchSelect(me: Option[UserModel], angle: PuzzleAngle, difficulty: PuzzleDifficulty, nb: Int) =
env.puzzle.batch.nextFor(me, angle, difficulty, nb atMost 50) flatMap
env.puzzle.jsonView.batch(me)

def apiBatchSolve(angleStr: String) = AnonOrScopedBody(parse.json)(_.Puzzle.Write) { implicit req => me =>
req.body
Expand All @@ -466,19 +464,18 @@ final class Puzzle(env: Env, apiC: => Api) extends LilaController(env):
err => BadRequest(err.toString).toFuccess,
data => {
val angle = PuzzleAngle findOrMix angleStr
{
me match {
for
rounds <- me match
case Some(me) =>
lila.common.LilaFuture
.applySequentially(data.solutions) { sol =>
env.puzzle
.finisher(sol.id, angle, me, sol.win, sol.mode)
.void
}
env.puzzle.finisher.batch(me, angle, data.solutions).map {
_.map { (round, rDiff) => env.puzzle.jsonView.roundJson.api(round, rDiff) }
}
case None =>
data.solutions.map { sol => env.puzzle.finisher.incPuzzlePlays(sol.id) }.parallel
}
} >> getInt("nb", req).fold(fuccess(NoContent))(batchSelect(me, angle, reqDifficulty, _)(using req))
data.solutions.map { sol => env.puzzle.finisher.incPuzzlePlays(sol.id) }.parallel inject Nil
newMe <- me.??(env.user.repo.byId)
nextPuzzles <- batchSelect(newMe, angle, reqDifficulty, ~getInt("nb", req))
result = Json.obj("next" -> nextPuzzles, "rounds" -> rounds)
yield Ok(result)
}
)
}
Expand Down
22 changes: 8 additions & 14 deletions modules/common/src/main/LilaFuture.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,22 @@ import lila.Lila.Fu

object LilaFuture:

def fold[T, R](
list: List[T]
)(zero: R)(op: (R, T) => Fu[R])(using Executor): Fu[R] =
def fold[T, R](list: List[T])(acc: R)(op: (R, T) => Fu[R])(using Executor): Fu[R] =
list match
case head :: rest =>
op(zero, head) flatMap { res =>
op(acc, head) flatMap { res =>
fold(rest)(res)(op)
}
case Nil => fuccess(zero)
case Nil => fuccess(acc)

def lazyFold[T, R](
futures: LazyList[Fu[T]]
)(zero: R)(op: (R, T) => R)(using Executor): Fu[R] =
LazyList.cons.unapply(futures).fold(fuccess(zero)) { case (future, rest) =>
def lazyFold[T, R](futures: LazyList[Fu[T]])(acc: R)(op: (R, T) => R)(using Executor): Fu[R] =
LazyList.cons.unapply(futures).fold(fuccess(acc)) { (future, rest) =>
future flatMap { f =>
lazyFold(rest)(op(zero, f))(op)
lazyFold(rest)(op(acc, f))(op)
}
}

def filter[A](
list: List[A]
)(f: A => Fu[Boolean])(using Executor): Fu[List[A]] =
def filter[A](list: List[A])(f: A => Fu[Boolean])(using Executor): Fu[List[A]] =
ScalaFu
.sequence {
list.map { element =>
Expand All @@ -45,7 +39,7 @@ object LilaFuture:

def linear[A, B, M[B] <: Iterable[B]](
in: M[A]
)(f: A => Fu[B])(implicit cbf: BuildFrom[M[A], B, M[B]], ec: Executor): Fu[M[B]] =
)(f: A => Fu[B])(using cbf: BuildFrom[M[A], B, M[B]], ec: Executor): Fu[M[B]] =
in.foldLeft(fuccess(cbf.newBuilder(in))) { (fr, a) =>
fr flatMap { r =>
f(a).dmap(r += _)
Expand Down
11 changes: 2 additions & 9 deletions modules/perfStat/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ final class JsonView(getLightUser: LightUser.GetterSync):

object JsonView:

private def round(v: Double, depth: Int = 2) = lila.common.Maths.roundDownAt(v, depth)
import lila.rating.Glicko.given

private val isoFormatter = ISODateTimeFormat.dateTime
private given Writes[DateTime] = Writes { d =>
Expand All @@ -53,18 +53,11 @@ object JsonView:
given OWrites[User] = OWrites { u =>
Json.obj("name" -> u.username)
}
given OWrites[Glicko] = OWrites { p =>
Json.obj(
"rating" -> round(p.rating),
"deviation" -> round(p.deviation),
"provisional" -> p.provisional
)
}
given OWrites[Perf] = OWrites { p =>
Json.obj("glicko" -> p.glicko, "nb" -> p.nb, "progress" -> p.progress)
}
private given Writes[Avg] = Writes { a =>
JsNumber(round(a.avg))
JsNumber(lila.common.Maths.roundDownAt(a.avg, 2))
}
given (using lang: Lang): OWrites[PerfType] = OWrites { pt =>
Json.obj(
Expand Down
37 changes: 21 additions & 16 deletions modules/puzzle/src/main/JsonView.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,25 +62,28 @@ final class JsonView(
"id" -> u.id,
"rating" -> u.perfs.puzzle.intRating
)
.add(
"provisional" -> u.perfs.puzzle.provisional
)
.add("provisional" -> u.perfs.puzzle.provisional)

private def replayJson(r: PuzzleReplay) =
Json.obj("days" -> r.days, "i" -> r.i, "of" -> r.nb)

def roundJson(u: User, round: PuzzleRound, perf: Perf) =
Json
.obj(
"win" -> round.win,
"ratingDiff" -> (perf.intRating.value - u.perfs.puzzle.intRating.value)
)
.add("vote" -> round.vote)
.add("themes" -> round.nonEmptyThemes.map { rt =>
JsObject(rt.map { t =>
t.theme.value -> JsBoolean(t.vote)
object roundJson {
def web(u: User, round: PuzzleRound, perf: Perf) =
base(round, IntRatingDiff(perf.intRating.value - u.perfs.puzzle.intRating.value))
.add("vote" -> round.vote)
.add("themes" -> round.nonEmptyThemes.map { rt =>
JsObject(rt.map { t =>
t.theme.value -> JsBoolean(t.vote)
})
})
})

def api = base _
private def base(round: PuzzleRound, ratingDiff: IntRatingDiff) = Json.obj(
"id" -> round.id.puzzleId,
"win" -> round.win,
"ratingDiff" -> ratingDiff
)
}

def pref(p: lila.pref.Pref) =
Json.obj(
Expand Down Expand Up @@ -114,7 +117,7 @@ final class JsonView(
"performance" -> res.performance
)

def batch(puzzles: Seq[Puzzle]): Fu[JsObject] = for {
def batch(user: Option[User])(puzzles: Seq[Puzzle]): Fu[JsObject] = for
games <- gameRepo.gameOptionsFromSecondary(puzzles.map(_.gameId))
jsons <- (puzzles zip games).collect { case (puzzle, Some(game)) =>
gameJson.noCache(game, puzzle.initialPly) map { gameJson =>
Expand All @@ -124,7 +127,9 @@ final class JsonView(
)
}
}.parallel
} yield Json.obj("puzzles" -> jsons)
yield
import lila.rating.Glicko.given
Json.obj("puzzles" -> jsons).add("glicko" -> user.map(_.perfs.puzzle.glicko))

object bc:

Expand Down
14 changes: 14 additions & 0 deletions modules/puzzle/src/main/PuzzleFinisher.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import lila.rating.{ Glicko, Perf, PerfType }
import lila.user.{ User, UserRepo }
import chess.Mode
import lila.common.config.Max
import lila.puzzle.PuzzleForm.batch.Solution

final private[puzzle] class PuzzleFinisher(
api: PuzzleApi,
Expand All @@ -27,6 +28,19 @@ final private[puzzle] class PuzzleFinisher(
name = "puzzle.finish"
)

def batch(me: User, angle: PuzzleAngle, solutions: List[Solution]): Fu[List[(PuzzleRound, IntRatingDiff)]] =
lila.common.LilaFuture
.fold(solutions)((me.perfs.puzzle, List.empty[(PuzzleRound, IntRatingDiff)])) {
case ((perf, rounds), sol) =>
apply(sol.id, angle, me, sol.win, sol.mode) map {
case Some((round, newPerf)) =>
val rDiff = IntRatingDiff(newPerf.intRating.value - perf.intRating.value)
(newPerf, (round, rDiff) :: rounds)
case None => (perf, rounds)
}
}
.map { (_, rounds) => rounds.reverse }

def apply(
id: PuzzleId,
angle: PuzzleAngle,
Expand Down
13 changes: 13 additions & 0 deletions modules/rating/src/main/Glicko.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ case object Glicko:
"v" -> w.double(o.volatility)
)

import play.api.libs.json.{ OWrites, Json }
given OWrites[Glicko] =
import lila.common.Maths.roundDownAt
import lila.common.Json.given
OWrites { p =>
Json
.obj(
"rating" -> roundDownAt(p.rating, 2),
"deviation" -> roundDownAt(p.deviation, 2)
)
.add("provisional" -> p.provisional)
}

sealed abstract class Result:
def negate: Result
object Result:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"check-format": "prettier --check .",
"lint": "eslint . --ext .ts",
"lila-journal": "journalctl --user -fu lila -o cat",
"metals-log": "tail -F .metals/metals.log | rg -v '(Unmatched cancel notification|handleCancellation)' | cut -c 21-",
"metals-log": "tail -F .metals/metals.log | stdbuf -oL cut -c 21- | rg -v '(notification for request|handleCancellation)'",
"serverlog": "pnpm lila-journal & pnpm metals-log",
"multilog": "pnpm serverlog & ui/build -w"
}
Expand Down

0 comments on commit c3da0ac

Please sign in to comment.