Activity, watchers, ping, and more
parent
1698f10645
commit
a3e557ab50
|
@ -8,12 +8,9 @@ import mvc._
|
|||
|
||||
object Application extends LilaController {
|
||||
|
||||
private val syncer = env.syncer
|
||||
private val server = env.server
|
||||
|
||||
def sync(id: String, color: String, version: Int, fullId: Option[String]) =
|
||||
def sync(gameId: String, color: String, version: Int, fullId: Option[String]) =
|
||||
Action {
|
||||
JsonOk(env.syncer.sync(id, color, version, fullId).unsafePerformIO)
|
||||
JsonOk(env.syncer.sync(gameId, color, version, fullId).unsafePerformIO)
|
||||
}
|
||||
|
||||
def move(fullId: String) = Action { implicit request ⇒
|
||||
|
@ -21,4 +18,13 @@ object Application extends LilaController {
|
|||
env.server.play(fullId, move._1, move._2, move._3).unsafePerformIO
|
||||
})
|
||||
}
|
||||
|
||||
def ping() = Action { implicit request =>
|
||||
JsonOk(env.pinger.ping(
|
||||
get("username"),
|
||||
get("player_key"),
|
||||
get("watcher"),
|
||||
get("get_nb_watchers")
|
||||
).unsafePerformIO)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,10 @@ object Internal extends LilaController {
|
|||
ValidIOk[JoinData](joinForm)(join ⇒ api.join(fullId, join._1, join._2))
|
||||
}
|
||||
|
||||
def activity(gameId: String, color: String) = Action {
|
||||
Ok(api.activity(gameId, color).toString) as TEXT
|
||||
}
|
||||
|
||||
def acceptRematch(gameId: String, newGameId: String, color: String) = Action { implicit request ⇒
|
||||
ValidIOk[RematchData](rematchForm)(rematch ⇒
|
||||
api.acceptRematch(gameId, newGameId, color, rematch._1, rematch._2))
|
||||
|
|
|
@ -11,13 +11,11 @@ import scala.io.Codec
|
|||
import com.codahale.jerkson.Json
|
||||
import scalaz.effects.IO
|
||||
|
||||
trait LilaController extends Controller {
|
||||
trait LilaController extends Controller with ContentTypes {
|
||||
|
||||
lazy val env = HttpEnv.static
|
||||
|
||||
val json = "application/json"
|
||||
|
||||
def JsonOk(map: Map[String, Any]) = Ok(Json generate map) as json
|
||||
def JsonOk(map: Map[String, Any]) = Ok(Json generate map) as JSON
|
||||
|
||||
def ValidOk(valid: Valid[Unit]) = valid.fold(
|
||||
e ⇒ BadRequest(e.list mkString "\n"),
|
||||
|
@ -32,6 +30,12 @@ trait LilaController extends Controller {
|
|||
|
||||
def IOk(op: IO[Unit]) = Ok(op.unsafePerformIO)
|
||||
|
||||
def get(name: String)(implicit request: Request[_]) =
|
||||
request.queryString get name flatMap (_.headOption)
|
||||
|
||||
def getInt(name: String)(implicit request: Request[_]) =
|
||||
get(name)(request) map (_.toInt)
|
||||
|
||||
// I like Unit requests.
|
||||
implicit def wUnit: Writeable[Unit] =
|
||||
Writeable[Unit](_ ⇒ Codec toUTF8 "ok")
|
||||
|
|
|
@ -11,13 +11,19 @@ redis {
|
|||
port = 6379
|
||||
}
|
||||
sync {
|
||||
duration = 10000
|
||||
sleep = 200
|
||||
duration = 7 seconds
|
||||
sleep = 200 milliseconds
|
||||
}
|
||||
memo {
|
||||
version.timeout = 1800
|
||||
alive.hard_timeout = 100
|
||||
alive.soft_timeout = 6
|
||||
version.timeout = 30 minutes
|
||||
alive.hard_timeout = 100 seconds
|
||||
alive.soft_timeout = 6 seconds
|
||||
watcher.timeout = 6 seconds
|
||||
username.timeout = 10 seconds
|
||||
}
|
||||
crafty {
|
||||
exec_path = "/usr/bin/crafty"
|
||||
book_path = "/usr/share/crafty"
|
||||
}
|
||||
|
||||
application.secret="CiebwjgIM9cHQ;I?Xk:sfqDJ;BhIe:jsL?r=?IPF[saf>s^r0]?0grUq4>q?5mP^"
|
||||
|
|
|
@ -2,9 +2,12 @@
|
|||
# This file defines all application routes (Higher priority routes first)
|
||||
# ~~~~
|
||||
|
||||
# Public API
|
||||
POST /move/:fullId controllers.Application.move(fullId: String)
|
||||
GET /sync/:id/:color/:version/:fullId controllers.Application.sync(id: String, color: String, version: Int, fullId: Option[String])
|
||||
GET /sync/:gameId/:color/:version/:fullId controllers.Application.sync(gameId: String, color: String, version: Int, fullId: Option[String])
|
||||
GET /ping controllers.Application.ping()
|
||||
|
||||
# Private API
|
||||
POST /internal/update-version/:gameId controllers.Internal.updateVersion(gameId: String)
|
||||
POST /internal/end/:gameId controllers.Internal.end(gameId: String)
|
||||
POST /internal/talk/:fullId controllers.Internal.talk(fullId: String)
|
||||
|
@ -12,6 +15,7 @@ POST /internal/join/:fullId controllers.Internal.join(fullId: String
|
|||
POST /internal/reload-table/:gameId controllers.Internal.reloadTable(gameId: String)
|
||||
POST /internal/accept-rematch/:gameId/:newGameId/:color controllers.Internal.acceptRematch(gameId: String, newGameId: String, color: String)
|
||||
POST /internal/alive/:gameId/:color controllers.Internal.alive(gameId: String, color: String)
|
||||
GET /internal/activity/:gameId/:color controllers.Internal.activity(gameId: String, color: String)
|
||||
|
||||
# Map static resources from the /public folder to the /assets URL path
|
||||
GET /assets/*file controllers.Assets.at(path="/public", file)
|
||||
|
|
|
@ -45,9 +45,12 @@ object ApplicationBuild extends Build with Resolvers with Dependencies {
|
|||
val lila = PlayProject("lila", mainLang = SCALA, settings = buildSettings).settings(
|
||||
libraryDependencies ++= Seq(scalaz),
|
||||
// Adds system code to continuous triggers
|
||||
watchSources <+= baseDirectory map { _ / "system/src/main/scala" },
|
||||
// Adds chess code to continuous triggers
|
||||
watchSources <+= baseDirectory map { _ / "chess/src/main/scala" }
|
||||
//watchSources <+= baseDirectory map { _ / "system/src/main/scala" }
|
||||
//// Adds chess code to continuous triggers
|
||||
////watchSources <+= baseDirectory map { _ / "chess/src/main/scala" }
|
||||
//scalaSource in Compile <<= baseDirectory / "system/src/main/scala",
|
||||
//sourceDirectory in Compile <<= baseDirectory / "system/src/main/scala",
|
||||
watchSources <++= baseDirectory map { path => ((path / "system") ** "*.scala").get }
|
||||
) dependsOn (system)
|
||||
|
||||
lazy val system = Project("system", file("system"), settings = buildSettings).settings(
|
||||
|
|
|
@ -60,6 +60,9 @@ final class InternalApi(
|
|||
_ ← aliveMemo.put(gameId, color)
|
||||
} yield ()
|
||||
|
||||
def activity(gameId: String, colorName: String): Int =
|
||||
Color(colorName) some { aliveMemo.activity(gameId, _) } none 0
|
||||
|
||||
private def ioColor(colorName: String): IO[Color] = io {
|
||||
Color(colorName) err "Invalid color"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package lila.system
|
||||
|
||||
import model._
|
||||
import memo._
|
||||
import scalaz.effects._
|
||||
|
||||
final class Pinger(
|
||||
aliveMemo: AliveMemo,
|
||||
usernameMemo: UsernameMemo,
|
||||
watcherMemo: WatcherMemo) {
|
||||
|
||||
def ping(
|
||||
username: Option[String],
|
||||
playerKey: Option[String],
|
||||
watcherKey: Option[String],
|
||||
getNbWatchers: Option[String]): IO[Map[String, Any]] = for {
|
||||
_ ← optionIO(playerKey, aliveMemo.put)
|
||||
_ ← optionIO(username, usernameMemo.put)
|
||||
_ ← optionIO(watcherKey, watcherMemo.put)
|
||||
} yield flatten(Map(
|
||||
"nbp" -> Some(aliveMemo.count),
|
||||
"nbw" -> (getNbWatchers map watcherMemo.count)
|
||||
))
|
||||
|
||||
private def flatten[A, B](map: Map[A, Option[B]]): Map[A, B] = map collect {
|
||||
case (k, Some(v)) ⇒ k -> v
|
||||
} toMap
|
||||
|
||||
private def optionIO[A](oa: Option[A], f: A ⇒ IO[Unit]) =
|
||||
oa map f getOrElse io(Unit)
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package lila.system
|
||||
|
||||
import com.mongodb.casbah.MongoConnection
|
||||
import com.mongodb.casbah.commons.conversions.scala._
|
||||
import com.typesafe.config._
|
||||
|
||||
import ai._
|
||||
|
@ -26,10 +25,17 @@ trait SystemEnv {
|
|||
repo = gameRepo,
|
||||
versionMemo = versionMemo,
|
||||
aliveMemo = aliveMemo,
|
||||
duration = config getInt "sync.duration",
|
||||
sleep = config getInt "sync.sleep")
|
||||
duration = (config getMilliseconds "sync.duration").toInt,
|
||||
sleep = (config getMilliseconds "sync.sleep").toInt)
|
||||
|
||||
lazy val ai: Ai = new CraftyAi
|
||||
def pinger = new Pinger(
|
||||
aliveMemo = aliveMemo,
|
||||
usernameMemo = usernameMemo,
|
||||
watcherMemo = watcherMemo)
|
||||
|
||||
lazy val ai: Ai = new CraftyAi(
|
||||
execPath = config getString "crafty.exec_path",
|
||||
bookPath = Some(config getString "crafty.book_path"))
|
||||
|
||||
def gameRepo = new GameRepo(
|
||||
mongodb(config getString "mongo.collection.game"))
|
||||
|
@ -41,11 +47,17 @@ trait SystemEnv {
|
|||
|
||||
lazy val versionMemo = new VersionMemo(
|
||||
repo = gameRepo,
|
||||
timeout = config getInt "memo.version.timeout")
|
||||
timeout = (config getMilliseconds "memo.version.timeout").toInt)
|
||||
|
||||
lazy val aliveMemo = new AliveMemo(
|
||||
hardTimeout = config getInt "memo.alive.hard_timeout",
|
||||
softTimeout = config getInt "memo.alive.soft_timeout")
|
||||
hardTimeout = (config getMilliseconds "memo.alive.hard_timeout").toInt,
|
||||
softTimeout = (config getMilliseconds "memo.alive.soft_timeout").toInt)
|
||||
|
||||
lazy val usernameMemo = new UsernameMemo(
|
||||
timeout = (config getMilliseconds "memo.username.timeout").toInt)
|
||||
|
||||
lazy val watcherMemo = new WatcherMemo(
|
||||
timeout = (config getMilliseconds "memo.watcher.timeout").toInt)
|
||||
}
|
||||
|
||||
object SystemEnv extends EnvBuilder {
|
||||
|
|
|
@ -11,7 +11,7 @@ import scala.sys.process.Process
|
|||
import scalaz.effects._
|
||||
|
||||
final class CraftyAi(
|
||||
execPath: String = "crafty",
|
||||
execPath: String,
|
||||
bookPath: Option[String] = None) extends Ai {
|
||||
|
||||
def apply(dbGame: DbGame): IO[Valid[(Game, Move)]] = {
|
||||
|
|
|
@ -15,7 +15,17 @@ final class AliveMemo(hardTimeout: Int, softTimeout: Int) {
|
|||
cache getIfPresent toKey(gameId, color)
|
||||
}
|
||||
|
||||
def put(gameId: String, color: Color): IO[Unit] = put(gameId, color, now)
|
||||
def put(key: String): IO[Unit] = io {
|
||||
cache.put(key, now)
|
||||
}
|
||||
|
||||
def put(gameId: String, color: Color): IO[Unit] = io {
|
||||
cache.put(toKey(gameId, color), now)
|
||||
}
|
||||
|
||||
def put(gameId: String, color: Color, time: Long): IO[Unit] = io {
|
||||
cache.put(toKey(gameId, color), time)
|
||||
}
|
||||
|
||||
def transfer(g1: String, c1: Color, g2: String, c2: Color): IO[Unit] = io {
|
||||
get(g1, c1) foreach { put(g2, c2, _) }
|
||||
|
@ -32,7 +42,10 @@ final class AliveMemo(hardTimeout: Int, softTimeout: Int) {
|
|||
*/
|
||||
def activity(game: DbGame, color: Color): Int =
|
||||
if (game.player(color).isAi) 2
|
||||
else latency(game.id, color) |> { l ⇒
|
||||
else activity(game.id, color)
|
||||
|
||||
def activity(gameId: String, color: Color): Int =
|
||||
latency(gameId, color) |> { l ⇒
|
||||
if (l <= softTimeout) 2
|
||||
else if (l <= hardTimeout) 1
|
||||
else 0
|
||||
|
@ -40,11 +53,7 @@ final class AliveMemo(hardTimeout: Int, softTimeout: Int) {
|
|||
|
||||
def count = cache.size
|
||||
|
||||
private def put(gameId: String, color: Color, time: Long): IO[Unit] = io {
|
||||
cache.put(toKey(gameId, color), time)
|
||||
}
|
||||
|
||||
private def toKey(gameId: String, color: Color) = gameId + "." + color.letter
|
||||
def toKey(gameId: String, color: Color) = gameId + "." + color.letter
|
||||
|
||||
private def now = System.currentTimeMillis
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import scalaz.effects._
|
||||
import collection.JavaConversions._
|
||||
|
||||
abstract class BooleanExpiryMemo(timeout: Int) {
|
||||
|
||||
protected val cache = Builder.expiry[String, Boolean](timeout)
|
||||
|
||||
def get(key: String): Boolean = Option {
|
||||
cache getIfPresent key
|
||||
} getOrElse false
|
||||
|
||||
def put(key: String): IO[Unit] = io {
|
||||
cache.put(key, true)
|
||||
}
|
||||
|
||||
def keys: Iterable[String] = cache.asMap.keys
|
||||
|
||||
def count = cache.size
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
final class UsernameMemo(timeout: Int) extends BooleanExpiryMemo(timeout)
|
|
@ -0,0 +1,9 @@
|
|||
package lila.system
|
||||
package memo
|
||||
|
||||
import scalaz.effects._
|
||||
|
||||
final class WatcherMemo(timeout: Int) extends BooleanExpiryMemo(timeout) {
|
||||
|
||||
def count(prefix: String): Int = keys count (_ startsWith prefix)
|
||||
}
|
Loading…
Reference in New Issue