send more live relay metadata to the client; make clocks tick

pull/3627/head
Thibault Duplessis 2017-10-05 18:22:29 -05:00
parent 75f86931c0
commit 793637eb6b
12 changed files with 81 additions and 28 deletions

View File

@ -71,7 +71,10 @@ private final class RelaySync(
rawNode = n,
uid = socketUid,
opts = moveOpts.copy(clock = n.clock),
relayMoveAt = DateTime.now.some
relay = Chapter.Relay(
path = position.path + n,
lastMoveAt = DateTime.now.some
).some
) flatten s"Can't add relay node $position $node"
} inject node.mainline.size
}

View File

@ -237,6 +237,7 @@ object BSONHandlers {
Tags(_)
)
private implicit val ChapterSetupBSONHandler = Macros.handler[Chapter.Setup]
implicit val ChapterRelayBSONHandler = Macros.handler[Chapter.Relay]
import Chapter.Ply
implicit val PlyBSONHandler = intAnyValHandler[Ply](_.value, Ply.apply)
implicit val ChapterBSONHandler = Macros.handler[Chapter]

View File

@ -22,7 +22,7 @@ case class Chapter(
practice: Option[Boolean] = None,
gamebook: Option[Boolean] = None,
description: Option[String] = None,
relayLastMoveAt: Option[DateTime] = None,
relay: Option[Chapter.Relay] = None,
createdAt: DateTime
) extends Chapter.Like {
@ -31,13 +31,11 @@ case class Chapter(
copy(root = newRoot)
}
def addNode(node: Node, path: Path, relayMoveAt: Option[DateTime] = None): Option[Chapter] =
updateRoot { root =>
root.withChildren(_.addNodeAt(node, path))
def addNode(node: Node, path: Path, newRelay: Option[Chapter.Relay] = None): Option[Chapter] =
updateRoot {
_.withChildren(_.addNodeAt(node, path))
} map {
_.copy(
relayLastMoveAt = relayMoveAt orElse relayLastMoveAt
)
_.copy(relay = newRelay orElse relay)
}
def setShapes(shapes: Shapes, path: Path): Option[Chapter] =
@ -82,10 +80,6 @@ case class Chapter(
def isConceal = conceal.isDefined
def withoutChildren = copy(root = root.withoutChildren)
def relaySecondsSinceLastMove: Option[Int] = relayLastMoveAt.map { at =>
(nowSeconds - at.getSeconds).toInt
}
}
object Chapter {
@ -114,6 +108,15 @@ object Chapter {
def isFromFen = ~fromFen
}
case class Relay(
path: Path,
lastMoveAt: Option[DateTime]
) {
def secondsSinceLastMove: Option[Int] = lastMoveAt.map { at =>
(nowSeconds - at.getSeconds).toInt
}
}
case class Metadata(
_id: Chapter.Id,
name: Chapter.Name,

View File

@ -60,8 +60,8 @@ final class ChapterRepo(coll: Coll) {
def removeConceal(chapterId: Chapter.Id) =
coll.unsetField($id(chapterId), "conceal").void
def setRelayLastMoveAt(chapterId: Chapter.Id, at: DateTime) =
coll.updateField($id(chapterId), "relayLastMoveAt", at).void
def setRelay(chapterId: Chapter.Id, relay: Chapter.Relay) =
coll.updateField($id(chapterId), "relay", relay).void
def setTagsFor(chapter: Chapter) =
coll.updateField($id(chapter.id), "tags", chapter.tags).void

View File

@ -47,7 +47,7 @@ final class JsonView(
"explorer" -> allowed(study.settings.explorer)
)
).add("description", currentChapter.description)
.add("relaySecondsSinceLastMove", currentChapter.relaySecondsSinceLastMove) |> addChapterMode(currentChapter)
.add("relay", currentChapter.relay)(relayWrites) |> addChapterMode(currentChapter)
}
)
}
@ -177,4 +177,9 @@ object JsonView {
JsNumber(p.value)
}
private[study] implicit val likingRefWrites: Writes[Study.Liking] = Json.writes[Study.Liking]
implicit val relayWrites = OWrites[Chapter.Relay] { r =>
Json.obj("path" -> r.path)
.add("secondsSinceLastMove", r.secondsSinceLastMove)
}
}

View File

@ -1,19 +1,19 @@
package lila.study
import akka.actor._
import org.joda.time.DateTime
import play.api.libs.json._
import scala.concurrent.duration._
import org.joda.time.DateTime
import chess.Centis
import chess.format.pgn.Glyphs
import lila.common.PimpedJson._
import lila.hub.TimeBomb
import lila.socket.actorApi.{ Connected => _, _ }
import lila.socket.Socket.Uid
import lila.socket.{ SocketActor, History, Historical, AnaDests }
import lila.tree.Node.{ Shapes, Comment }
import lila.user.User
import lila.common.PimpedJson._
private final class Socket(
studyId: Study.Id,
@ -69,7 +69,7 @@ private final class Socket(
"w" -> who(uid).map(whoWriter.writes)
), noMessadata)
case AddNode(pos, node, variant, uid, sticky) =>
case AddNode(pos, node, variant, uid, sticky, relay) =>
val dests = AnaDests(
variant,
node.fen,
@ -83,7 +83,7 @@ private final class Socket(
"d" -> dests.dests,
"o" -> dests.opening,
"s" -> sticky
), noMessadata)
).add("relay", relay), noMessadata)
case DeleteNode(pos, uid) => notifyVersion("deleteNode", Json.obj(
"p" -> pos,
@ -300,7 +300,8 @@ object Socket {
node: Node,
variant: chess.variant.Variant,
uid: Uid,
sticky: Boolean
sticky: Boolean,
relay: Option[Chapter.Relay]
)
case class DeleteNode(position: Position.Ref, uid: Uid)
case class Promote(position: Position.Ref, toMainline: Boolean, uid: Uid)

View File

@ -84,6 +84,8 @@ final class StudyApi(
def studyIdOf = chapterRepo.studyIdOf _
def members(id: Study.Id): Fu[Option[StudyMembers]] = studyRepo membersById id
def create(data: StudyMaker.Data, user: User): Fu[Option[Study.WithChapter]] = (data.form.as match {
case DataForm.AsNewStudy =>
studyMaker(data, user) flatMap { res =>
@ -182,9 +184,9 @@ final class StudyApi(
}
}
def doAddNode(userId: User.ID, study: Study, position: Position, rawNode: Node, uid: Uid, opts: MoveOpts, relayMoveAt: Option[DateTime]): Fu[Option[Position]] = {
def doAddNode(userId: User.ID, study: Study, position: Position, rawNode: Node, uid: Uid, opts: MoveOpts, relay: Option[Chapter.Relay]): Fu[Option[Position]] = {
val node = rawNode.withoutChildren
position.chapter.addNode(node, position.path, relayMoveAt) match {
position.chapter.addNode(node, position.path, relay) match {
case None =>
fufail(s"Invalid addNode ${study.id} ${position.ref} $node") >>-
reloadUidBecauseOf(study, uid, position.chapter.id) inject none
@ -192,10 +194,17 @@ final class StudyApi(
chapter.root.nodeAt(position.path) ?? { parent =>
val newPosition = position.ref + node
chapterRepo.setChildren(chapter, position.path, parent.children) >>
(relayMoveAt ?? { chapterRepo.setRelayLastMoveAt(chapter.id, _) }) >>
(relay ?? { chapterRepo.setRelay(chapter.id, _) }) >>
(opts.sticky ?? studyRepo.setPosition(study.id, newPosition)) >>
updateConceal(study, chapter, newPosition) >>- {
sendTo(study, Socket.AddNode(position.ref, node, chapter.setup.variant, uid, sticky = opts.sticky))
sendTo(study, Socket.AddNode(
position.ref,
node,
chapter.setup.variant,
uid,
sticky = opts.sticky,
relay = relay
))
sendStudyEnters(study, userId)
if (opts.promoteToMainline && !Path.isMainline(chapter.root, newPosition.path))
promote(userId, study.id, position.ref + node, toMainline = true, uid)

View File

@ -5,6 +5,7 @@ import play.api.libs.json.Json
import actorApi._
import lila.hub.actorApi.SendTos
import lila.socket.Socket.makeMessage
private[tournament] final class Reminder extends Actor {
@ -16,9 +17,9 @@ private[tournament] final class Reminder extends Actor {
val userIds =
if (activeUserIds.size > max) scala.util.Random.shuffle(activeUserIds) take max
else activeUserIds
context.system.lilaBus.publish(SendTos(userIds.toSet, Json.obj(
"t" -> "tournamentReminder",
"d" -> Json.obj(
context.system.lilaBus.publish(SendTos(userIds.toSet, makeMessage(
"tournamentReminder",
Json.obj(
"id" -> tour.id,
"name" -> tour.fullName
)

View File

@ -20,7 +20,16 @@ export default function(ctrl: AnalyseCtrl): VNode | undefined {
whiteCentis = clock;
blackCentis = parentClock;
}
const whitePov = ctrl.bottomColor() === 'white';
const study = ctrl.study,
whitePov = ctrl.bottomColor() === 'white';
const relay = study && study.data.chapter.relay;
if (relay && relay.lastMoveAt && relay.path === ctrl.path && ctrl.path !== '') {
const spent = (Date.now() - relay.lastMoveAt) / 10;
if (isWhiteTurn) whiteCentis -= spent;
else blackCentis -= spent;
}
const opts = {
tenths: !ctrl.study || !ctrl.study.relay
};

View File

@ -125,6 +125,13 @@ export interface StudyChapter {
gamebook: boolean;
features: StudyChapterFeatures;
description?: string;
relay?: StudyChapterRelay;
}
interface StudyChapterRelay {
path: Tree.Path;
secondsSinceLastMove?: number;
lastMoveAt?: number;
}
interface StudyChapterSetup {

View File

@ -8,6 +8,7 @@ export default class RelayCtrl {
constructor(d: RelayData, readonly send: SocketSend, readonly redraw: () => void, readonly members: any) {
this.data = d;
setInterval(this.redraw, 1000);
}
setSync = (v: Boolean) => {

View File

@ -30,6 +30,8 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
const sri: string = li.StrongSocket ? li.StrongSocket.sri : '';
convertRelayDate();
const vm: StudyVm = (function() {
const isManualChapter = data.chapter.id !== data.position.chapterId;
const sticked = data.features.sticky && !ctrl.initialPath && !isManualChapter && !practiceData;
@ -163,6 +165,7 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
members.dict(s.members);
chapters.list(s.chapters);
ctrl.flipped = false;
convertRelayDate();
const merge = !vm.mode.write && sameChapter;
ctrl.reloadData(d.analysis, merge);
@ -194,6 +197,12 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
ctrl.startCeval();
};
function convertRelayDate() {
const relay = data.chapter.relay;
if (relay && typeof relay.secondsSinceLastMove !== 'undefined' && !relay.lastMoveAt)
relay.lastMoveAt = Date.now() - relay.secondsSinceLastMove * 1000;
}
function xhrReload() {
vm.loading = true;
return xhr.reload(
@ -295,6 +304,10 @@ export default function(data: StudyData, ctrl: AnalyseCtrl, tagTypes: TagTypes,
data.position.path = position.path + node.id;
return;
}
if (d.relay) {
data.chapter.relay = d.relay;
convertRelayDate();
}
const newPath = ctrl.tree.addNode(node, position.path);
if (!newPath) return xhrReload();
ctrl.tree.addDests(d.d, newPath, d.o);