Activity, watchers, ping, and more

pull/1/merge
Thibault Duplessis 2012-03-18 21:10:51 +01:00
parent 1698f10645
commit a3e557ab50
14 changed files with 152 additions and 33 deletions

View File

@ -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)
}
}

View File

@ -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))

View File

@ -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")

View File

@ -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^"

View File

@ -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)

View 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(

View File

@ -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"
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)]] = {

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,6 @@
package lila.system
package memo
import scalaz.effects._
final class UsernameMemo(timeout: Int) extends BooleanExpiryMemo(timeout)

View File

@ -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)
}