Listeners

A Listener[M, E, S] watches slice S and runs S => IO[E, Unit] when that slice changes per FastEq. c.subscribe(_.count)(cb) registers immediately (it does not wait for run()). The Subscribe / Unsubscribe ops exist so a handler can wire a listener as a follow-up.

Slice notifications

Changing label does not fire a _.count listener. The timeline below only records count.

Mermoid.diagram(slice)
for
  c    <- Conduit(S(0, "x"))(handler)
  seen <- Ref.make(List.empty[Int])
  _    <- c.subscribe(_.count)(n => seen.update(_ :+ n))
  _    <- c(Op.Inc, Op.Inc, Op.SetLabel("y"), Op.Inc, Op.Dec)
  _    <- c.run()
  ns   <- seen.get
yield ns
List(1, 2, 3, 2)

Live log

The log appends only when count changes. Toggle the label: the log stays still. +/− append a line.

for
  (c, ctx) <- DocsRuntime.live(S(0, "x"))(handler)
  log      <- sq(List.empty[String])
  _        <- c.subscribe(_.count)(n => log.update(_ :+ s"count=$n").unit)
  count    <- ctx.squawk(_.count)
  label    <- ctx.squawk(_.label)
yield E.div(
  E.p("count ", E.strong(count.map(_.toString)), "  label ", E.strong(label)),
  E.button(Events.onClick(_ => ctx(Op.Dec)), "−"),
  E.button(Events.onClick(_ => ctx(Op.Inc)), "+"),
  E.button(Events.onClick(_ => ctx(Op.SetLabel("alpha"))), "Label alpha"),
  E.button(Events.onClick(_ => ctx(Op.SetLabel("beta"))), "Label beta"),
  E.pre(log.map(lines => if lines.isEmpty then "(no count events yet)" else lines.mkString("\n"))),
)

count 0 label x

(no count events yet)

Subscribe as a follow-up

A handler can return Subscribe(listener) in ActionResult.next. Until that op runs, the callback never fires. c.unsubscribe(listener) removes it; there is also an Unsubscribe op.

Mermoid.diagramInteractive(slice, initialWidth = 640)
viewport 640px