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

codata.async.mutable.Signal.scala Maven / Gradle / Ivy

package org.specs2.codata.async.mutable

import org.specs2.codata.Cause._
import scalaz.\/
import scalaz.concurrent._
import org.specs2.codata.Process._
import org.specs2.codata._
import org.specs2.codata.async.mutable
import java.util.concurrent.atomic.AtomicReference


/**
 * A signal whose value may be set asynchronously. Provides continuous
 * and discrete streams for responding to changes to this value.
 */
trait Signal[A] extends org.specs2.codata.async.immutable.Signal[A] {


  /**
   * Returns sink that can be used to set this signal
   */
  def sink: Sink[Task, Signal.Msg[A]]


  /**
   * Asynchronously refreshes the value of the signal,
   * keep the value of this `Signal` the same, but notify any listeners.
   * If the `Signal` is not yet set, this is no-op
   */
  def refresh: Task[Unit] = compareAndSet(oa=>oa).map(_=>())

  /**
   * Asynchronously get the current value of this `Signal`
   */
  def get: Task[A]


  /**
   * Sets the value of this `Signal`.
   */
  def set(a: A): Task[Unit]

  /**
   * Asynchronously sets the current value of this `Signal` and returns previous value of the `Signal`.
   * If this `Signal` has not been set yet, the Task will return None and value is set. If this `Signal`
   * is `finished` Task will fail with `End` exception. If this `Signal` is `failed` Task will fail
   * with `Signal` failure exception.
   *
   */
  def getAndSet(a:A) : Task[Option[A]]

  /**
   * Asynchronously sets the current value of this `Signal` and returns new value os this `Signal`.
   * If this `Signal` has not been set yet, the Task will return None and value is set. If this `Signal`
   * is `finished` Task will fail with `End` exception. If this `Signal` is `failed` Task will fail
   * with `Signal` failure exception.
   *
   * Furthermore if `f` results in evaluating to None, this Task is no-op and will return current value of the
   * `Signal`.
   *
   */
  def compareAndSet(f: Option[A] => Option[A]) : Task[Option[A]]

  /**
   * Indicate that the value is no longer valid. Any attempts to `set` or `get` this
   * `Signal` after a `close` will fail with `Terminated(End)` exception. This `Signal` is `finished` from now on.
   *
   * Running this task once the `Signal` is `failed` or `finished` is no-op and this task will not fail.
   *
   */
  def close : Task[Unit] = failWithCause(End)

  /**
   * Indicate that the value is no longer valid. Any attempts to `set` or `get` this
   * `Signal` after a `close` will fail with `Terminated(Kill)` exception. This `Signal` is `finished` from now on.
   *
   * Running this task once the `Signal` is `failed` or `finished` is no-op and this task will not fail.
   *
   */
  def kill: Task[Unit] = failWithCause(Kill)

  /**
   * Raise an asynchronous error for readers of this `Signal`. Any attempts to
   * `set` or `get` this `Ref` after the `fail` will result in task failing with `Terminated(Error(errr))`.
   * This `Signal` is `failed` from now on.
   *
   * Running this task once the `Signal` is `failed` or `finished` is no-op and this task will not fail.
   */
  def fail(error: Throwable): Task[Unit] = failWithCause(Error(error))



  private[codata] def failWithCause(c:Cause):Task[Unit]


}


object Signal {

  sealed trait Msg[+A]

  /**
   * Sets the signal to given value
   */
  case class Set[A](a:A) extends Msg[A]

  /**
   * Conditionally sets signal to given value. Acts similarly as `Signal.compareAndSet`
   */
  case class CompareAndSet[A](f:Option[A] => Option[A]) extends Msg[A]



  protected[async] def signalWriter[A]: Writer1[A,Msg[A],Nothing] = {
    def go(oa:Option[A]) : Writer1[A,Msg[A],Nothing] =
    receive1[Msg[A], A \/ Nothing] {
      case Set(a) => tell(a) ++ go(Some(a))
      case CompareAndSet(f:(Option[A] => Option[A])@unchecked) =>
        val next = f(oa)
        next match {
          case Some(a) => tell(a) ++ go(Some(a))
          case None => go(oa)
        }
    }
    go(None)
  }


  protected[async] def apply[A](from:Option[A] \/ Process[Task,Msg[A]])(implicit S: Strategy) : Signal[A] = {
    val writer: Writer1[A, Msg[A], Nothing] =
      from.swap.toOption.flatten map { a => process1.feed1(Set(a): Msg[A])(signalWriter) } getOrElse signalWriter

    val topic = WriterTopic.apply[A,Msg[A],Nothing](writer)(from.toOption getOrElse halt, from.isRight)

    new mutable.Signal[A] {
      def changed: Process[Task, Boolean] = topic.signal.changed
      def discrete: Process[Task, A] = topic.signal.discrete
      def continuous: Process[Task, A] = topic.signal.continuous
      def changes: Process[Task, Unit] = topic.signal.changes
      def sink: Sink[Task, Msg[A]] = topic.publish
      def get: Task[A] = discrete.take(1).runLast.flatMap {
        case Some(a) => Task.now(a)
        case None    => Task.fail(Terminated(End))
      }
      def getAndSet(a: A): Task[Option[A]] = {
        for {
          ref <- Task.delay(new AtomicReference[Option[A]](None))
          _ <- topic.publishOne(CompareAndSet[A](curr => curr.map{ca => ref.set(Some(ca)); a}))
        } yield ref.get()
      }
      def set(a: A): Task[Unit] = topic.publishOne(Set(a))
      def compareAndSet(f: (Option[A]) => Option[A]): Task[Option[A]] = {
        for {
          ref <- Task.delay(new AtomicReference[Option[A]](None))
          _ <- topic.publishOne(CompareAndSet[A]({ curr =>
            val r = f(curr)
            ref.set(r orElse curr)
            r
          }))
        } yield ref.get()
      }
      private[codata] def failWithCause(c: Cause): Task[Unit] = topic.failWithCause(c)
    }

  }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy