study WIP

pull/1834/head
Thibault Duplessis 2016-02-26 19:08:11 +07:00
parent 328fda1ba4
commit 5a7d7f61b4
14 changed files with 371 additions and 3 deletions

View File

@ -157,4 +157,5 @@ object Env {
def slack = lila.slack.Env.current
def challenge = lila.challenge.Env.current
def explorer = lila.explorer.Env.current
def study = lila.study.Env.current
}

View File

@ -524,6 +524,15 @@ challenge {
history.message.ttl = 40 seconds
uid.timeout = 7 seconds
}
study {
collection.study = study
socket {
name = study-socket
timeout = 1 minute
}
history.message.ttl = 40 seconds
uid.timeout = 10 seconds
}
site {
socket {
name = site-socket

View File

@ -116,6 +116,8 @@ object BSON {
map get k flatMap reader.asInstanceOf[BSONReader[BSONValue, A]].readOpt
def getD[A](k: String, default: A)(implicit reader: BSONReader[_ <: BSONValue, A]): A =
getO[A](k) getOrElse default
def getsD[A](k: String)(implicit reader: BSONReader[_ <: BSONValue, List[A]]) =
getO[List[A]](k) getOrElse Nil
def str(k: String) = get[String](k)
def strO(k: String) = getO[String](k)

View File

@ -42,8 +42,7 @@ object Forecast {
uci: String,
san: String,
fen: String,
check: Option[Boolean],
dests: String) {
check: Option[Boolean]) {
def is(move: Move) = move.toUci.uci == uci
def is(move: Uci.Move) = move.uci == uci

View File

@ -0,0 +1,74 @@
package lila.study
import chess.format.Uci
import chess.{ Pos, Role, PromotableRole }
import reactivemongo.bson._
import lila.db.BSON
import lila.db.BSON._
import lila.db.BSON.BSONJodaDateTimeHandler
private object BSONHandlers {
import Study._
private implicit val PosBSONHandler = new BSONHandler[BSONString, Pos] {
def read(bsonStr: BSONString): Pos = Pos.posAt(bsonStr.value) err s"No such pos: ${bsonStr.value}"
def write(x: Pos) = BSONString(x.key)
}
implicit val ShapeBSONHandler = new BSON[Shape] {
def reads(r: Reader) = {
val brush = r str "b"
r.getO[Pos]("p") map { pos =>
Shape.Circle(brush, pos)
} getOrElse Shape.Arrow(brush, r.get[Pos]("o"), r.get[Pos]("d"))
}
def writes(w: Writer, t: Shape) = t match {
case Shape.Circle(brush, pos) => BSONDocument("b" -> brush, "p" -> pos.key)
case Shape.Arrow(brush, orig, dest) => BSONDocument("b" -> brush, "o" -> orig.key, "d" -> dest.key)
}
}
implicit val PromotableRoleHandler = new BSONHandler[BSONString, PromotableRole] {
def read(bsonStr: BSONString): PromotableRole = bsonStr.value.headOption flatMap Role.allPromotableByForsyth.get err s"No such role: ${bsonStr.value}"
def write(x: PromotableRole) = BSONString(x.forsyth.toString)
}
implicit val RoleHandler = new BSONHandler[BSONString, Role] {
def read(bsonStr: BSONString): Role = bsonStr.value.headOption flatMap Role.allByForsyth.get err s"No such role: ${bsonStr.value}"
def write(x: Role) = BSONString(x.forsyth.toString)
}
implicit val UciBSONHandler = new BSON[Uci] {
def reads(r: Reader) = {
r.getO[Pos]("o") map { orig =>
Uci.Move(orig, r.get[Pos]("d"), r.getO[PromotableRole]("p"))
} getOrElse Uci.Drop(r.get[Role]("r"), r.get[Pos]("p"))
}
def writes(w: Writer, u: Uci) = u match {
case Uci.Move(orig, dest, prom) => BSONDocument("o" -> orig, "d" -> dest, "p" -> prom)
case Uci.Drop(role, pos) => BSONDocument("r" -> role, "p" -> pos)
}
}
import Step.Move
private implicit val MoveBSONHandler = Macros.handler[Move]
private implicit def StepBSONHandler: BSON[Step] = new BSON[Step] {
def reads(r: Reader) = Step(
ply = r int "p",
move = r.getO[Move]("m"),
fen = r str "f",
check = r boolD "c",
variations = r.getsD[List[Step]]("v"))
def writes(w: Writer, s: Step) = BSONDocument(
"p" -> s.ply,
"m" -> s.move,
"f" -> s.fen,
"c" -> w.boolO(s.check),
"v" -> s.variations)
}
implicit val StudyBSONHandler = Macros.handler[Study]
}

View File

@ -0,0 +1,62 @@
package lila.study
import akka.actor._
import akka.pattern.ask
import com.typesafe.config.Config
import scala.concurrent.duration._
import lila.common.PimpedConfig._
import lila.hub.actorApi.map.Ask
import lila.socket.actorApi.GetVersion
import makeTimeout.short
final class Env(
config: Config,
system: ActorSystem,
hub: lila.hub.Env,
db: lila.db.Env) {
private val settings = new {
val CollectionStudy = config getString "collection.study"
val HistoryMessageTtl = config duration "history.message.ttl"
val UidTimeout = config duration "uid.timeout"
val SocketTimeout = config duration "socket.timeout"
val SocketName = config getString "socket.name"
}
import settings._
private val socketHub = system.actorOf(
Props(new lila.socket.SocketHubActor.Default[Socket] {
def mkActor(studyId: String) = new Socket(
studyId = studyId,
history = new lila.socket.History(ttl = HistoryMessageTtl),
getStudy = repo.byId,
uidTimeout = UidTimeout,
socketTimeout = SocketTimeout)
}), name = SocketName)
def version(studyId: Study.ID): Fu[Int] =
socketHub ? Ask(studyId, GetVersion) mapTo manifest[Int]
lazy val socketHandler = new SocketHandler(
hub = hub,
socketHub = socketHub)
lazy val jsonView = new JsonView
lazy val api = new StudyApi(
repo = repo,
jsonView = jsonView,
socketHub = socketHub)
private lazy val repo = new StudyRepo(coll = db(CollectionStudy))
}
object Env {
lazy val current: Env = "study" boot new Env(
config = lila.common.PlayApp loadConfig "study",
system = lila.common.PlayApp.system,
hub = lila.hub.Env.current,
db = lila.db.Env.current)
}

View File

@ -0,0 +1,8 @@
package lila.study
import play.api.libs.json._
import lila.common.PimpedJson._
final class JsonView {
}

View File

@ -0,0 +1,70 @@
package lila.study
import akka.actor._
import play.api.libs.iteratee.Concurrent
import play.api.libs.json._
import scala.concurrent.duration.Duration
import lila.hub.TimeBomb
import lila.socket.actorApi.{ Connected => _, _ }
import lila.socket.{ SocketActor, History, Historical }
private final class Socket(
studyId: String,
val history: History[Unit],
getStudy: Study.ID => Fu[Option[Study]],
uidTimeout: Duration,
socketTimeout: Duration) extends SocketActor[Socket.Member](uidTimeout) with Historical[Socket.Member, Unit] {
private val timeBomb = new TimeBomb(socketTimeout)
def receiveSpecific = {
case Socket.Reload =>
getStudy(studyId) foreach {
_ foreach { study =>
notifyVersion("reload", JsNull, ())
}
}
case PingVersion(uid, v) => {
ping(uid)
timeBomb.delay
withMember(uid) { m =>
history.since(v).fold(resync(m))(_ foreach sendMessage(m))
}
}
case Broom => {
broom
if (timeBomb.boom) self ! PoisonPill
}
case GetVersion => sender ! history.version
case Socket.Join(uid, userId, owner) =>
val (enumerator, channel) = Concurrent.broadcast[JsValue]
val member = Socket.Member(channel, userId, owner)
addMember(uid, member)
sender ! Socket.Connected(enumerator, member)
case Quit(uid) => quit(uid)
}
protected def shouldSkipMessageFor(message: Message, member: Socket.Member) = false
}
private object Socket {
case class Member(
channel: JsChannel,
userId: Option[String],
owner: Boolean) extends lila.socket.SocketMember {
val troll = false
}
case class Join(uid: String, userId: Option[String], owner: Boolean)
case class Connected(enumerator: JsEnumerator, member: Member)
case object Reload
}

View File

@ -0,0 +1,42 @@
package lila.study
import scala.concurrent.duration._
import akka.actor._
import akka.pattern.ask
import akka.actor.ActorSelection
import lila.common.PimpedJson._
import lila.hub.actorApi.map._
import lila.socket.actorApi.{ Connected => _, _ }
import lila.socket.Handler
import lila.user.User
import makeTimeout.short
private[study] final class SocketHandler(
hub: lila.hub.Env,
socketHub: ActorRef) {
def join(
studyId: Study.ID,
uid: String,
userId: Option[User.ID],
owner: Boolean): Fu[Option[JsSocketHandler]] = for {
socket socketHub ? Get(studyId) mapTo manifest[ActorRef]
join = Socket.Join(uid = uid, userId = userId, owner = owner)
handler Handler(hub, socket, uid, join, userId) {
case Socket.Connected(enum, member) =>
(controller(socket, studyId, uid, member), enum, member)
}
} yield handler.some
private def controller(
socket: ActorRef,
studyId: Study.ID,
uid: String,
member: Socket.Member): Handler.Controller = {
case ("p", o) => o int "v" foreach { v =>
socket ! PingVersion(uid, v)
}
}
}

View File

@ -0,0 +1,55 @@
package lila.study
import chess.Pos
import chess.format.Uci
import org.joda.time.DateTime
import lila.user.User
case class Study(
_id: Study.ID,
owner: User.ID,
gameId: Option[String],
steps: List[Study.Step],
shapes: List[Study.Shape],
createdAt: DateTime) {
import Study._
def id = _id
}
object Study {
type ID = String
type Brush = String
sealed trait Shape
object Shape {
case class Circle(brush: Brush, pos: Pos) extends Shape
case class Arrow(brush: Brush, orig: Pos, dest: Pos) extends Shape
}
case class Step(
ply: Int,
move: Option[Step.Move],
fen: String,
check: Boolean,
variations: List[List[Step]]) {
}
object Step {
case class Move(uci: Uci, san: String)
}
def make(
id: ID,
owner: User.ID,
gameId: Option[String]): Study = Study(
_id = id,
owner = owner,
gameId = gameId,
steps = Nil,
shapes = Nil,
createdAt = DateTime.now)
}

View File

@ -0,0 +1,14 @@
package lila.study
import org.joda.time.DateTime
import lila.hub.actorApi.map.Tell
import lila.hub.actorApi.SendTo
final class StudyApi(
repo: StudyRepo,
jsonView: JsonView,
socketHub: akka.actor.ActorRef) {
def byId = repo byId _
}

View File

@ -0,0 +1,22 @@
package lila.study
import org.joda.time.DateTime
import reactivemongo.bson.{ BSONDocument, BSONInteger, BSONRegex, BSONArray, BSONBoolean }
import scala.concurrent.duration._
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.Types.Coll
import lila.user.User
private final class StudyRepo(coll: Coll) {
import BSONHandlers._
def byId(id: Study.ID) = coll.find(selectId(id)).one[Study]
def exists(id: Study.ID) = coll.count(selectId(id).some).map(0<)
def insert(s: Study): Funit = coll.insert(s).void
private def selectId(id: Study.ID) = BSONDocument("_id" -> id)
}

View File

@ -0,0 +1,5 @@
package lila
import lila.socket.WithSocket
package object study extends PackageObject with WithPlay with WithSocket

View File

@ -51,7 +51,8 @@ object ApplicationBuild extends Build {
importer, tournament, simul, relation, report, pref, // simulation,
evaluation, chat, puzzle, tv, coordinate, blog, donation, qa,
history, worldMap, opening, video, shutup, push,
playban, insight, perfStat, slack, quote, challenge, explorer)
playban, insight, perfStat, slack, quote, challenge,
study, explorer)
lazy val moduleRefs = modules map projectToRef
lazy val moduleCPDeps = moduleRefs map { new sbt.ClasspathDependency(_, None) }
@ -227,6 +228,10 @@ object ApplicationBuild extends Build {
libraryDependencies ++= provided(play.api, RM, PRM)
)
lazy val study = project("study", Seq(common, db, hub, socket, game)).settings(
libraryDependencies ++= provided(play.api, RM, PRM)
)
lazy val playban = project("playban", Seq(common, db, game)).settings(
libraryDependencies ++= provided(play.api, RM, PRM)
)