implement SubchannelClassification in MainBus, fixes #1340

This commit is contained in:
Roland 2011-11-10 00:26:53 +01:00
parent 70ae4e1984
commit b2d548bd0e
5 changed files with 261 additions and 15 deletions

View file

@ -8,6 +8,7 @@ import akka.actor.ActorRef
import akka.util.Index
import java.util.concurrent.ConcurrentSkipListSet
import java.util.Comparator
import akka.util.{ Subclassification, SubclassifiedIndex }
/**
* Represents the base type for EventBuses
@ -54,14 +55,14 @@ trait ActorEventBus extends EventBus {
/**
* Can be mixed into an EventBus to specify that the Classifier type is ActorRef
*/
trait ActorClassifier { self: EventBus
trait ActorClassifier { this: EventBus
type Classifier = ActorRef
}
/**
* Can be mixed into an EventBus to specify that the Classifier type is a Function from Event to Boolean (predicate)
*/
trait PredicateClassifier { self: EventBus
trait PredicateClassifier { this: EventBus
type Classifier = Event Boolean
}
@ -71,7 +72,7 @@ trait PredicateClassifier { self: EventBus ⇒
*
* The compareSubscribers need to provide a total ordering of the Subscribers
*/
trait LookupClassification { self: EventBus
trait LookupClassification { this: EventBus
protected final val subscribers = new Index[Classifier, Subscriber](mapSize(), new Comparator[Subscriber] {
def compare(a: Subscriber, b: Subscriber): Int = compareSubscribers(a, b)
@ -109,6 +110,70 @@ trait LookupClassification { self: EventBus ⇒
}
}
/**
* Classification which respects relationships between channels: subscribing
* to one channel automatically and idempotently subscribes to all sub-channels.
*/
trait SubchannelClassification { this: EventBus
implicit val subclassification: Subclassification[Classifier]
// must be lazy to avoid initialization order problem with subclassification
private lazy val subscriptions = new SubclassifiedIndex[Classifier, Subscriber]()
@volatile
private var cache = Map.empty[Classifier, Set[Subscriber]]
protected def subscribers = cache.values.flatten
/**
* Returns the Classifier associated with the given Event
*/
protected def classify(event: Event): Classifier
/**
* Publishes the given Event to the given Subscriber
*/
protected def publish(event: Event, subscriber: Subscriber): Unit
def subscribe(subscriber: Subscriber, to: Classifier): Boolean = subscriptions.synchronized {
val diff = subscriptions.addValue(to, subscriber)
if (diff.isEmpty) false
else {
cache = cache ++ diff
true
}
}
def unsubscribe(subscriber: Subscriber, from: Classifier): Boolean = subscriptions.synchronized {
val diff = subscriptions.removeValue(from, subscriber)
if (diff.isEmpty) false
else {
cache = cache ++ diff
true
}
}
def unsubscribe(subscriber: Subscriber): Unit = subscriptions.synchronized {
val diff = subscriptions.removeValue(subscriber)
if (diff.nonEmpty) cache = cache ++ diff
}
def publish(event: Event): Unit = {
val c = classify(event)
val recv = cache get c getOrElse {
subscriptions.synchronized {
cache get c getOrElse {
val diff = subscriptions.addKey(c)
cache = cache ++ diff
cache(c)
}
}
}
recv foreach (publish(event, _))
}
}
/**
* Maps Classifiers to Subscribers and selects which Subscriber should receive which publication through scanning through all Subscribers
* through the matches(classifier, event) method
@ -169,7 +234,7 @@ trait ScanningClassification { self: EventBus ⇒
/**
* Maps ActorRefs to ActorRefs to form an EventBus where ActorRefs can listen to other ActorRefs
*/
trait ActorClassification { self: ActorEventBus with ActorClassifier
trait ActorClassification { this: ActorEventBus with ActorClassifier
import java.util.concurrent.ConcurrentHashMap
import scala.annotation.tailrec