More hook stuff

pull/1/merge
Thibault Duplessis 2012-03-23 11:20:58 +01:00
parent 74e1c9a0e1
commit 75f416f6aa
17 changed files with 203 additions and 113 deletions

View File

@ -9,8 +9,7 @@ import mvc._
object AppXhrC extends LilaController {
private val xhr = env.appXhr
private val pinger = env.pinger
private val syncer = env.syncer
private val syncer = env.appSyncer
def sync(gameId: String, color: String, version: Int, fullId: String) = Action {
JsonOk(syncer.sync(gameId, color, version, Some(fullId)).unsafePerformIO)
@ -27,7 +26,7 @@ object AppXhrC extends LilaController {
}
def ping() = Action { implicit request =>
JsonOk(pinger.ping(
JsonOk(env.pinger.ping(
get("username"),
get("player_key"),
get("watcher"),

View File

@ -19,7 +19,11 @@ object LobbyApiC extends LilaController {
IOk(api.inc)
}
def create(hookOwnerId: String) = Action { implicit request =>
def create(hookOwnerId: String) = Action {
IOk(api.create(hookOwnerId))
}
def remove(hookId: String) = Action {
IOk(api.remove(hookId))
}
}

View File

@ -9,13 +9,14 @@ import mvc._
object LobbyXhrC extends LilaController {
private val xhr = env.lobbyXhr
private val syncer = env.lobbySyncer
def syncWithHook(hookId: String) = sync(Some(hookId))
def syncWithoutHook() = sync(None)
private def sync(hookId: Option[String]) = Action { implicit request =>
JsonOk(xhr.sync(
JsonOk(syncer.sync(
hookId,
getIntOr("auth", 0) == 1,
getIntOr("state", 0)

View File

@ -17,7 +17,7 @@ sync {
sleep = 200 milliseconds
}
lobby {
poll {
sync {
duration = 10 seconds
sleep = 300 milliseconds
}

View File

@ -32,6 +32,7 @@ GET /api/lobby/preload/:hookId controllers.LobbyXhrC.syncWithHook(hookId: S
GET /api/lobby/preload controllers.LobbyXhrC.syncWithoutHook
POST /api/lobby/inc controllers.LobbyApiC.inc
POST /api/lobby/create/:hookOwnerId controllers.LobbyApiC.create(hookOwnerId: String)
POST /api/lobby/remove/:hookId controllers.LobbyApiC.remove(hookId: String)
# Useless, but play2 needs it
GET /assets/*file controllers.Assets.at(path="/public", file)

View File

@ -9,7 +9,7 @@ import scala.annotation.tailrec
import scala.math.max
import org.apache.commons.lang3.StringEscapeUtils.escapeXml
final class Syncer(
final class AppSyncer(
gameRepo: GameRepo,
versionMemo: VersionMemo,
aliveMemo: AliveMemo,
@ -42,7 +42,7 @@ final class Syncer(
) filterValues (null !=)
} getOrElse failMap
}
} except (e io(failMap))
} except (e {println(e.getMessage);io(failMap)})
private def renderEvents(events: List[Event], isPrivate: Boolean) =
if (isPrivate) events map {

View File

@ -27,4 +27,9 @@ case class LobbyApi(
_ (lobbyMemo++)
_ hookMemo put hookOwnerId
} yield ()
def remove(hookId: String): IO[Unit] = for {
_ hookRepo removeId hookId
_ (lobbyMemo++)
} yield ()
}

View File

@ -0,0 +1,75 @@
package lila.system
import model._
import memo._
import db._
import scalaz.effects._
import scala.annotation.tailrec
import scala.math.max
final class LobbySyncer(
hookRepo: HookRepo,
gameRepo: GameRepo,
lobbyMemo: LobbyMemo,
duration: Int,
sleep: Int) {
type Response = Map[String, Any]
def sync(
myHookId: Option[String],
auth: Boolean,
version: Int): IO[Response] = for {
newVersion versionWait(version)
hooks if (auth) hookRepo.allOpen else hookRepo.allOpenCasual
response {
val response = () stdResponse(newVersion, hooks, myHookId)
myHookId some { hookResponse(_, response) } none io { response() }
}
} yield response
def hookResponse(myHookId: String, response: () Response): IO[Response] =
hookRepo ownedHook myHookId flatMap { hookOption
hookOption.fold(
hook hook.game.fold(
ref gameRepo game ref.getId.toString.pp map { game
Map("redirect" -> (game fullIdOf game.creatorColor))
},
io { response() }
),
io { Map("redirect" -> "") }
)
}
def stdResponse(
version: Int,
hooks: List[Hook],
myHookId: Option[String]): Response = Map(
"state" -> version,
"pool" -> {
if (hooks.nonEmpty) Map("hooks" -> renderHooks(hooks, myHookId).toMap)
else Map("message" -> "No game available right now, create one!")
},
"chat" -> null,
"timeline" -> ""
)
private def renderHooks(hooks: List[Hook], myHookId: Option[String]) = for {
hook hooks
} yield hook.id -> {
hook.render ++ {
if (myHookId == Some(hook.ownerId))
Map("action" -> "cancel", "id" -> myHookId)
else Map("action" -> "join", "id" -> hook.id)
}
}
private def versionWait(version: Int): IO[Int] = io {
@tailrec
def wait(loop: Int): Int = {
if (loop == 0 || lobbyMemo.version != version) lobbyMemo.version
else { Thread sleep sleep; wait(loop - 1) }
}
wait(max(1, duration / sleep))
}
}

View File

@ -9,42 +9,6 @@ import scala.math.max
final class LobbyXhr(
hookRepo: HookRepo,
lobbyMemo: LobbyMemo,
duration: Int,
sleep: Int) {
def sync(
myHookId: Option[String],
auth: Boolean,
version: Int): IO[Map[String, Any]] = for {
newVersion versionWait(version)
hooks if (auth) hookRepo.allOpen else hookRepo.allOpenCasual
} yield Map(
"state" -> newVersion,
"pool" -> {
if (hooks.nonEmpty) Map("hooks" -> renderHooks(hooks, myHookId).toMap)
else Map("message" -> "No game available right now, create one!")
},
"chat" -> null,
"timeline" -> ""
)
private def renderHooks(hooks: List[Hook], myHookId: Option[String]) = for {
hook hooks
} yield hook.id -> {
hook.render ++ {
if (myHookId == Some(hook.ownerId))
Map("action" -> "cancel", "id" -> myHookId)
else Map("action" -> "join", "id" -> hook.id)
}
}
private def versionWait(version: Int): IO[Int] = io {
@tailrec
def wait(loop: Int): Int = {
if (loop == 0 || lobbyMemo.version != version) lobbyMemo.version
else { Thread sleep sleep; wait(loop - 1) }
}
wait(max(1, duration / sleep))
}
gameRepo: GameRepo,
lobbyMemo: LobbyMemo) {
}

View File

@ -20,11 +20,17 @@ final class SystemEnv(config: Config) {
versionMemo = versionMemo,
aliveMemo = aliveMemo)
lazy val appSyncer = new AppSyncer(
gameRepo = gameRepo,
versionMemo = versionMemo,
aliveMemo = aliveMemo,
duration = getMilliseconds("sync.duration"),
sleep = getMilliseconds("sync.sleep"))
lazy val lobbyXhr = new LobbyXhr(
hookRepo = hookRepo,
lobbyMemo = lobbyMemo,
duration = getMilliseconds("lobby.poll.duration"),
sleep = getMilliseconds("lobby.poll.sleep"))
gameRepo = gameRepo,
lobbyMemo = lobbyMemo)
lazy val lobbyApi = new LobbyApi(
hookRepo = hookRepo,
@ -34,12 +40,12 @@ final class SystemEnv(config: Config) {
aliveMemo = aliveMemo,
hookMemo = hookMemo)
lazy val syncer = new Syncer(
lazy val lobbySyncer = new LobbySyncer(
hookRepo = hookRepo,
gameRepo = gameRepo,
versionMemo = versionMemo,
aliveMemo = aliveMemo,
duration = getMilliseconds("sync.duration"),
sleep = getMilliseconds("sync.sleep"))
lobbyMemo = lobbyMemo,
duration = getMilliseconds("lobby.sync.duration"),
sleep = getMilliseconds("lobby.sync.sleep"))
lazy val pinger = new Pinger(
aliveMemo = aliveMemo,

View File

@ -45,7 +45,7 @@ class GameRepo(collection: MongoCollection)
update(DBObject("_id" -> a.id), diff(encode(a), encode(b)), false, false)
}
private def diff(a: RawDbGame, b: RawDbGame): DBObject = {
def diff(a: RawDbGame, b: RawDbGame): MongoDBObject = {
val builder = MongoDBObject.newBuilder
def d[A](name: String, f: RawDbGame A) {
if (f(a) != f(b)) builder += name -> f(b)
@ -70,7 +70,7 @@ class GameRepo(collection: MongoCollection)
d("clock.timer", _.clock.get.timer)
}
MongoDBObject("$set" -> builder.result)
MongoDBObject("$set" -> builder.result.pp)
}
def insert(game: DbGame): IO[Option[String]] = io {

View File

@ -12,6 +12,14 @@ import scalaz.effects._
class HookRepo(collection: MongoCollection)
extends SalatDAO[Hook, String](collection) {
def hook(hookId: String): IO[Option[Hook]] = io {
findOneByID(hookId)
}
def ownedHook(ownerId: String): IO[Option[Hook]] = io {
findOne(DBObject("ownerId" -> ownerId))
}
def allOpen = hookList(DBObject(
"match" -> false
))
@ -24,4 +32,8 @@ class HookRepo(collection: MongoCollection)
def hookList(query: DBObject): IO[List[Hook]] = io {
find(query) sort DBObject("createdAt" -> 1) toList
}
def removeId(id: String): IO[Unit] = io {
remove(DBObject("id" -> id))
}
}

View File

@ -14,6 +14,7 @@ case class DbGame(
turns: Int,
clock: Option[Clock],
lastMove: Option[String],
creatorColor: Color,
positionHashes: String = "",
castles: String = "KQkq",
isRated: Boolean = false,

View File

@ -2,6 +2,7 @@ package lila.system
package model
import com.novus.salat.annotations._
import com.mongodb.DBRef
case class Hook(
@Key("_id") id: String,
@ -15,7 +16,8 @@ case class Hook(
elo: Option[Int],
`match`: Boolean,
eloRange: Option[String],
engine: Boolean) {
engine: Boolean,
game: Option[DBRef]) {
def realVariant = Variant(variant) | Standard

View File

@ -15,6 +15,7 @@ case class RawDbGame(
turns: Int,
clock: Option[RawDbClock],
lastMove: Option[String],
creatorColor: String = "white",
positionHashes: String = "",
castles: String = "KQkq",
isRated: Boolean = false,
@ -24,6 +25,7 @@ case class RawDbGame(
whitePlayer players find (_.color == "white") flatMap (_.decode)
blackPlayer players find (_.color == "black") flatMap (_.decode)
trueStatus Status(status)
trueCreatorColor Color(creatorColor)
trueVariant Variant(variant)
validClock = clock flatMap (_.decode)
if validClock.isDefined == clock.isDefined
@ -36,6 +38,7 @@ case class RawDbGame(
turns = turns,
clock = validClock,
lastMove = lastMove,
creatorColor = trueCreatorColor,
positionHashes = positionHashes,
castles = castles,
isRated = isRated,
@ -55,6 +58,7 @@ object RawDbGame {
turns = turns,
clock = clock map RawDbClock.encode,
lastMove = lastMove,
creatorColor = creatorColor.name,
positionHashes = positionHashes,
castles = castles,
isRated = isRated,

View File

@ -21,7 +21,8 @@ trait Fixtures {
status = Created,
turns = 0,
lastMove = None,
clock = None
clock = None,
creatorColor = White
)
def newDbGameWithBoard(b: Board) = newDbGame.update(Game(b), anyMove)
@ -55,7 +56,8 @@ trait Fixtures {
status = Resign,
turns = 24,
clock = None,
lastMove = None
lastMove = None,
creatorColor = White
)
lazy val dbGame2 = DbGame(
@ -72,7 +74,8 @@ trait Fixtures {
whiteTime = 196250,
blackTime = 304100
).some,
lastMove = Some("a7 c7")
lastMove = Some("a7 c7"),
creatorColor = White
)
// { "_id" : "7xfxoj4v", "clock" : null, "createdAt" : ISODate("2012-01-28T01:55:33Z"), "creatorColor" : Black, "initialFen" : "rkbbnnqr/pppppppp/8/8/8/8/PPPPPPPP/RKBBNNQR w KQkq - 0 1", "lastMove" : "a3 a8", "pgn" : "d4 d5 f3 Bf5 Ne3 Nd6 Bd2 c6 g4 Bb6 gxf5 Nd7 Qg5 f6 Qg4 h5 Qh4 Rh6 N1g2 Rg6 Qf2 Rg5 c3 e6 Kc1 exf5 Kb1 f4 Nxf4 Nf5 h4 Nxe3 hxg5 Nxd1 Qf1 Nxc3+ Bxc3 a5 Qf2 Nc5 Kc1 Ra6 Rh2 fxg5 dxc5 gxf4 cxb6 Rxb6 Rxh5 g6 Qxb6 Qe6 Qd8+ Qc8 Qxc8+ Kxc8 Rh2 a4 Kc2 b5 Rh7 c5 Bg7 a3 b3 c4 Bf6 cxb3+ axb3 b4 Be5 g5 Bd6 Kd8 Bxb4 d4 Rxa3 d3+ exd3 g4 Ra8#", "players" : [ { "aiLevel" : 1, "color" : White, "id" : "jqsx", "isAi" : true, "isWinner" : true, "ps" : "zb6 dB 6Q12 uN4 DN18 4r76 kk24 3r42 rp68 rP64 sP22 PP0 tp78 vp2 LP8 MP30" }, { "color" : Black, "id" : "7n7r", "ps" : "LB3 PB9 6Q51 IN11 sN5 PR41 7k55 MR17 qP37 zP59 rP7 tP1 DP23 Dp13 Ep49 NP15" } ], "status" : 30, "turns" : 81, "updatedAt" : ISODate("2012-01-28T02:01:28Z"), "userIds" : [ ], "variant" : 2, "winnerUserId" : "" }
@ -84,7 +87,8 @@ trait Fixtures {
status = Mate,
turns = 81,
clock = None,
lastMove = Some("a3 a8")
lastMove = Some("a3 a8"),
creatorColor = White
)
lazy val dbGame4 = DbGame(
@ -95,7 +99,8 @@ trait Fixtures {
status = Resign,
turns = 24,
clock = None,
lastMove = None
lastMove = None,
creatorColor = White
)
// from online prod DB
@ -114,7 +119,8 @@ trait Fixtures {
whiteTime = 27610,
blackTime = 60240
)),
lastMove = Some("d8 d2")
lastMove = Some("d8 d2"),
creatorColor = White
)
def newMove(

View File

@ -1,5 +1,6 @@
package lila.system
import model._
import lila.chess._
import scalaz.{ Success, Failure }
import scalaz.effects._
@ -11,68 +12,77 @@ class GameRepoTest extends SystemTest {
val repo = env.gameRepo
val anyGame = repo.findOne(DBObject()) flatMap repo.decode get // unsafe but who cares
"the game repo" should {
"find a game" in {
"by ID" in {
"non existing" in {
repo game "haha" must beIO.failure
}
"existing" in {
repo game anyGame.id must beIO.like {
case g g.id must_== anyGame.id
}
"diff" should {
"empty" in {
val raw = RawDbGame encode newDbGame
repo.diff(raw, raw) must_== MongoDBObject("$set" -> DBObject())
}
"pgn" in {
val raw = RawDbGame encode newDbGame
val newRaw = raw.copy(pgn = "foo")
repo.diff(raw, newRaw) must_== MongoDBObject("$set" -> DBObject("pgn" -> "foo"))
}
}
"find a game" should {
"by ID" in {
"non existing" in {
repo game "haha" must beIO.failure
}
"existing" in {
repo game anyGame.id must beIO.like {
case g g.id must_== anyGame.id
}
}
}
"find a player" in {
"by private ID" in {
"non existing" in {
repo player "huhu" must beIO.failure
}
"existing" in {
val player = anyGame.players.head
anyGame fullIdOf player map repo.player must beSome.like {
case iop iop must beIO.like {
case (g, p) p.id must_== player.id
}
}
}
}
"find a player" should {
"by private ID" in {
"non existing" in {
repo player "huhu" must beIO.failure
}
"by ID and color" in {
"non existing" in {
repo.player("haha", White) must beIO.failure
}
"existing" in {
val player = anyGame.players.head
repo.player(anyGame.id, player.color) must beIO.like {
"existing" in {
val player = anyGame.players.head
anyGame fullIdOf player map repo.player must beSome.like {
case iop iop must beIO.like {
case (g, p) p.id must_== player.id
}
}
}
}
"insert a new game" in {
val game = newDbGameWithRandomIds()
"find the saved game" in {
(for {
_ repo insert game
newGame repo game game.id
} yield newGame) must beIO.like {
case g g must_== game
}
"by ID and color" in {
"non existing" in {
repo.player("haha", White) must beIO.failure
}
}
"update a game" in {
val game = newDbGameWithRandomIds()
val updated = game.copy(turns = game.turns + 1)
"find the updated game" in {
(for {
_ repo insert game
_ repo save updated
newGame repo game game.id
} yield newGame) must beIO.like {
case g g must_== updated
"existing" in {
val player = anyGame.players.head
repo.player(anyGame.id, player.color) must beIO.like {
case (g, p) p.id must_== player.id
}
}
}
}
"insert a new game" should {
val game = newDbGameWithRandomIds()
"find the saved game" in {
(for {
_ repo insert game
newGame repo game game.id
} yield newGame) must beIO.like {
case g g must_== game
}
}
}
"update a game" should {
val game = newDbGameWithRandomIds()
val updated = game.copy(turns = game.turns + 1)
"find the updated game" in {
(for {
_ repo insert game
_ repo save updated
newGame repo game game.id
} yield newGame) must beIO.like {
case g g must_== updated
}
}
}
}