ballsy study optimisation: set shapes without updating the whole chapter

Featuring backtrack recursion \o/
Falls back to logged full update in case of failure (?!)

TODO: same thing for comments, adding/deleting nodes.
pull/2876/head
Thibault Duplessis 2017-03-30 20:58:50 +02:00
parent 6401491d01
commit 0166d68d5c
5 changed files with 41 additions and 6 deletions

View File

@ -78,6 +78,12 @@ object BSONHandlers {
private implicit val FenBSONHandler = stringAnyValHandler[FEN](_.value, FEN.apply)
implicit val ShapesBSONHandler: BSONHandler[BSONArray, Shapes] =
isoHandler[Shapes, List[Shape], BSONArray](
(s: Shapes) => s.value.distinct,
Shapes(_)
)
import lila.tree.Node.{ Comment, Comments }
private implicit val CommentIdBSONHandler = stringAnyValHandler[Comment.Id](_.value, Comment.Id.apply)
private implicit val CommentTextBSONHandler = stringAnyValHandler[Comment.Text](_.value, Comment.Text.apply)
@ -105,9 +111,6 @@ object BSONHandlers {
private def readComments(r: Reader) =
Comments(r.getsD[Comment]("co").filter(_.text.value.nonEmpty))
private def readShapes(r: Reader) =
Shapes(r.getsD[Shape]("h"))
private implicit def CrazyDataBSONHandler: BSON[Crazyhouse.Data] = new BSON[Crazyhouse.Data] {
private def writePocket(p: Crazyhouse.Pocket) = p.roles.map(_.forsyth).mkString
private def readPocket(p: String) = Crazyhouse.Pocket(p.flatMap(chess.Role.forsyth)(scala.collection.breakOut))
@ -139,7 +142,7 @@ object BSONHandlers {
move = WithSan(r.get[Uci]("u"), r.str("s")),
fen = r.get[FEN]("f"),
check = r boolD "c",
shapes = readShapes(r),
shapes = r.getO[Shapes]("h") | Shapes.empty,
comments = readComments(r),
glyphs = r.getO[Glyphs]("g") | Glyphs.empty,
crazyData = r.getO[Crazyhouse.Data]("z"),
@ -167,7 +170,7 @@ object BSONHandlers {
ply = r int "p",
fen = r.get[FEN]("f"),
check = r boolD "c",
shapes = readShapes(r),
shapes = r.getO[Shapes]("h") | Shapes.empty,
comments = readComments(r),
glyphs = r.getO[Glyphs]("g") | Glyphs.empty,
clock = r.getO[Centis]("l"),

View File

@ -59,6 +59,27 @@ final class ChapterRepo(coll: Coll) {
def setTagsFor(chapter: Chapter) =
coll.updateField($id(chapter.id), "tags", chapter.tags).void
def setShapes(chapter: Chapter, path: Path, shapes: lila.tree.Node.Shapes): Option[Funit] =
pathToField(chapter, path) map { field =>
val shapesField = s"$field.h"
if (shapes.value.isEmpty) coll.unsetField($id(chapter.id), shapesField).void
else coll.updateField($id(chapter.id), shapesField, shapes).void
}
// root.n[0].n[0].n[1].n[0].n[2]
private def pathToField(chapter: Chapter, path: Path): Option[String] =
pathToIndexes(chapter.root.children, path) map { indexes =>
s"root.n.${indexes.mkString(".n.")}"
}
// List(0, 0, 1, 0, 2)
private def pathToIndexes(children: Node.Children, path: Path): Option[List[Int]] =
path.split.fold(List.empty[Int].some) {
case (head, tail) => children.getNodeAndIndex(head) flatMap {
case (node, index) => pathToIndexes(node.children, tail).map(rest => index :: rest)
}
}
private[study] def idNamesByStudyIds(studyIds: Seq[Study.Id]): Fu[Map[Study.Id, Vector[Chapter.IdName]]] =
coll.find(
$doc("studyId" $in studyIds),

View File

@ -130,6 +130,11 @@ object Node {
def get(id: UciCharPair): Option[Node] = nodes.find(_.id == id)
def getNodeAndIndex(id: UciCharPair): Option[(Node, Int)] =
nodes.zipWithIndex.collectFirst {
case pair if pair._1.id == id => pair
}
def has(id: UciCharPair): Boolean = nodes.exists(_.id == id)
def updateWith(id: UciCharPair, op: Node => Option[Node]): Option[Children] =

View File

@ -251,7 +251,10 @@ final class StudyApi(
chapter.setShapes(shapes, position.path) match {
case Some(newChapter) =>
studyRepo.updateNow(study)
chapterRepo.update(newChapter) >>-
chapterRepo.setShapes(newChapter, position.path, shapes) | {
logger.warn(s"Failed to setShapes $studyId $position")
chapterRepo update newChapter
} >>-
sendTo(study, Socket.SetShapes(position, shapes, uid))
case None => fufail(s"Invalid setShapes $position $shapes") >>- reloadUid(study, uid)
}

View File

@ -109,6 +109,9 @@ object Node {
def list = value
def ++(shapes: Shapes) = Shapes(value ::: shapes.value)
}
object Shapes {
val empty = Shapes(Nil)
}
case class Comment(id: Comment.Id, text: Comment.Text, by: Comment.Author) {
def removeMeta = text.removeMeta map { t =>