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

lucuma.ui.syntax.effect.scala Maven / Gradle / Ivy

// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package lucuma.ui.syntax

import cats.MonadThrow
import cats.data.NonEmptyList
import cats.effect.Resource
import cats.effect.Temporal
import cats.syntax.all.*
import crystal.*

import scala.concurrent.duration.*

trait effect {
  extension [F[_], A](self: F[A])
    /**
     * Materializes exceptions into values as `Pot.Error`, and return values as `Pot.Ready`.
     */
    def attemptPot(using MonadThrow[F]): F[Pot[A]] = self.attempt.map(_.toTry.some.toPot)

    /**
     * Given an effect producing an A and a signal stream, runs the effect and then re-runs it
     * whenver a signal is received, producing a Stream[A].
     */
    def reRunOnSignal(
      signal:   fs2.Stream[F, Unit],
      debounce: Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): fs2.Stream[F, A] = {
      val debouncedSignal = debounce.fold(signal)(signal.debounce)
      fs2.Stream.eval(self) ++ debouncedSignal.evalMap(_ => self)
    }

    /**
     * Given an effect producing an A and a bunch of signal streams, runs the effect and then
     * re-runs it whenver a signal is received, producing a Stream[A].
     */
    def reRunOnSignals(
      signals:  NonEmptyList[fs2.Stream[F, Unit]],
      debounce: Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): fs2.Stream[F, A] =
      reRunOnSignal(signals.reduceLeft(_ merge _), debounce)

    /**
     * Given an effect producing an A and a bunch of `Resource`s providing signal streams, runs the
     * effect and then re-runs it whenver a signal is received, producing a Stream[A].
     */
    def reRunOnResourceSignals(
      subscriptions: NonEmptyList[Resource[F, fs2.Stream[F, ?]]],
      debounce:      Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): Resource[F, fs2.Stream[F, A]] =
      subscriptions.sequence
        .map(ss => reRunOnSignals(ss.map(_.void), debounce))

    /**
     * Given an effect producing an A and a bunch of `Resource`s providing signal streams, runs the
     * effect and then re-runs it whenver a signal is received, producing a Stream[A].
     */
    def reRunOnResourceSignals(
      head: Resource[F, fs2.Stream[F, ?]],
      tail: Resource[F, fs2.Stream[F, ?]]*
    )(using Temporal[F]): Resource[F, fs2.Stream[F, A]] =
      reRunOnResourceSignals(
        NonEmptyList.of(head, tail*),
        2.seconds.some // For some reason, compiler can't resolve default parameter value here
      )

    /**
     * Given an effect producing an A and a bunch of `Resource`s providing signal streams, runs the
     * effect and then re-runs it whenver a signal is received, producing a Stream[A].
     */
    def reRunOnResourceSignals(
      debounce: FiniteDuration,
      head:     Resource[F, fs2.Stream[F, ?]],
      tail:     Resource[F, fs2.Stream[F, ?]]*
    )(using Temporal[F]): Resource[F, fs2.Stream[F, A]] =
      reRunOnResourceSignals(NonEmptyList.of(head, tail*), debounce.some)

  extension [F[_], A](f: F[Pot[A]])
    /**
     * Given an effect `f` producing an A and a bunch of signal streams, returns a `Stream` that
     * starts with the result of `f` and then upon each signal, produces `Pending` followed by the
     * result of re-evaluating `f`.
     */
    def resetOnSignal(
      signal:   fs2.Stream[F, Unit],
      debounce: Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): fs2.Stream[F, Pot[A]] = {
      val debouncedSignal = debounce.fold(signal)(signal.debounce)
      fs2.Stream.eval(f) ++ debouncedSignal.flatMap(_ =>
        fs2.Stream(Pot.pending) ++ fs2.Stream.eval(f)
      )
    }

    /**
     * Given an effect `f` producing an A and a bunch of signal streams, returns a `Stream` that
     * starts with the result of `f` and then upon each signal, produces `Pending` followed by the
     * result of re-evaluating `f`.
     */
    def resetOnSignals(
      signals:  NonEmptyList[fs2.Stream[F, Unit]],
      debounce: Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): fs2.Stream[F, Pot[A]] =
      resetOnSignal(signals.reduceLeft(_ merge _), debounce)

    private def resetOnResourceSignalsB(
      subscriptions: NonEmptyList[Resource[F, fs2.Stream[F, ?]]],
      debounce:      Option[FiniteDuration] = 2.seconds.some
    )(using Temporal[F]): Resource[F, fs2.Stream[F, Pot[A]]] =
      subscriptions.sequence
        .map(ss => resetOnSignals(ss.map(_.void), debounce))

    /**
     * Given an effect `f` producing an A and a bunch of `Resource`s providing signal streams,
     * returns a `Stream` that starts with the result of `f` and then upon each signal, produces
     * `Pending` followed by the result of re-evaluating `f`.
     */
    def resetOnResourceSignals(
      head: Resource[F, fs2.Stream[F, ?]],
      tail: Resource[F, fs2.Stream[F, ?]]*
    )(using Temporal[F]): Resource[F, fs2.Stream[F, Pot[A]]] =
      resetOnResourceSignalsB(NonEmptyList.of(head, tail*))

    /**
     * Given an effect `f` producing an A and a bunch of `Resource`s providing signal streams,
     * returns a `Stream` that starts with the result of `f` and then upon each signal, produces
     * `Pending` followed by the result of re-evaluating `f`.
     */
    def resetOnResourceSignals(
      debounce: FiniteDuration,
      head:     Resource[F, fs2.Stream[F, ?]],
      tail:     Resource[F, fs2.Stream[F, ?]]*
    )(using Temporal[F]): Resource[F, fs2.Stream[F, Pot[A]]] =
      resetOnResourceSignalsB(NonEmptyList.of(head, tail*), debounce.some)
}

object effect extends effect




© 2015 - 2025 Weber Informatics LLC | Privacy Policy