
scala.reactive.Reactive.scala Maven / Gradle / Ivy
The newest version!
package scala.reactive
import scala.annotation.tailrec
import scala.collection._
import scala.reactive.util._
import scala.reactive.calc.RefValFun
/** A basic reactive value.
*
* Reactive values, or simply, ''reactives'' are special objects that may produce events
* of a certain type `T`.
* Clients may subscribe side-effecting functions (i.e. callbacks)
* to these events with `onReaction`, `onEvent`, `onCase` and `on` --
* each of these methods will invoke the callback when an event
* is produced, but some may be more suitable depending on the use-case.
*
* A reactive produces events until it ''unreacts''.
* After the reactive value unreacts, it never produces an event again.
*
* Reactive values can also be manipulated using declarative combinators
* such as `map`, `filter`, `until`, `after` and `scanPast`:
*
* {{{
* def positiveSquares(r: Reactive[Int]) = r.map(x => x * x).filter(_ != 0)
* }}}
*
* With the exception of `onX` family of methods,
* operators passed to these declarative combinators should be pure --
* they should not have any side-effects.
*
* The result of applying a declarative combinator on `Reactive[T]` is usually another
* `Reactive[S]`, possibly with a different type parameter.
* Most declarative combinators return a `Subscription` object used to
* `unsubscribe` from their event source.
* This is not necessary in most situations,
* but can be used to prune the dataflow graph.
*
* Reactive values are specialized for `Int`, `Long` and `Double`.
*
* Every reactive value is bound to a specific `Isolate`.
* A reactive value will only produce events during the execution of that isolate --
* events are never triggered on a different isolate.
* It is forbidden to share reactive values between isolates --
* instead, an isolate should be attached to a specific channel.
*
* Every reactive value is either ''active'' or ''passive''.
* Active reactive values produce events irregardless if there are any subscribers
* to these events.
* Examples are mouse movement, file changes or operating system events.
* Here is an active reactive:
*
* {{{
* val emitter = new Reactive.Emitter[Int]
* emitter += 1 // events are produced regardless
* // of subscribers like evens below
* val evens = emitter.filter(_ % 2 == 0)
* }}}
*
* Passive reactive values produce events depending on whether there are any
* subscribers -- typically, each subscriber gets a sequence of events
* upon subscription.
*
* {{{
* val items = Reactive.items(Array(1, 2, 3))
* items.onEvent(println) // array elements are emitted on-demand
* items.onEvent(println) // twice -- once for each `onEvent` call
* }}}
*
* @author Aleksandar Prokopec
*
* @tparam T type of the events in this reactive value
*/
trait Reactive[@spec(Int, Long, Double) +T] {
self =>
/** Is there any other reactive that depends on the events
* produced by this reactive.
*
* Passive reactives, such as `Reactive.items` will always returns `false`.
* Other reactives will return `true` if there are any subscribers attached to them.
* This method is used internally to optimize and recycle some subscriptions away.
*/
private[reactive] def hasSubscriptions: Boolean
/** Attaches a new `reactor` to this reactive
* that is called multiple times when an event is produced
* and once when the reactive is terminated.
*
* Reactives can create events specifically for this reactor,
* in which case they are called ''passive''.
* A passive reactive can create events both synchronously and asynchronously,
* but it will only do so on its own isolate.
*
* An ''active'' reactive value will produce events irregardless of the reactors subscribed to it.
* Subscribing to an active reactive value only forwards those events that have
* been produced after the subscription started.
*
* @param reactor the reactor that accepts `react` and `unreact` events
* @return a subscription for unsubscribing from reactions
*/
def onReaction(reactor: Reactor[T]): Reactive.Subscription
/** A shorthand for `onReaction` -- the specified functions are invoked whenever there is an event or an unreaction.
*
* @param reactFunc called when this reactive produces an event
* @param unreactFunc called when this reactive unreacts
* @return a subscription for unsubscribing from reactions
*/
def onReactUnreact(reactFunc: T => Unit)(unreactFunc: =>Unit): Reactive.Subscription = onReaction(new Reactor[T] {
def react(event: T) = reactFunc(event)
def unreact() = unreactFunc
})
/** A shorthand for `onReaction` -- the specified function is invoked whenever there is an event.
*
* @param reactor the callback for events
* @return a subcriptions for unsubscribing from reactions
*/
def onEvent(reactor: T => Unit): Reactive.Subscription = onReaction(new Reactor[T] {
def react(event: T) = reactor(event)
def unreact() {}
})
/** A shorthand for `onReaction` -- the specified partial function is applied to only those events
* for which is defined.
*
* This method only works for `AnyRef` values.
*
* Example:
*
* {{{
* r onCase {
* case s: String => println(s)
* case n: Int => println("number " + s)
* }
* }}}
*
* '''Use case''':
*
* {{{
* def onCase(reactor: PartialFunction[T, Unit]): Reactive.Subscription
* }}}
*
* @param reactor the callback for those events for which it is defined
* @return a subscription for unsubscribing from reactions
*/
def onCase(reactor: PartialFunction[T, Unit])(implicit sub: T <:< AnyRef): Reactive.Subscription = onReaction(new Reactor[T] {
def react(event: T) = if (reactor.isDefinedAt(event)) reactor(event)
def unreact() {}
})
/** A shorthand for `onReaction` -- called whenever an event occurs.
*
* This method is handy when the precise event is not important,
* or the type of the event is `Unit`.
*
* @param reactor the callback invoked when an event arrives
* @return a subscription for unsubscribing from reactions
*/
def on(reactor: =>Unit): Reactive.Subscription = onReaction(new Reactor[T] {
def react(value: T) = reactor
def unreact() {}
})
/** Executes the specified block when `this` reactive unreacts.
*
* @param f the callback invoked when `this` unreacts
* @return a subscription for the unreaction notification
*/
def onUnreact(reactor: =>Unit): Reactive.Subscription = onReaction(new Reactor[T] {
def react(value: T) {}
def unreact() = reactor
})
/** Executes the specified function every time an event arrives.
*
* Semantically equivalent to `onEvent`,
* but supports `for`-loop syntax with reactive values.
*
* {{{
* for (event <- r) println("Event arrived: " + event)
* }}}
*
* @param f the callback invoked when an event arrives
* @return a subscription that is also a reactive value
* producing `Unit` events after each callback invocation
*/
def foreach(f: T => Unit): Reactive[Unit] with Reactive.Subscription = {
val rf = new Reactive.Foreach(self, f)
rf.subscription = self onReaction rf
rf
}
/** Creates a new reactive `s` that produces events by consecutively
* applying the specified operator `op` to the previous event that `s`
* produced and the current event that this reactive value produced.
*
* The `scanPast` operation allows the current event from this reactive to be mapped into a different
* event by looking "into the past", i.e. at the event previously emitted by the resulting reactive.
*
* Example -- assume that a reactive value `r` produces events `1`, `2` and `3`.
* The following `s`:
*
* {{{
* val s = r.scanPast(0)((sum, n) => sum + n)
* }}}
*
* will produce events `1`, `3` (`1 + 2`) and `6` (`3 + 3`).
* '''Note:''' the initial value `0` is '''not emitted'''.
*
* The `scanPast` can also be used to produce a reactive value of a different type:
* The following produces a complete history of all the events seen so far:
*
* {{{
* val s2 = r.scanPast(List[Int]()) {
* (history, n) => n :: history
* }
* }}}
*
* The `s2` will produce events `1 :: Nil`, `2 :: 1 :: Nil` and `3 :: 2 :: 1 :: Nil`.
* '''Note:''' the initial value `Nil` is '''not emitted'''.
*
* The resulting reactive value is not only a reactive value, but also a `Signal`,
* so the value of the previous event can be obtained by calling `apply` at any time.
*
* This operation is closely related to a `scanLeft` on a collection --
* if a reactive value were a sequence of elements, then `scanLeft` would produce
* a new sequence whose elements correspond to the events of the resulting reactive.
*
* @tparam S the type of the events in the resulting reactive value
* @param z the initial value of the scan past
* @param op the operator the combines the last produced and the current event into a new one
* @return a subscription that is also a reactive value that scans events from `this` reactive value
*/
def scanPast[@spec(Int, Long, Double) S](z: S)(op: (S, T) => S): Signal[S] with Reactive.Subscription = {
val r = new Reactive.ScanPast(self, z, op)
r.subscription = self onReaction r
r
}
/** Checks if this reactive value is also a signal.
*
* @return `true` if the reactive value is a signal, `false` otherwise
*/
def isSignal: Boolean = this match {
case s: Signal[T] => true
case _ => false
}
/** Mutates the target reactive mutable called `mutable` each time `this` reactive value produces an event.
*
* One type of a reactive mutable is a mutable signal (`Signal.Mutable`),
* which is a wrapper for regular mutable objects.
* Here is an example, given a reactive of type `r`:
*
* {{{
* val eventLog = Signal.Mutable(mutable.Buffer[String]())
* val eventLogMutations = r.mutate(eventLog) { event =>
* eventLog() += "at " + System.nanoTime + ": " + event
* } // <-- eventLog event propagated
* }}}
*
* Whenever an event arrives on `r`, an entry is added to the buffer underlying `eventLog`.
* After the `mutation` completes, a modification event is produced by the `eventLog`
* and can be used subsequently:
*
* {{{
* val uiUpdates = eventLog onEvent { b =>
* eventListWidget.add(b.last)
* }
* }}}
*
* '''Use case:'''
*
* {{{
* def mutate(mutable: ReactMutable)(mutation: T => Unit): Reactive.Subscription
* }}}
*
* @note No two events will ever be concurrently processed by different threads on the same reactive mutable,
* but an event that is propagated from within the `mutation` can trigger an event on `this`.
* The result is that `mutation` is invoked concurrently on the same thread.
* The following code is problematic has a feedback loop in the dataflow graph:
*
* {{{
* val emitter = new Reactive.Emitter[Int]
* val cell = ReactCell(0) // type of ReactMutable
* emitter.mutate(cell) { n =>
* cell := n
* if (n == 0)
* emitter += n + 1 // <-- event propagated
* assert(cell() == n)
* }
* emitter += 0
* }}}
*
* The statement `emitter += n + 1` in the `mutate` block
* suspends the current mutation, calls the mutation
* recursively and changes the value of `cell`, and the assertion fails when
* the first mutation resumes.
*
* Care must be taken to avoid `mutation` from emitting events that have feedback loops.
*
* @tparam M the type of the reactive mutable value
* @param mutable the target mutable to be mutated with events from this stream
* @param mutation the function that modifies `mutable` given an event of type `T`
* @return a subscription used to cancel this mutation
*/
def mutate[M <: ReactMutable](mutable: M)(mutation: T => Unit): Reactive.Subscription = {
val rm = new Reactive.Mutate(self, mutable, mutation)
rm.subscription = mutable.bindSubscription(self onReaction rm)
rm
}
private def mutablesCompositeSubscription[M <: ReactMutable](mutables: Seq[M], selfsub: Reactive.Subscription) = {
for (m <- mutables) yield m.bindSubscription(selfsub)
}
/** Mutates multiple reactive mutables `m1`, `m2` and `mr` each time
* `this` reactive value produces an event.
*
* This version of the `mutate` works on multiple reactive values.
*
* @tparam M the type of the reactive mutable value
* @param m1 the first mutable
* @param m2 the second mutable
* @param mr the rest of the mutables
* @param mutation the function that modifies the mutables
* @return a subscription used to cancel this mutation
*/
def mutate[M <: ReactMutable](m1: M, m2: M, mr: M*)(mutation: T => Unit): Reactive.Subscription = {
val mutables = Seq(m1, m2) ++ mr
val rm = new Reactive.MutateMany(self, mutables, mutation)
val selfsub = self onReaction rm
val subs = mutablesCompositeSubscription(mutables, selfsub)
rm.subscription = Reactive.CompositeSubscription(subs: _*)
rm
}
/** Creates a new reactive value that produces events from `this` reactive value
* only after `that` produces an event.
*
* After `that` emits some event, all events from `this` are produced on the resulting reactive.
* If `that` unreacts before an event is produced on `this`, the resulting reactive unreacts.
* If `this` unreacts, the resulting reactive unreacts.
*
* @tparam S the type of `that` reactive
* @param that the reactive after whose first event the result can start propagating events
* @return a subscription and the resulting reactive that emits only after `that` emits
* at least once.
*/
def after[@spec(Int, Long, Double) S](that: Reactive[S]): Reactive[T] with Reactive.Subscription = {
val ra = new Reactive.After(self, that)
ra.selfSubscription = self onReaction ra.selfReactor
ra.thatSubscription = that onReaction ra.thatReactor
ra.subscription = Reactive.CompositeSubscription(ra.selfSubscription, ra.thatSubscription)
ra
}
/** Creates a new reactive value that produces events from `this` reactive value
* until `that` produces an event.
*
* If `this` unreacts before `that` produces a value, the resulting reactive unreacts.
* Otherwise, the resulting reactive unreacts whenever `that` produces a value.
*
* @tparam S the type of `that` reactive
* @param that the reactive until whose first event the result propagates events
* @return a subscription and the resulting reactive that emits only until `that` emits
*/
def until[@spec(Int, Long, Double) S](that: Reactive[S]): Reactive[T] with Reactive.Subscription = {
val ru = new Reactive.Until(self, that)
ru.selfSubscription = self onReaction ru.selfReactor
ru.thatSubscription = that onReaction ru.thatReactor
ru.subscription = Reactive.CompositeSubscription(ru.selfSubscription, ru.thatSubscription)
ru
}
/** Creates a reactive that forwards an event from this reactive only once.
*
* The resulting reactive emits only a single event produced by `this` reactive after `once` is called, and then unreacts.
*
* {{{
* time ----------------->
* this --1-----2----3--->
* once ---2|
* }}}
*
* @return a subscription and a reactive with the first event from `this`
*/
def once: Reactive[T] with Reactive.Subscription = {
val ro = new Reactive.Once(self)
ro.subscription = self onReaction ro
ro
}
/** Filters events from `this` reactive value using a specified predicate `p`.
*
* Only events from `this` for which `p` returns `true` are emitted on the resulting reactive.
*
* @param p the predicate used to filter events
* @return a subscription and a reactive with the filtered events
*/
def filter(p: T => Boolean): Reactive[T] with Reactive.Subscription = {
val rf = new Reactive.Filter[T](self, p)
rf.subscription = self onReaction rf
rf
}
/** Filters events from `this` reactive and maps them in the same time.
*
* The `collect` combinator uses a partial function `pf` to filter events
* from `this` reactive. Events for which the partial function is defined
* are mapped using the partial function, others are discarded.
*
* '''Note:'''
* This combinator is defined only for reactives that contain reference events.
* You cannot call it for reactives whose events are primitive values, such as `Int`.
* This is because the `PartialFunction` class is not specialized.
*
* @tparam S the type of the mapped reactive
* @param pf partial function used to filter and map events
* @param evidence evidence that `T` is a reference type
* @return a subscription and a reactive value with the partially mapped events
*/
def collect[S <: AnyRef](pf: PartialFunction[T, S])(implicit evidence: T <:< AnyRef): Reactive[S] with Reactive.Subscription = {
val cf = new Reactive.Collect[T, S](self, pf)
cf.subscription = self onReaction cf
cf
}
/** Returns a new reactive that maps events from `this` reactive using the mapping function `f`.
*
* @tparam S the type of the mapped events
* @param f the mapping function
* @return a subscription and reactive value with the mapped events
*/
def map[@spec(Int, Long, Double) S](f: T => S): Reactive[S] with Reactive.Subscription = {
val rm = new Reactive.Map[T, S](self, f)
rm.subscription = self onReaction rm
rm
}
/** Splits the primitive value events from this reactive into a reactive value pair.
*
* Events in this reactive must be primitive values.
*
* @tparam P the type of the first value in the reactive pair
* @tparam Q the type of the second value in the reactive pair
* @param pf mapping function from events in this reactive to the first part of the pair
* @param qf mapping function from events in this reactive to the second part of the pair
* @param e evidence that events in this reactive are values
* @return reactive value pair
*/
def valsplit[@spec(Int, Long, Double) P <: AnyVal, @spec(Int, Long, Double) Q <: AnyVal](pf: T => P)(qf: T => Q)(implicit e: T <:< AnyVal): ReactValPair[P, Q] = {
val e = new ReactValPair.Emitter[P, Q]
e.subscription = this.onReaction(new Reactor[T] {
def react(x: T) = e.emit(pf(x), qf(x))
def unreact() = e.close()
})
e
}
/** Splits the object events from this reactive into a reactive value pair.
*
* Events in this reactive must be objects.
*
* @tparam P the type of the first value in the reactive pair
* @tparam Q the type of the second value in the reactive pair
* @param pf mapping function from events in this reactive to the first part of the pair
* @param qf mapping function from events in this reactive to the second part of the pair
* @param ev evidence that events in this reactive are values
* @return reactive value pair
*/
def valsplit[@spec(Int, Long, Double) P <: AnyVal, @spec(Int, Long, Double) Q <: AnyVal](pf: RefValFun[T, P])(qf: RefValFun[T, Q])(implicit ev: T <:< AnyRef): ReactValPair[P, Q] = {
val e = new ReactValPair.Emitter[P, Q]
e.subscription = this.onReaction(new Reactor[T] {
def react(x: T) = e.emit(pf(x), qf(x))
def unreact() = e.close()
})
e
}
/** Splits the events from this reactive into a reactive pair.
*
* '''Note:'''
* This reactive needs to contain object events.
*
* @tparam P the type of the first value in the reactive pair
* @tparam Q the type of the second value in the reactive pair
* @param pf mapping function from events in this reactive to the first part of the pair
* @param qf mapping function from events in this reactive to the second part of the pair
* @return reactive pair
*/
def split[@spec(Int, Long, Double) P <: AnyVal, Q <: AnyRef](pf: RefValFun[T, P])(qf: T => Q)(implicit ev: T <:< AnyRef): ReactPair[P, Q] = {
val e = new ReactPair.Emitter[P, Q]
e.subscription = this.onReaction(new Reactor[T] {
def react(x: T) = e.emit(pf(x), qf(x))
def unreact() = e.close()
})
e
}
/** Splits the events from this reactive into a reactive pair.
*
* '''Note:'''
* This reactive needs to contain object events.
*
* @tparam P the type of the first value in the reactive pair
* @tparam Q the type of the second value in the reactive pair
* @param pf mapping function from events in this reactive to the first part of the pair
* @param qf mapping function from events in this reactive to the second part of the pair
* @param e evidence that events in this reactive are values
* @return reactive pair
*/
def split[P <: AnyRef, Q <: AnyRef](pf: T => P)(qf: T => Q)(implicit ev: T <:< AnyRef): ReactPair[P, Q] = {
val e = new ReactPair.Emitter[P, Q]
e.subscription = this.onReaction(new Reactor[T] {
def react(x: T) = e.emit(pf(x), qf(x))
def unreact() = e.close()
})
e
}
/* higher-order combinators */
/** Returns events from the last reactive value that `this` emitted as an event of its own,
* in effect multiplexing the nested reactives.
*
* The resulting reactive only emits events from the reactive value last emitted by `this`,
* the preceding reactive values are ignored.
*
* This combinator is only available if this reactive value emits events
* that are themselves reactive values.
*
* Example:
*
* {{{
* val currentReactive = new Reactive.Emitter[Reactive[Int]]
* val e1 = new Reactive.Emitter[Int]
* val e2 = new Reactive.Emitter[Int]
* val currentEvent = currentReactive.mux()
* val prints = currentEvent.onEvent(println)
*
* currentReactive += e1
* e2 += 1 // nothing is printed
* e1 += 2 // 2 is printed
* currentReactive += e2
* e2 += 6 // 6 is printed
* e1 += 7 // nothing is printed
* }}}
*
* Shown on the diagram:
*
* {{{
* time ------------------->
* currentReactive --e1------e2------->
* e1 --------2----6----->
* e2 -----1----------7-->
* currentEvent --------2----6----->
* }}}
*
* '''Use case:'''
*
* {{{
* def mux[S](): Reactive[S]
* }}}
*
* @tparam S the type of the events in the nested reactive
* @param evidence an implicit evidence that `this` reactive is nested --
* it emits events of type `T` that is actually a `Reactive[S]`
* @return a reactive of events from the reactive last emitted by `this`
*/
def mux[@spec(Int, Long, Double) S]()(implicit evidence: T <:< Reactive[S]): Reactive[S] = {
new Reactive.Mux[T, S](this, evidence)
}
}
/** Contains useful `Reactive` implementations and factory methods.
*/
object Reactive {
implicit def reactive2ops[@spec(Int, Long, Double) T](self: Reactive[T]) = new ReactiveOps(self)
class ReactiveOps[@spec(Int, Long, Double) T](val self: Reactive[T]) {
/** Given an initial event `init`, converts this reactive into a `Signal`.
*
* The resulting signal initially contains the event `init`,
* and subsequently any event that the `this` reactive produces.
*
* @param init an initial value for the signal
* @return the signal version of the current reactive
*/
def signal(init: T) = self.scanPast(init) {
(cached, value) => value
}
/** If the current reactive is a signal already this method downcasts it,
* otherwise it lifts it into a signal with the initial value `init`.
*
* @param init optional value to use when converting the reactive to a signal
* @return the signal version of the current reactive
*/
def asSignalOrElse(init: T) = self match {
case s: Signal[T] => s
case _ => signal(init)
}
/** Downcasts this reactive into a signal.
*
* Throws an exception if the current reactive is not a signal.
*
* @return the signal version of the current reactive
*/
def asSignal = this match {
case s: Signal[T] => s
case _ => throw new UnsupportedOperationException("This is not a signal.")
}
/** Creates a union of `this` and `that` reactive.
*
* The resulting reactive value emits events from both `this` and `that` reactive.
* It unreacts when both `this` and `that` reactive unreact.
*
* @param that another reactive value for the union
* @return a subscription and the reactive value with unified events from `this` and `that`
*/
def union(that: Reactive[T]): Reactive[T] with Reactive.Subscription = {
val ru = new Reactive.Union(self, that)
ru.selfSubscription = self onReaction ru.selfReactor
ru.thatSubscription = that onReaction ru.thatReactor
ru.subscription = Reactive.CompositeSubscription(ru.selfSubscription, ru.thatSubscription)
ru
}
/** Creates a concatenation of `this` and `that` reactive.
*
* The resulting reactive value produces all the events from `this` reactive
* until `this` unreacts, and then outputs all the events from `that`
* that happened before and after `this` unreacted.
* To do this, this operation potentially caches all the events from `that`.
* When `that` unreacts, the resulting reactive value unreacts.
*
* '''Use case:'''
*
* {{{
* def concat(that: Reactive[T]): Reactive[T]
* }}}
*
* @param that another reactive value for the concatenation
* @note This operation potentially caches events from `that`.
* Unless certain that `this` eventually unreacts, `concat` should not be used.
* To enforce this, clients must import the `CanBeBuffered` evidence explicitly
* into the scope in which they call `concat`.
*
* @param a evidence that arrays can be created for the type `T`
* @param b evidence that the client allows events from `that` to be buffered
* @return a subscription and a reactive value that concatenates events from `this` and `that`
*/
def concat(that: Reactive[T])(implicit a: Arrayable[T], b: CanBeBuffered): Reactive[T] with Reactive.Subscription = {
val rc = new Reactive.Concat(self, that, a)
rc.selfSubscription = self onReaction rc.selfReactor
rc.thatSubscription = that onReaction rc.thatReactor
rc.subscription = Reactive.CompositeSubscription(rc.selfSubscription, rc.thatSubscription)
rc
}
/** Syncs the arrival of events from `this` and `that` reactive value.
*
* Ensures that pairs of events from this reactive value and that reactive value
* are emitted together.
* If the events produced in time by `this` and `that`, the sync will be as follows:
*
* {{{
* time --------------------------->
* this ----1---------2-------4---->
* that --1-----2--3--------------->
* sync ----1,1-------2,2-----4,3-->
* }}}
*
* Pairs of events produced from `this` and `that` are then transformed using
* specified function `f`.
* For example, clients that want to output tuples do:
*
* {{{
* val synced = (a sync b) { (a, b) => (a, b) }
* }}}
*
* Clients that, for example, want to create differences in pairs of events do:
*
* {{{
* val diffs = (a sync b)(_ - _)
* }}}
*
* The resulting reactive unreacts either when
* `this` unreacts and there are no more buffered events from this,
* or when `that` unreacts and there are no more buffered events from `that`.
*
* '''Use case:'''
*
* {{{
* def sync[S, R](that: Reactive[S])(f: (T, S) => R): Reactive[R]
* }}}
*
* @note This operation potentially caches events from `this` and `that`.
* Unless certain that both `this` produces a bounded number of events
* before the `that` produces an event, and vice versa, this operation should not be called.
* To enforce this, clients must import the `CanBeBuffered` evidence explicitly
* into the scope in which they call `sync`.
*
* @tparam S the type of the events in `that` reactive
* @tparam R the type of the events in the resulting reactive
* @param that the reactive to sync with
* @param f the mapping function for the pair of events
* @param at evidence that arrays can be created for the type `T`
* @param as evidence that arrays can be created for the type `S`
* @param b evidence that the client allows events to be buffered
* @return a subscription and the reactive with the resulting events
*/
def sync[@spec(Int, Long, Double) S, @spec(Int, Long, Double) R](that: Reactive[S])(f: (T, S) => R)
(implicit at: Arrayable[T], as: Arrayable[S], b: CanBeBuffered): Reactive[R] with Reactive.Subscription = {
val rs = new Reactive.Sync(self, that, f, at, as)
rs.selfSubscription = self onReaction rs.selfReactor
rs.thatSubscription = that onReaction rs.thatReactor
rs.subscription = Reactive.CompositeSubscription(rs.selfSubscription, rs.thatSubscription)
rs
}
/* higher-order combinators */
/** Unifies the events produced by all the reactives emitted by `this`.
*
* This operation is only available for reactive values that emit
* other reactives as events.
* The resulting reactive unifies events of all the reactives emitted by `this`.
* Once `this` and all the reactives emitted by `this` unreact, the resulting reactive terminates.
*
* Example:
*
* {{{
* time -------------------------->
* this --1----2--------3------>
* ---------5----6---->
* ---4----------7-->
* union -----1----2-4---5--3-6-7-->
* }}}
*
* '''Use case:'''
*
* {{{
* def union[S](): Reactive[S]
* }}}
*
* @tparam S the type of the events in reactives emitted by `this`
* @param evidence evidence that events of type `T` produced by `this` are
* actually reactive values of type `S`
* @return a subscription and the reactive with the union of all the events
*
*/
def union[@spec(Int, Long, Double) S]()(implicit evidence: T <:< Reactive[S]): Reactive[S] with Reactive.Subscription = {
new Reactive.PostfixUnion[T, S](self, evidence)
}
/** Concatenates the events produced by all the reactives emitted by `this`.
*
* This operation is only available for reactive values that emit
* other reactives as events.
* Once `this` and all the reactives unreact, this reactive unreacts.
*
* '''Use case:'''
*
* {{{
* def concat[S](): Reactive[S]
* }}}
*
* @note This operation potentially buffers events from the nested reactives.
* Unless each reactive emitted by `this` is known to unreact eventually,
* this operation should not be called.
* To enforce this, clients are required to import the `CanBeBuffered` evidence
* explicitly into the scope in which they call `concat`.
*
* @tparam S the type of the events in reactives emitted by `this`
* @param evidence evidence that events of type `T` produced by `this` are
* actually reactive values of type `S`
* @param a evidence that arrays can be created for type `S`
* @param b evidence that buffering events is allowed
* @return a subscription and the reactive that concatenates all the events
*/
def concat[@spec(Int, Long, Double) S]()(implicit evidence: T <:< Reactive[S], a: Arrayable[S], b: CanBeBuffered): Reactive[S] with Reactive.Subscription = {
val pc = new Reactive.PostfixConcat[T, S](self, evidence)
pc.subscription = self onReaction pc
pc
}
}
private[reactive] class Foreach[@spec(Int, Long, Double) T]
(val self: Reactive[T], val f: T => Unit)
extends Reactive.Default[Unit] with Reactor[T] with Reactive.ProxySubscription {
def react(value: T) {
f(value)
reactAll(())
}
def unreact() {
unreactAll()
}
var subscription = Subscription.empty
}
private[reactive] class ScanPast[@spec(Int, Long, Double) T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val z: S, val op: (S, T) => S)
extends Signal.Default[S] with Reactor[T] with Reactive.ProxySubscription {
private var cached: S = _
def init(z: S) {
cached = z
}
init(z)
def apply() = cached
def react(value: T) {
cached = op(cached, value)
reactAll(cached)
}
def unreact() {
unreactAll()
}
var subscription = Subscription.empty
}
private[reactive] class Mutate[@spec(Int, Long, Double) T, M <: ReactMutable]
(val self: Reactive[T], val mutable: M, val mutation: T => Unit)
extends Reactor[T] with Reactive.ProxySubscription {
def react(value: T) = {
mutation(value)
mutable.onMutated()
}
def unreact() {}
var subscription = Subscription.empty
}
private[reactive] class MutateMany[@spec(Int, Long, Double) T, M <: ReactMutable]
(val self: Reactive[T], val mutables: Seq[M], val mutation: T => Unit)
extends Reactor[T] with Reactive.ProxySubscription {
def react(value: T) = {
mutation(value)
for (m <- mutables) m.onMutated()
}
def unreact() {}
var subscription = Subscription.empty
}
private[reactive] class After[@spec(Int, Long, Double) T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val that: Reactive[S])
extends Reactive.Default[T] with Reactive.ProxySubscription {
var started = false
var live = true
def unreactBoth() = if (live) {
live = false
unreactAll()
}
val selfReactor = new Reactor[T] {
def react(value: T) {
if (started) reactAll(value)
}
def unreact() = unreactBoth()
}
val thatReactor = new Reactor[S] {
def react(value: S) {
started = true
}
def unreact() = if (!started) unreactBoth()
}
var selfSubscription = Subscription.empty
var thatSubscription = Subscription.empty
var subscription = Subscription.empty
}
private[reactive] class Until[@spec(Int, Long, Double) T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val that: Reactive[S])
extends Reactive.Default[T] with Reactive.ProxySubscription {
var live = true
def unreactBoth() = if (live) {
live = false
unreactAll()
}
val selfReactor = new Reactor[T] {
def react(value: T) = if (live) reactAll(value)
def unreact() = unreactBoth()
}
val thatReactor = new Reactor[S] {
def react(value: S) = unreactBoth()
def unreact() {}
}
var selfSubscription: Reactive.Subscription = Subscription.empty
var thatSubscription: Reactive.Subscription = Subscription.empty
var subscription = Subscription.empty
}
private[reactive] class Union[@spec(Int, Long, Double) T](val self: Reactive[T], val that: Reactive[T])
extends Reactive.Default[T] with Reactive.ProxySubscription {
var live = 2
def unreactBoth() = {
live -= 1
if (live == 0) unreactAll()
}
val selfReactor = new Reactor[T] {
def react(value: T) = reactAll(value)
def unreact() = unreactBoth()
}
val thatReactor = new Reactor[T] {
def react(value: T) = reactAll(value)
def unreact() = unreactBoth()
}
var selfSubscription: Reactive.Subscription = Subscription.empty
var thatSubscription: Reactive.Subscription = Subscription.empty
var subscription = Subscription.empty
}
private[reactive] class Concat[@spec(Int, Long, Double) T](val self: Reactive[T], val that: Reactive[T], val a: Arrayable[T])
extends Reactive.Default[T] with Reactive.ProxySubscription {
var selfLive = true
var thatLive = true
val buffer = new UnrolledBuffer[T]()(a)
def unreactBoth() {
if (!selfLive && !thatLive) {
unreactAll()
}
}
def flush() {
buffer.foreach(v => reactAll(v))
buffer.clear()
}
val selfReactor = new Reactor[T] {
def react(value: T) = reactAll(value)
def unreact() {
selfLive = false
flush()
unreactBoth()
}
}
val thatReactor = new Reactor[T] {
def react(value: T) = {
if (selfLive) buffer += value
else reactAll(value)
}
def unreact() {
thatLive = false
unreactBoth()
}
}
var selfSubscription: Reactive.Subscription = Subscription.empty
var thatSubscription: Reactive.Subscription = Subscription.empty
var subscription = Subscription.empty
}
private[reactive] class Sync[@spec(Int, Long, Double) T, @spec(Int, Long, Double) S, @spec(Int, Long, Double) R]
(val self: Reactive[T], val that: Reactive[S], val f: (T, S) => R, val at: Arrayable[T], val as: Arrayable[S])
extends Reactive.Default[R] with Reactive.ProxySubscription {
val tbuffer = new UnrolledBuffer[T]()(at)
val sbuffer = new UnrolledBuffer[S]()(as)
def unreactBoth() {
tbuffer.clear()
sbuffer.clear()
unreactAll()
}
val selfReactor = new Reactor[T] {
def react(tvalue: T) {
if (sbuffer.isEmpty) tbuffer += tvalue
else {
val svalue = sbuffer.dequeue()
reactAll(f(tvalue, svalue))
}
}
def unreact() = unreactBoth()
}
val thatReactor = new Reactor[S] {
def react(svalue: S) = {
if (tbuffer.isEmpty) sbuffer += svalue
else {
val tvalue = tbuffer.dequeue()
reactAll(f(tvalue, svalue))
}
}
def unreact() = unreactBoth()
}
var selfSubscription: Reactive.Subscription = Subscription.empty
var thatSubscription: Reactive.Subscription = Subscription.empty
var subscription = Subscription.empty
}
private[reactive] class Filter[@spec(Int, Long, Double) T](val self: Reactive[T], val p: T => Boolean)
extends Reactive.Default[T] with Reactor[T] with Reactive.ProxySubscription {
def react(value: T) {
if (p(value)) reactAll(value)
}
def unreact() {
unreactAll()
}
var subscription = Subscription.empty
}
private[reactive] class Collect[T, S <: AnyRef](val self: Reactive[T], val pf: PartialFunction[T, S])
extends Reactive.Default[S] with Reactor[T] with Reactive.ProxySubscription {
def react(value: T) {
if (pf.isDefinedAt(value)) reactAll(pf(value))
}
def unreact() {
unreactAll()
}
var subscription = Subscription.empty
}
private[reactive] class Map[@spec(Int, Long, Double) T, @spec(Int, Long, Double) S](val self: Reactive[T], val f: T => S)
extends Reactive.Default[S] with Reactor[T] with Reactive.ProxySubscription {
def react(value: T) {
reactAll(f(value))
}
def unreact() {
unreactAll()
}
var subscription = Subscription.empty
}
private[reactive] class Once[@spec(Int, Long, Double) T](val self: Reactive[T])
extends Reactive.Default[T] with Reactor[T] with Reactive.ProxySubscription {
private var forwarded = false
def react(value: T) {
if (!forwarded) {
reactAll(value)
unreact()
}
}
def unreact() {
if (!forwarded) {
forwarded = true
unreactAll()
}
}
var subscription = Subscription.empty
}
private[reactive] class Mux[T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val evidence: T <:< Reactive[S])
extends Reactive.Default[S] with Reactor[T] with Reactive.ProxySubscription {
muxed =>
private[reactive] var currentSubscription: Subscription = null
private[reactive] var terminated = false
def newReactor: Reactor[S] = new Reactor[S] {
def react(value: S) = reactAll(value)
def unreact() {
currentSubscription = Subscription.empty
checkUnreact()
}
}
def checkUnreact() = if (terminated && currentSubscription == Subscription.empty) unreactAll()
def react(value: T) {
val nextReactive = evidence(value)
currentSubscription.unsubscribe()
currentSubscription = nextReactive onReaction newReactor
}
def unreact() {
terminated = true
checkUnreact()
}
override def unsubscribe() {
currentSubscription.unsubscribe()
currentSubscription = Subscription.empty
super.unsubscribe()
}
var subscription: Subscription = null
def init(e: T <:< Reactive[S]) {
currentSubscription = Subscription.empty
subscription = self onReaction this
}
init(evidence)
}
private[reactive] class PostfixUnion[T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val evidence: T <:< Reactive[S])
extends Reactive.Default[S] with Reactor[T] with Reactive.ProxySubscription {
union =>
private[reactive] var subscriptions = mutable.Map[Reactive[S], Subscription]()
private[reactive] var terminated = false
def checkUnreact() = if (terminated && subscriptions.isEmpty) unreactAll()
def newReactor(r: Reactive[S]) = new Reactor[S] {
def react(value: S) = reactAll(value)
def unreact() = {
subscriptions.remove(r)
checkUnreact()
}
}
def react(value: T) {
val nextReactive = evidence(value)
if (!subscriptions.contains(nextReactive)) {
val sub = nextReactive onReaction newReactor(nextReactive)
subscriptions(nextReactive) = sub
}
}
def unreact() {
terminated = true
checkUnreact()
}
override def unsubscribe() {
for (kv <- subscriptions) kv._2.unsubscribe()
subscriptions.clear()
super.unsubscribe()
}
val subscription = self onReaction this
}
private[reactive] final class ConcatEntry[S](var subscription: Subscription, var buffer: UnrolledBuffer[S], var live: Boolean) {
def ready = buffer == null
}
private[reactive] class PostfixConcat[T, @spec(Int, Long, Double) S]
(val self: Reactive[T], val evidence: T <:< Reactive[S])(implicit val arrayable: Arrayable[S])
extends Reactive.Default[S] with Reactor[T] with Reactive.ProxySubscription {
union =>
private[reactive] var subscriptions = new UnrolledBuffer[ConcatEntry[S]]()
private[reactive] var terminated = false
private def checkUnreact() = if (terminated && subscriptions.isEmpty) unreactAll()
def moveToNext() {
if (subscriptions.isEmpty) checkUnreact()
else {
val entry = subscriptions.head
val buffer = entry.buffer
entry.buffer = null
while (buffer.nonEmpty) reactAll(buffer.dequeue())
if (!entry.live) {
subscriptions.dequeue()
moveToNext()
}
}
}
def newReactor(entry: ConcatEntry[S]) = new Reactor[S] {
def react(value: S) {
if (entry.ready) reactAll(value)
else entry.buffer.enqueue(value)
}
def unreact() {
if (entry.ready) {
subscriptions.dequeue()
moveToNext()
} else entry.live = false
}
}
def react(value: T) {
val nextReactive = evidence(value)
val entry = new ConcatEntry(null, new UnrolledBuffer[S], true)
entry.subscription = nextReactive onReaction newReactor(entry)
subscriptions.enqueue(entry)
if (!subscriptions.head.ready) moveToNext()
}
def unreact() {
terminated = true
checkUnreact()
}
override def unsubscribe() {
while (subscriptions.nonEmpty) {
subscriptions.head.subscription.unsubscribe()
subscriptions.dequeue()
}
super.unsubscribe()
}
var subscription = Subscription.empty
}
/** A base trait for reactives that never emit events.
*
* @tparam T type of events never emitted by this reactive
*/
trait Never[@spec(Int, Long, Double) T]
extends Reactive[T] {
def hasSubscriptions = false
def onReaction(reactor: Reactor[T]) = {
reactor.unreact()
Subscription.empty
}
def reactAll(value: T) {}
def unreactAll() {}
}
private object NeverImpl extends Never[Nothing]
/** A reactive that never emits events.
*
* @tparam T type of events in this reactive
*/
def Never[T] = NeverImpl.asInstanceOf[Reactive[T]]
// TODO Amb
/** The proxy reactive that emits events of its underlying reactive.
*
* @tparam T type of the proxy signal
*/
trait Proxy[@spec(Int, Long, Double) T]
extends Reactive[T] {
val underlying: Reactive[T]
def hasSubscriptions = underlying.hasSubscriptions
def onReaction(r: Reactor[T]) = underlying.onReaction(r)
}
/** A subscription to a certain kind of event,
* event processing or computation in general.
* Calling `unsubscribe` on the subscription
* causes the events to no longer be propagated
* to this particular subscription or some computation to cease.
*
* Unsubscribing is idempotent -- calling `unsubscribe` second time does nothing.
*/
trait Subscription {
def unsubscribe(): Unit
}
/** Contains factory methods for subscription.
*/
object Subscription {
/** Unsubscribing does nothing. */
val empty = new Subscription {
def unsubscribe() {}
}
/** Invokes the specified `onUnsubscribe` block when `unsubscribe` is called.
*
* @param onUnsubscribe code to execute when `unsubscribe` is called
*/
def apply(onUnsubscribe: =>Unit) = new Subscription {
def unsubscribe() = onUnsubscribe
}
}
/** Unsubscribes by calling `unsubscribe` on the underlying subscription.
*/
trait ProxySubscription extends Subscription {
def subscription: Subscription
def unsubscribe() {
subscription.unsubscribe()
}
}
/** Unsubscribing will call `unsubscribe` on all the
* subscriptions in `ss`.
*
* @param ss the child subscriptions
* @return the composite subscription
*/
def CompositeSubscription(ss: Subscription*): Subscription = new Subscription {
def unsubscribe() {
for (s <- ss) s.unsubscribe()
}
}
private val bufferUpperBound = 8
private val hashTableLowerBound = 5
/** The default implementation of a reactive value.
*
* Keeps an optimized weak collection of weak references to subscribers.
* References to subscribers that are no longer reachable in the application
* will be removed eventually.
*
* @tparam T type of the events in this reactive value
*/
trait Default[@spec(Int, Long, Double) T] extends Reactive[T] {
private[reactive] var demux: AnyRef = null
private[reactive] var unreacted: Boolean = false
def onReaction(reactor: Reactor[T]) = {
if (unreacted) {
reactor.unreact()
Subscription.empty
} else {
demux match {
case null =>
demux = new WeakRef(reactor)
case w: WeakRef[Reactor[T] @unchecked] =>
val wb = new WeakBuffer[Reactor[T]]
wb.addRef(w)
wb.addEntry(reactor)
demux = wb
case wb: WeakBuffer[Reactor[T] @unchecked] =>
if (wb.size < bufferUpperBound) {
wb.addEntry(reactor)
} else {
val wht = toHashTable(wb)
wht.addEntry(reactor)
demux = wht
}
case wht: WeakHashTable[Reactor[T] @unchecked] =>
wht.addEntry(reactor)
}
newSubscription(reactor)
}
}
private def newSubscription(r: Reactor[T]) = new Subscription {
onSubscriptionChange()
def unsubscribe() {
removeReaction(r)
}
}
private def removeReaction(r: Reactor[T]) {
demux match {
case null =>
// nothing to invalidate
case w: WeakRef[Reactor[T] @unchecked] =>
if (w.get eq r) w.clear()
case wb: WeakBuffer[Reactor[T] @unchecked] =>
wb.invalidateEntry(r)
case wht: WeakHashTable[Reactor[T] @unchecked] =>
wht.invalidateEntry(r)
}
onSubscriptionChange()
}
def reactAll(value: T) {
demux match {
case null =>
// no need to inform anybody
case w: WeakRef[Reactor[T] @unchecked] =>
val r = w.get
if (r != null) r.react(value)
else demux = null
case wb: WeakBuffer[Reactor[T] @unchecked] =>
bufferReactAll(wb, value)
case wht: WeakHashTable[Reactor[T] @unchecked] =>
tableReactAll(wht, value)
}
}
def unreactAll() {
unreacted = true
demux match {
case null =>
// no need to inform anybody
case w: WeakRef[Reactor[T] @unchecked] =>
val r = w.get
if (r != null) r.unreact()
else demux = null
case wb: WeakBuffer[Reactor[T] @unchecked] =>
bufferUnreactAll(wb)
case wht: WeakHashTable[Reactor[T] @unchecked] =>
tableUnreactAll(wht)
}
}
private def checkBuffer(wb: WeakBuffer[Reactor[T]]) {
if (wb.size <= 1) {
if (wb.size == 1) demux = new WeakRef(wb(0))
else if (wb.size == 0) demux = null
}
}
private def bufferReactAll(wb: WeakBuffer[Reactor[T]], value: T) {
val array = wb.array
var until = wb.size
var i = 0
while (i < until) {
val ref = array(i)
val r = ref.get
if (r ne null) {
r.react(value)
i += 1
} else {
wb.removeEntryAt(i)
until -= 1
}
}
checkBuffer(wb)
}
private def bufferUnreactAll(wb: WeakBuffer[Reactor[T]]) {
val array = wb.array
var until = wb.size
var i = 0
while (i < until) {
val ref = array(i)
val r = ref.get
if (r ne null) {
r.unreact()
i += 1
} else {
wb.removeEntryAt(i)
until -= 1
}
}
checkBuffer(wb)
}
private def toHashTable(wb: WeakBuffer[Reactor[T]]) = {
val wht = new WeakHashTable[Reactor[T]]
val array = wb.array
val until = wb.size
var i = 0
while (i < until) {
val r = array(i).get
if (r != null) wht.addEntry(r)
i += 1
}
wht
}
private def toBuffer(wht: WeakHashTable[Reactor[T]]) = {
val wb = new WeakBuffer[Reactor[T]](bufferUpperBound)
val table = wht.table
var i = 0
while (i < table.length) {
var ref = table(i)
if (ref != null && ref.get != null) wb.addRef(ref)
i += 1
}
wb
}
private def checkHashTable(wht: WeakHashTable[Reactor[T]]) {
if (wht.size < hashTableLowerBound) {
val wb = toBuffer(wht)
demux = wb
checkBuffer(wb)
}
}
private def tableReactAll(wht: WeakHashTable[Reactor[T]], value: T) {
val table = wht.table
var i = 0
while (i < table.length) {
val ref = table(i)
if (ref ne null) {
val r = ref.get
if (r ne null) r.react(value)
else wht.removeEntryAt(i, null)
}
i += 1
}
checkHashTable(wht)
}
private def tableUnreactAll(wht: WeakHashTable[Reactor[T]]) {
val table = wht.table
var i = 0
while (i < table.length) {
val ref = table(i)
if (ref ne null) {
val r = ref.get
if (r ne null) r.unreact()
else wht.removeEntryAt(i, null)
i += 1
}
}
checkHashTable(wht)
}
def hasSubscriptions: Boolean = demux != null
def onSubscriptionChange() {}
}
/** A reactive value that can programatically emit events.
*
* Events are emitted to the reactive value by calling the `+=` method.
* The emitter can be closed by calling the `close` method --
* after this no more events will be accepted through `+=`.
*
* Example:
*
* {{{
* val emitter = new Reactive.Emitter[Int]
* val prints = emitter.onEvent(println)
* emitter += 1
* emitter += 2
* }}}
*
* @tparam the type of events that this emitter can emit
*/
class Emitter[@spec(Int, Long, Double) T]
extends Reactive[T] with Default[T] {
private var live = true
def +=(value: T) {
if (live) reactAll(value)
}
def close(): Unit = if (live) {
live = false
unreactAll()
}
}
/** A reactive emitter that can be used with the `mutate` block.
*
* Calling `mutate` with this bind emitter will add a subscription
* to the emitter that can unsubscribe from that `mutate` statement.
*
* For most purposes, clients should just use the regular `Reactive.Emitter`.
*
* @tparam T the type of events in the bind emitter
*/
class BindEmitter[@spec(Int, Long, Double) T]
extends Reactive[T] with Default[T] with ReactMutable.Subscriptions {
private var live = true
def +=(value: T) {
if (live) reactAll(value)
}
def close(): Unit = if (live) {
live = false
unreactAll()
}
}
/** Uses the specified function `f` to produce an event when the `emit` method is called.
*/
class SideEffectEmitter[@spec(Int, Long, Double) T](f: () => T) extends Reactive.Default[T] {
private var closed = false
final def emit() = if (!closed) reactAll(f())
final def close() = closed = true
}
/** Creates a new reactive that invokes the function `f` on any `Reactor` that subscribes to it.
*
* Passive reactives usually emit separate event streams to each reactor
* for which `onReaction` has been called.
*
* Once all the events are emitted, clients should call `unreact` to notify
* the reactor that there will be no more events.
*
* Example:
*
* {{{
* val r = passive[Int] { reactor =>
* reactor.react(1)
* reactor.react(2)
* reactor.unreact()
* }
* }}}
*
* Never use `passive` to install adapt a callback in a 3rd party API.
* For example, *never* do this:
*
* {{{
* val r = passive[String] { reactor =>
* Future { "... " * 5 + "done!" } onSuccess {
* case e => reactor.react(e); reactor.unreact()
* }
* }
* val m = Signal.Mutable(mutable.ArrayBuffer[String]())
* val s1 = r.onEvent(e => m += e)
* val s2 = r.onEvent(e => m += e)
* }}}
*
* Above, subscriptions `s1` and `s2` could execute concurrenctly and corrupt the buffer.
*
* @note You should never use this method to bind reactors to bind reactors to custom callbacks
* in 3rd party APIs. Those callbacks may execute on another thread, and that is *unsafe*.
*
* @tparam T type of the events in this passive reactive
* @param f function to execute on any newly subscribed reactor
* @return the passive reactive defined by `f`
*/
def passive[@spec(Int, Long, Double) T](f: Reactor[T] => Subscription): Reactive[T] = {
new Reactive[T] {
def hasSubscriptions = false
def reactAll(value: T) {}
def unreactAll() {}
def onReaction(r: Reactor[T]) = f(r)
}
}
/** Defines a new passive reactive that emits a single event
* to any new subscriber.
*
* @tparam T type of the event to emit
* @param event event to emit
* @return a reactive that emits a single event
*/
def single[@spec(Int, Long, Double) T](event: T): Reactive[T] = passive[T] { r =>
var cancelled = false
r.react(event)
if (!cancelled) r.unreact()
Subscription { cancelled = true }
}
/** Defines a new passive reactive that emits several events
* to any new subscriber.
*
* @tparam T type of the events to emit
* @param events events to emit
* @return a reactive that emits the specified events
*/
def items[@spec(Int, Long, Double) T](events: Array[T]): Reactive[T] = passive[T] { r =>
var cancelled = false
var it = events.iterator
while (!cancelled && it.hasNext) {
r.react(it.next())
}
if (!cancelled) r.unreact()
Subscription { cancelled = true }
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy