study WIP
parent
328fda1ba4
commit
5a7d7f61b4
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package lila.study
|
||||
|
||||
import play.api.libs.json._
|
||||
|
||||
import lila.common.PimpedJson._
|
||||
|
||||
final class JsonView {
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 _
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package lila
|
||||
|
||||
import lila.socket.WithSocket
|
||||
|
||||
package object study extends PackageObject with WithPlay with WithSocket
|
|
@ -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)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue