I\'m pushing messages in Play Framework WebSockets using Concurrent.unicast[JsValue]
, and I want to optimize sending the same message to multiple users. Is it p
Short Answer
val (enumerator, channel) = Concurrent.broadcast[String]
use above thing globally
Long answer
package controllers
import play.api._
import play.api.mvc._
import play.libs.Akka
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.api.libs.iteratee.Iteratee
import play.api.libs.iteratee.Enumerator
import akka.actor.Props
import akka.pattern.ask
import akka.util.Timeout
import Room._
import scala.concurrent.duration._
object Application extends Controller {
def index = Action {
Ok(views.html.index("Your new application is ready."))
}
/**
* get actor ref
*/
val room = Akka.system.actorOf(Props[Room])
/**
* websocket action
*/
def chat(name: String) = WebSocket.async[String](implicit request => {
implicit val timeout = Timeout(1 seconds)
(room ? Join(name)).mapTo[(Iteratee[String, _], Enumerator[String])]
})
}
//Here is the actor
package controllers
import akka.actor.Actor
import play.api.libs.iteratee.Concurrent
import play.api.libs.iteratee.Iteratee
import play.api.libs.concurrent.Execution.Implicits.defaultContext
object Room {
case class Join(name: String)
case class Broadcast(msg: String)
case object Quit
}
class Room extends Actor {
/**
* here is the meat
* Creating channel globally is important here
* This can be accessed across all cases in receive method
* pushing the message into this channel and returning this enumerator to all ,
* broadcasts the message
*/
val (enumerator, channel) = Concurrent.broadcast[String]
/**
* keep track of users
*/
val users = scala.collection.mutable.Set[String]()
import Room._
def receive = {
case Join(name) => {
/**
* add new users
*/
if(!users.contains(name)) {
users += name
val iteratee = Iteratee.foreach[String]{
msg => {
/**
* process messages from users
* here we are broadcast it to all other users
*/
self ! Broadcast(msg)
}
}.map( _ => {
/**
* user closed his websocket.
* remove him from users
*/
users -= name
})
/**
* send iteratee, enumerator pair to the sender of join message
*/
sender ! (iteratee, enumerator)
} else {
/**
* already added users
*/
val iteratee = Iteratee.ignore[String]
/**
* send iteratee and enumerator pair
*/
sender ! (iteratee, enumerator)
}
}
case Broadcast(msg) => channel push(msg)
/**
* close the common channel only when actor is stopped
*/
case Quit => channel eofAndEnd(); context.stop(self)
}
}