You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

121 lines
3.7 KiB

package outwatchapp.util.reactive
import scala.concurrent.Future
import io.circe.Decoder
import io.circe.Encoder
import io.circe.Printer
import io.circe.generic.JsonCodec
import io.circe.parser._
import io.circe.syntax._
import monix.bio.Task
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.Observer
import monix.reactive.OverflowStrategy
import org.scalajs.dom.raw.Event
import org.scalajs.dom.raw.MessageEvent
import org.scalajs.dom.{raw => sjsdr}
import outwatchapp.util.reactive.MonixProSubject
import monix.execution.cancelables.CompositeCancelable
import monix.eval.Coeval
import monix.reactive.observers.Subscriber
import monix.execution.Scheduler
import monix.reactive.observers.BufferedSubscriber
import outwatchapp.util.reactive.Exceptions.DecodeException
import outwatchapp.util.reactive.Exceptions.WrongTypeException
import outwatchapp.util.reactive.Exceptions.TerminatedException
class WebWorkerImpl[T <: Product: Encoder: Decoder](
worker: sjsdr.Worker,
overflowStrategy: OverflowStrategy.Synchronous[T]
) {
val printer = Printer.noSpaces
val source: Task[Observable[T]] =
Task.deferAction(implicit s =>
for {
c <- Task(CompositeCancelable())
obs <- Task(
Observable
.create[T](overflowStrategy) { sub =>
// val c = SingleAssignCancelable()
worker.onmessage = (e: MessageEvent) =>
e.data match {
case s: String =>
decode[T](s)
.map { res =>
if (sub.onNext(res) == Ack.Stop) c.cancel()
res
}
.left
.foreach(err =>
sub.onError(
DecodeException(
s"Failed to decode $s. Error was $err"
)
)
)
case other =>
sub.onError(
WrongTypeException(s"Received wrong type: $other")
)
}
worker.onerror = (e: Event) =>
sub.onError(
TerminatedException(s"Worker terminated with error: $e")
)
c += Cancelable(() => worker.terminate())
}
.doOnSubscriptionCancelF(Coeval(println("Worker cancelled")))
.publish
.refCount
)
_ <- Task(c += obs.subscribe(Observer.empty))
} yield obs
)
val sink: Task[Observer[T]] =
Task.deferAction(implicit s =>
Task(
BufferedSubscriber(
new Subscriber[T] {
override implicit def scheduler: Scheduler = s
override def onNext(elem: T): Future[Ack] = {
val msg = printer.print(elem.asJson)
worker.postMessage(msg)
Ack.Continue
}
override def onError(ex: Throwable): Unit = s.reportFailure(ex)
override def onComplete(): Unit = ()
},
OverflowStrategy.Default
)
)
)
}
object WebWorker {
sealed trait Error
final case object Error extends Error
def apply[T <: Product: Encoder: Decoder](
path: String,
overflowStrategy: OverflowStrategy.Synchronous[T] =
OverflowStrategy.DropOld(50)
): Task[WebWorker[T]] = for {
worker <- Task(new sjsdr.Worker(path))
impl = new WebWorkerImpl[T](worker, overflowStrategy)
source <- impl.source
sink <- impl.sink
} yield MonixProSubject.from(sink, source)
}
@JsonCodec
case class WorkerData(data: Long)