All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.ops.Async.scala Maven / Gradle / Ivy

The newest version!
package rx
package ops
import acyclic.file
import scala.concurrent.{ExecutionContext, Future}

import scala.util.Try
import java.util.concurrent.atomic.{AtomicLong, AtomicReference}
import java.lang.ref.WeakReference
import concurrent.duration._
import rx._
import rx.core._
import scala.Some
import scala.util.Success
import scala.Some
import rx.core.SpinSet
import rx.Rx
import scala.util.Success


/**
 * A [[Rx]] which flattens out an Rx[Future[T]] into a Rx[T]. If the first
 * Future has not yet arrived, the Async contains its default value.
 * Afterwards, it updates itself when and with whatever the Futures complete
 * with.
 *
 * The Async can be configured with a variety of Targets, to configure
 * its handling of Futures which complete out of order (RunAlways, DiscardLate)
 */
class Async[+T, P](default: => T,
                   source: Rx[Future[T]],
                   discardLate: Boolean)
                  (implicit ec: ExecutionContext, p: Propagator[P])
                   extends Rx[T]
                   with Incrementing[T]
                   with Reactor[Future[_]]{

  source.linkChild(this)
  def name = "Async " + source.name

  protected[this] type StateType = SpinState

  protected[this] val state = SpinSet(new SpinState(0, Try(default)))

  override def ping[P](incoming: Set[Emitter[_]])(implicit p: Propagator[P]) = {
    val stamp = getStamp
    source().onComplete{ x =>
      val set = state.spinSetOpt{oldState =>
        if (x != state().value && (stamp >= oldState.timestamp || !discardLate)){
          Some(new SpinState(stamp, x))
        }else{
          None
        }
      }

      if(set) propagate()
    }
    Set.empty
  }
  def parents = Set(source)

  def level = source.level + 1

  this.ping(parents)
}

/**
 * An [[Rx]] which wraps an existing [[Rx]] but only emits changes at most once
 * every `interval`.
 */
class Debounce[+T](source: Rx[T], interval: FiniteDuration)
                  (implicit scheduler: Scheduler, ex: ExecutionContext)
                   extends core.Dynamic[T](() => source(), "Debounced " + source.name){

  val nextPingTime = new AtomicReference(Deadline.now)

  override def ping[P: Propagator](incoming: Set[Emitter[_]]): Set[Reactor[_]] = {

    val npt = nextPingTime.get

    if (Deadline.now > npt && nextPingTime.compareAndSet(npt, Deadline.now + interval)) {
      super.ping(incoming)
    } else {
      scheduler.scheduleOnce(npt - Deadline.now){
        if (nextPingTime.compareAndSet(npt, Deadline.now)) {
          if(ping(incoming) != Nil)this.propagate()
        }
      }
      Set.empty
    }
  }
  override def level = source.level + 1
}

/**
 * An [[Rx]] which wraps and existing [[Rx]] but delays the propagation by
 * `delay`.
 */
class Delay[+T](source: Rx[T], delay: FiniteDuration)
               (implicit scheduler: Scheduler, ex: ExecutionContext)
                extends core.Dynamic[T](() => source(), "Delayed " + source.name){

  override def ping[P: Propagator](incoming: Set[Emitter[_]]): Set[Reactor[_]] = {
    scheduler.scheduleOnce(delay){
      if(super.ping(incoming) != Nil) this.propagate()
    }
    Set.empty
  }

  override def level = source.level + 1
}


object Timer{
  def apply[P](interval: FiniteDuration, delay: FiniteDuration = 0 seconds)
              (implicit scheduler: Scheduler, p: Propagator[P], ec: ExecutionContext) = {

    new Timer(interval, delay)
  }
}

/**
 * An [[Rx]] which begins a propagation once every `interval` after an initial
 * delay of `delay`. Its value is the number of times it has emitted.
 */
class Timer[P](interval: FiniteDuration, delay: FiniteDuration)
              (implicit scheduler: Scheduler, p: Propagator[P], ec: ExecutionContext)
               extends Rx[Long]{


  private[rx] val count = new AtomicLong(0L)
  private[this] val holder = new WeakTimerHolder(new WeakReference(this), interval, delay)

  def name = "Timer" + this.hashCode()

  def level = 0
  def toTry = Success(count.get)
  def parents: Set[Emitter[_]] = Set.empty
  def ping[P: Propagator](incoming: Set[Emitter[_]]) = {
    this.children
  }
}

/**
 * Wraps a timer, which is managed by Akka or the DOM depending if this is
 * JVM or Javascript. Ensures that the timer gets shut off when the [[Timer]]
 * is killed or garbage collected.
 */
private[rx] class WeakTimerHolder[P](val target: WeakReference[Timer[P]],
                                     interval: FiniteDuration,
                                     delay: FiniteDuration)
                                    (implicit scheduler: Scheduler,
                                     p: Propagator[P],
                                     ec: ExecutionContext){

  def schedule(delay: FiniteDuration): Unit = {
    scheduler.scheduleOnce(delay){
      (target.get: Timer[_]) match{
        case null =>
        case timer if timer.alive =>
          schedule(interval)
          timer.count.getAndIncrement
          timer.propagate()

        case _ =>
      }
    }
  }

  schedule(delay)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy