Skip to content

Commit

Permalink
KRZ-189 Turbo Mode
Browse files Browse the repository at this point in the history
  • Loading branch information
nob13 committed Apr 25, 2024
1 parent ac0742a commit cef5174
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import scala.util.control.NonFatal
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue

/** Encapsulate the highly stateful event handling. */
class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
class EventManager(delegate: EventManagerDelegate)(using sp: ServiceRepository) {

/** A Pending change. */
private sealed trait PendingChange
Expand Down Expand Up @@ -130,11 +130,11 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
node: TreeNode,
sourceSink: EventBinding.SourceSink[E]
): Unit = {
val transformedSink = transformSink(node, sourceSink.sink)
val transformedSink = transformSink(sourceSink.sink)
bindEventSource(node, sourceSink.source, transformedSink)
}

private def transformSink[T](node: TreeNode, eventSink: EventSink[T]): T => Unit = {
private def transformSink[T](eventSink: EventSink[T]): T => Unit = {
eventSink match
case EventSink.ModelChange(modelId, f) =>
eventData =>
Expand All @@ -145,27 +145,73 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
_pending.append(change)
ensureNextIteration()
case EventSink.ChannelSink(channel) =>
eventData => triggerChannel(channel, eventData)
eventData => triggerWeakChannel(channel, eventData)
case EventSink.ExecuteCode(f) => f
case EventSink.SetProperty(property) =>
eventData => {
updateJsProperty(eventData, property)
}
case EventSink.PreTransformer(underlying, transformer) =>
preTransformSink(node, transformer, underlying)
preTransformSink(transformer, underlying)
case EventSink.Handler(fn) =>
value => {
fn(eventHandlerContext, value)
}
}

private object eventHandlerContext extends HandlerContext {
override def setModel[T](model: Model[T], value: T): Unit = {
updateModel(model, _ => value)
}

override def updateModel[T](model: Model[T], updateFn: T => T): Unit = {
val change = PendingModelChange(model, fn = updateFn)
_pending.append(change)
ensureNextIteration()
}

override def triggerChannel[T](channel: Channel[T], value: T): Unit = {
EventManager.this.triggerChannel(channel, value)
}

override def triggerSink[E](sink: EventSink[E], value: E): Unit = {
transformSink(sink)(value)
}

override def state[T](state: RuntimeState[T]): T = {
fetchStateUnsafe(state)
}

override def setProperty[D <: ScalaJsElement, T](property: RuntimeState.JsProperty[D, T], value: T): Unit = {
updateJsProperty(value, property)
}

override def value[M](model: Subscribeable[M]): M = {
_currentState.value(model)
}

override def serviceOption[S](using snp: ServiceNameProvider[S]): Option[S] = {
sp.serviceOption
}

override def execute(runnable: Runnable): Unit = {
implicitly[ExecutionContext].execute(runnable)
}

override def reportFailure(cause: Throwable): Unit = {
implicitly[ExecutionContext].reportFailure(cause)
}
}

private def preTransformSink[E, F](
node: TreeNode,
transformer: EventTransformer[E, F],
sink: EventSink[F]
): E => Unit = {
val underlying = transformSink(node, sink)
buildTransformer(node, transformer, underlying)
val underlying = transformSink(sink)
buildTransformer(transformer, underlying)
}

private def buildTransformer[E, F](
node: TreeNode,
transformer: EventTransformer[E, F],
underlying: F => Unit
): E => Unit = {
Expand Down Expand Up @@ -197,7 +243,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
}
}
case EventTransformer.TryUnpack1(failureSink) => {
val failureSinkTransformed = transformSink(node, failureSink)
val failureSinkTransformed = transformSink(failureSink)
in => {
in match {
case Success(ok) => underlying(ok)
Expand All @@ -206,7 +252,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
}
}
case EventTransformer.TryUnpack2(failureSink) => {
val failureSinkTransformed = transformSink(node, failureSink)
val failureSinkTransformed = transformSink(failureSink)
in => {
in match {
case (v, Success(ok)) => underlying(v -> ok)
Expand All @@ -215,7 +261,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
}
}
case EventTransformer.And(other) => {
val othersTransformed = transformSink(node, other)
val othersTransformed = transformSink(other)
in => {
othersTransformed.apply(in)
underlying(in)
Expand All @@ -225,20 +271,24 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
underlying(in)
}
case EventTransformer.Chained(a, b) => {
val preTransform1 = buildTransformer(node, b, underlying)
buildTransformer(node, a, preTransform1)
val preTransform1 = buildTransformer(b, underlying)
buildTransformer(a, preTransform1)
}
}
}

private def triggerChannel[T](ref: WeakReference[Channel[T]], data: T): Unit = {
private def triggerWeakChannel[T](ref: WeakReference[Channel[T]], data: T): Unit = {
ref.get match {
case Some(c) =>
_channelBindings.foreachKey(c.id) { _.asInstanceOf[ChannelBinding[T]].handler(data) }
triggerChannel(c, data)
case None => // nothing
}
}

private def triggerChannel[T](channel: Channel[T], data: T): Unit = {
_channelBindings.foreachKey(channel.id) { _.asInstanceOf[ChannelBinding[T]].handler(data) }
}

private def bindEventSource[E](ownNode: TreeNode, eventSource: EventSource[E], sink: E => Unit): Unit = {
eventSource match
case j: EventSource.Js[_] =>
Expand Down Expand Up @@ -306,7 +356,7 @@ class EventManager(delegate: EventManagerDelegate)(using ServiceRepository) {
}
_registeredTimers.add(ownNode.id, timer)
case EventSource.PostTransformer(source, transformer) =>
val transformedSink = buildTransformer(ownNode, transformer, sink)
val transformedSink = buildTransformer(transformer, sink)
bindEventSource(ownNode, source, transformedSink)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,10 @@ case object Counter extends SimpleComponentBase {
override def assemble(using c: SimpleContext): Html = {
val counter = subscribe(model)

add(
EventSource
.Timer(1.second, true)
.changeModelDirect(model) { x => x + 1 }
)
addHandler(EventSource.Timer(1.second, true)) { _ =>
model.set(model.read + 1)
}

all.span(s"Showing for ${counter} seconds")
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ object XmlPage extends SimpleComponentBase {
val scalaTagsLabel = Label(value)
val xmlLabel = XmlLabel(value)

add(
editor.onInputEvent
.withState(editor.text)
.changeModel(value)((n, _) => n)
)
addHandler(editor.onInputEvent) { _ =>
val v = editor.text.read
value.set(v)
}

<div>
<h2>XML Example</h2>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,22 @@ object TodoPageWithApi extends SimpleComponentBase {
val lister = provide[Api].todoApi
val form = TodoAdderForm

addHandler(form.onAdd) { text =>
lister.addItem(text).foreach { _ =>
LazyTodoViewer.refresh.trigger(())
form.clear.trigger(())
}
}

/*
add(
form.onAdd
.future(text => lister.addItem(text))
.to(LazyTodoViewer.refresh)
.and
.trigger(form.clear)
)
*/

div(
h2("API Based TODO App"),
Expand Down
7 changes: 7 additions & 0 deletions lib/shared/src/main/scala/kreuzberg/Channel.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kreuzberg

import scala.ref.WeakReference

/**
* A Channel is something where you can send data to and can subscribe in event bindings. They are allowed to be
* singletons. They are identified using their ID. There is only one channel of the same id allowed within an Engine.
Expand All @@ -15,6 +17,11 @@ final class Channel[T] private {
case _ => false
}
}

/** Trigger from Handler. */
def trigger(value: T)(using h: HandlerContext): Unit = {
h.triggerChannel(this, value)
}
}

object Channel {
Expand Down
2 changes: 1 addition & 1 deletion lib/shared/src/main/scala/kreuzberg/Effect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ case class Effect[T](fn: ExecutionContext => Future[T]) {
object Effect {

def const[T](value: T): Effect[T] = Effect(_ => Future.successful(value))

inline def future[T](f: ExecutionContext ?=> Future[T]): Effect[T] = Effect(ec => f(using ec))
}
14 changes: 14 additions & 0 deletions lib/shared/src/main/scala/kreuzberg/EventBinding.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ sealed trait EventSource[+E] extends EventTransformable[E] {
override def to[T >: E](sink: EventSink[T]): EventBinding.SourceSink[T] = {
EventBinding.SourceSink(this, sink)
}

/** Attach an imperative handler. */
def handle[T >: E](f: T => HandlerContext ?=> Unit): EventBinding.SourceSink[T] = {
val sink = EventSink.Handler[T]((c, v) => f(v)(using c))
to(sink)
}
}

object EventSource {
Expand Down Expand Up @@ -143,6 +149,11 @@ sealed trait EventSink[-E] {
def contraMap[F](f: F => E): EventSink[F] = {
preTransform(EventTransformer.Map(f))
}

/** Call from an imperative handler */
def trigger(value: E)(using h: HandlerContext): Unit = {
h.triggerSink(this, value)
}
}

object EventSink {
Expand All @@ -162,6 +173,9 @@ object EventSink {
/** Pretransformes a sink. */
case class PreTransformer[E, F](sink: EventSink[F], transformer: EventTransformer[E, F]) extends EventSink[E]

/** An imperative handler. */
case class Handler[E](f: (HandlerContext, E) => Unit) extends EventSink[E]

object ChannelSink {
inline def apply[E](channel: Channel[E]): ChannelSink[E] = ChannelSink(WeakReference(channel))
}
Expand Down
29 changes: 29 additions & 0 deletions lib/shared/src/main/scala/kreuzberg/HandlerContext.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kreuzberg

import kreuzberg.RuntimeState.JsProperty
import kreuzberg.dom.ScalaJsElement

import scala.concurrent.ExecutionContext
import scala.ref.WeakReference

/** Context for imperative Event Handlers */
trait HandlerContext extends ModelValueProvider with ServiceRepository with ExecutionContext {

/** Issue a model change. */
def setModel[T](model: Model[T], value: T): Unit

/** Update model, existing to new state */
def updateModel[T](model: Model[T], updateFn: T => T): Unit

/** Trigger a channel. */
def triggerChannel[T](channel: Channel[T], value: T): Unit

/** Call another Event sink */
def triggerSink[E](sink: EventSink[E], value: E): Unit

/** Read some JavaScript state. */
def state[T](state: RuntimeState[T]): T

/** Set some JavaScript property. */
def setProperty[D <: ScalaJsElement, T](property: JsProperty[D, T], value: T): Unit
}
12 changes: 11 additions & 1 deletion lib/shared/src/main/scala/kreuzberg/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class Model[T] private (initialValue: ServiceRepository ?=> T) extends Sub
override def equals(obj: Any): Boolean = {
obj match {
case c: Model[_] => id == c.id
case _ => false
case _ => false
}
}

Expand All @@ -55,6 +55,16 @@ final class Model[T] private (initialValue: ServiceRepository ?=> T) extends Sub
override def initial(using ServiceRepository): T = initialValue

override def dependencies: Seq[Identifier] = Seq(id)

/** Set a value from an Handler. */
def set(value: T)(using h: HandlerContext): Unit = {
h.setModel(this, value)
}

/** Update a model from handler. */
def update(f: T => T)(using h: HandlerContext): Unit = {
h.updateModel(this, f)
}
}

object Model {
Expand Down
11 changes: 10 additions & 1 deletion lib/shared/src/main/scala/kreuzberg/RuntimeState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ sealed trait RuntimeState[S] {
def map[S2](f: S => S2): RuntimeState[S2] = {
RuntimeState.Mapping(this, f)
}

/** Read the state from Handler */
def read(using h: HandlerContext): S = {
h.state(this)
}
}

object RuntimeState {
Expand Down Expand Up @@ -44,7 +49,11 @@ object RuntimeState {
componentId: Identifier,
getter: D => S,
setter: (D, S) => Unit
) extends JsRuntimeStateBase[D, S]
) extends JsRuntimeStateBase[D, S] {
def set(value: S)(using h: HandlerContext): Unit = {
h.setProperty(this, value)
}
}

case class And[S1, S2](
left: RuntimeState[S1],
Expand Down
7 changes: 7 additions & 0 deletions lib/shared/src/main/scala/kreuzberg/SimpleContextDsl.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package kreuzberg

import scala.concurrent.Future

/** Helpers for building imperative Components using [[SimpleContext]] */
trait SimpleContextDsl extends ComponentDsl {
self: Component =>
Expand All @@ -25,4 +27,9 @@ trait SimpleContextDsl extends ComponentDsl {
c.addEventBinding(binding0)
others.foreach(c.addEventBinding)
}

/** Add an imperative handler. */
protected def addHandler[E](source: EventSource[E])(f: E => HandlerContext ?=> Unit)(using c: SimpleContext): Unit = {
add(source.handle(f))
}
}

0 comments on commit cef5174

Please sign in to comment.