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

rx.core.Core.scala Maven / Gradle / Ivy

package rx.core

import util.Try

import java.util.concurrent.atomic.AtomicReference
import rx.core

object Rx{

  /**
   * This guys sole purpose is to help implement a "keyword-only" argument
   * list in Rx.apply
   */
  object Cookie
  /**
   * Creates an [[Rx]] that is defined relative to other [[Rx]]s, and
   * updates automatically when they change.
   *
   * @param calc The method of calculating the value of this [[Rx]]. This
   *             expression should be pure, as it may be evaluated multiple
   *             times redundantly.
   * @param name The name of this [[Rx]]
   * @param default The default value for this [[Rx]]
   * @tparam T The type of the value this [[Rx]] contains
   */
  def apply[T](calc: => T): Rx[T] = {
    new Dynamic(() => calc)
  }

  def apply[T](c: Cookie.type = Cookie,
               name: String = "")
              (calc: => T): Rx[T] = {
    new core.Dynamic(() => calc, name)
  }
}

/**
 * An [[Rx]] is a value that can change over time, emitting pings whenever it
 * changes to notify any dependent [[Rx]]s that they need to update.
 *
 */
trait Rx[+T] extends Emitter[T] with Reactor[Any]{

  protected[this] def currentValue: T = toTry.get

  /**
   * Identical to `apply()`, except that it does not create a dependency if
   * called within the body of another [[Rx]]
   *
   *@return The current value of this [[Rx]]
   */
  def now: T = currentValue

  /**
   * Returns current value of this [[Rx]]. If this is called within the body of
   * another [[Rx]], this will create a dependency between the two [[Rx]]s. If
   * this [[Rx]] contains an exception, that exception gets thrown.
   *
   * @return The current value of this [[Rx]]
   */
  def apply(): T = {
    Dynamic.enclosing.value = Dynamic.enclosing.value match{
      case Some((enclosing, dependees)) =>
        this.linkChild(enclosing)
        Some((enclosing, this :: dependees))
      case None => None
    }
    currentValue
  }

  def propagate[P: Propagator]() = {
    Propagator().propagate(this.children.map(this -> _))
  }

  /**
   * Returns the current value stored within this [[Rx]] as a `Try`
   */
  def toTry: Try[T]

  /**
   * descndents
   * descendants
   * ancestors
   * Shorthand to call `.kill()` on this [[Rx]] as well as any of its
   */
  def killAll(): Unit = {
    this.kill()
    descendants.foreach(_.kill())
  }

  /**
   * Forces this [[Rx]] to recalculate its value. This typically does
   * nothing unless the [[Rx]] is a [[Dynamic]], since only [[Dynamic]]s
   * can have their calculated value change without their ancestors
   * pinging them.
   */
    def recalc[P: Propagator](): P = Propagator().propagate(Set(this -> this))
}

class Staged[T](val v: Var[T], t: T){
  def commit() = v.updateSilent(t)
}

object Var {
  /**
   * Convenience method for creating a new [[Var]].
   */
  def apply[T](value: => T, name: String = "") = {
    new Var(value, name)
  }
  def set[P: Propagator](thingies: Staged[_]*) = {
    thingies.foreach(_.commit())

    Propagator().propagate(
      thingies.flatMap{ v =>
        v.v.children
           .map(v.v.asInstanceOf[Emitter[_]] -> _)
      }.toSet
    )
  }
}

/**
 * A [[Var]] is an [[Rx]] which can be changed manually via assignment.
 *
 * @param initValue The initial future of the Var
 */
class Var[T](initValue: => T, val name: String = "") extends Rx[T]{

  private val state = new AtomicReference(Try(initValue))

  /**
   * Updates the value in this `Var` and propagates the change through to its
   * children and descendents
   */
  def update[P: Propagator](newValue: => T): P = {
    updateSilent(newValue)
    propagate()
  }

  /**
   * Updates the value in this `Var` *without* propagating the change through
   * to its children and descendents
   */
  def updateSilent(newValue: => T) = {
    state.set(Try(newValue))
  }
  def level = 0

  def toTry = state.get()
  def parents: Set[Emitter[Any]] = Set.empty

  def ping[P: Propagator](incoming: Set[Emitter[_]]) = {
    this.children
  }
}
object Obs{
  /**
   * Convenience method for creating a new [[Obs]].
   */
  def apply[T](es: Emitter[Any], name: String = "", skipInitial: Boolean = false)
              (callback: => Unit) = {
    new Obs(es, () => callback, name, skipInitial)
  }

}

/**
 * An [[Obs]] is something that produces side-effects when the source [[Rx]]
 * changes. An [[Obs]] is always run right at the end of every propagation wave,
 * ensuring it is only called once per wave (in contrast with [[Rx]]s, which
 * may update multiple times before settling).
 *
 * @param callback a callback to run when this Obs is pinged
 */
class Obs(source: Emitter[Any],
          callback: () => Unit,
          val name: String = "",
          skipInitial: Boolean = false)
          extends Reactor[Any]{

  source.linkChild(this)

  def parents = Set(source)

  def level = Long.MaxValue

  def ping[P: Propagator](incoming: Set[Emitter[_]]) = {
    if (!parents.intersect(incoming).isEmpty){
      callback()
    }
    Set.empty
  }

  /**
   * Manually trigger this observer, causing its callback to run.
   */
  def trigger() = {
    this.ping(this.parents)(Propagator.Immediate)
  }

  if (!skipInitial) trigger()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy