From cec1fe0316ad2e76d703d36efb64e23dfb254d2b Mon Sep 17 00:00:00 2001 From: Laimonas Turauskas Date: Tue, 5 Dec 2023 16:26:20 -0800 Subject: [PATCH] [Formula] Explore event batching. --- .../main/java/com/instacart/formula/Action.kt | 7 ++++++ .../com/instacart/formula/FormulaContext.kt | 1 + .../formula/internal/ActionBuilderImpl.kt | 7 +++++- .../formula/internal/DeferredTransition.kt | 1 + .../formula/internal/FormulaManagerImpl.kt | 22 +++++++++++++++++++ .../formula/internal/ListenerImpl.kt | 3 ++- .../formula/internal/SnapshotImpl.kt | 2 ++ 7 files changed, 41 insertions(+), 2 deletions(-) diff --git a/formula/src/main/java/com/instacart/formula/Action.kt b/formula/src/main/java/com/instacart/formula/Action.kt index 18d39d11..b32324f6 100644 --- a/formula/src/main/java/com/instacart/formula/Action.kt +++ b/formula/src/main/java/com/instacart/formula/Action.kt @@ -100,6 +100,13 @@ interface Action { * An identifier used to distinguish between different types of actions. */ fun key(): Any? + + /** + * Defines if an action updates can be batched with other updates. This enables Formula + * to batch multiple updates into a single re-evaluation and emit a single output instead of + * emitting multiple intermediate updates. + */ + fun isBatchable() = false // TODO default } /** diff --git a/formula/src/main/java/com/instacart/formula/FormulaContext.kt b/formula/src/main/java/com/instacart/formula/FormulaContext.kt index 6924da5d..c0b60104 100644 --- a/formula/src/main/java/com/instacart/formula/FormulaContext.kt +++ b/formula/src/main/java/com/instacart/formula/FormulaContext.kt @@ -131,6 +131,7 @@ abstract class FormulaContext internal constructor( internal abstract fun eventListener( key: Any, useIndex: Boolean = true, + isBatchable: Boolean = false, transition: Transition ): Listener diff --git a/formula/src/main/java/com/instacart/formula/internal/ActionBuilderImpl.kt b/formula/src/main/java/com/instacart/formula/internal/ActionBuilderImpl.kt index d3d949e2..83c06ea2 100644 --- a/formula/src/main/java/com/instacart/formula/internal/ActionBuilderImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/ActionBuilderImpl.kt @@ -52,7 +52,12 @@ internal class ActionBuilderImpl internal constructor( transition: Transition, ): DeferredAction { val key = snapshot.context.createScopedKey(transition.type(), stream.key()) - val listener = snapshot.context.eventListener(key, useIndex = false, transition) + val listener = snapshot.context.eventListener( + key = key, + useIndex = false, + isBatchable = stream.isBatchable(), + transition = transition, + ) return DeferredAction( key = key, action = stream, diff --git a/formula/src/main/java/com/instacart/formula/internal/DeferredTransition.kt b/formula/src/main/java/com/instacart/formula/internal/DeferredTransition.kt index 781a4408..0db9f29b 100644 --- a/formula/src/main/java/com/instacart/formula/internal/DeferredTransition.kt +++ b/formula/src/main/java/com/instacart/formula/internal/DeferredTransition.kt @@ -13,6 +13,7 @@ class DeferredTransition internal constructor( private val listener: ListenerImpl, private val transition: Transition, private val event: EventT, + val isBatchable: Boolean, ) { fun execute() { diff --git a/formula/src/main/java/com/instacart/formula/internal/FormulaManagerImpl.kt b/formula/src/main/java/com/instacart/formula/internal/FormulaManagerImpl.kt index c880738b..e7466992 100644 --- a/formula/src/main/java/com/instacart/formula/internal/FormulaManagerImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/FormulaManagerImpl.kt @@ -259,6 +259,27 @@ internal class FormulaManagerImpl( // then we'll execute the transition. transitionQueue.addLast(transition) } else { + // If we pass this to the root manager it will add it to a queue. + // - When ready, it will process first one + // - First one, triggers state change and asks for evaluation + // - Can we execute more updates before we start an evaluation? + // - If same formula has multiple batched updates, we need to evaluate it + // - If different formulas have batched updates, we likely could trigger all + // before a single evaluation setups actions and picks up new output. + // - Maybe we need to have `canAcceptTransition()` function + // - If my state changed already, I cannot accept new transition + // - If child state changed, can I accept state change myself? + // it is ready to process these updates, it will t + // We likely need to add this locally and also pass it to the parent + // + // TODO: this is the place where we need to batch + + + // + // If for batchable updates, we add it to the queue and request re-eval? + // that be bad for performance? We could also optimize that via some peek mechanism + // that starts the + // Is update order important? transition.execute() } } @@ -295,6 +316,7 @@ internal class FormulaManagerImpl( return true } + @Suppress("RedundantIfStatement") if (!terminated && actionManager.startNew(evaluationId)) { return true } diff --git a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt index 3cf42ca6..f8255b9e 100644 --- a/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/ListenerImpl.kt @@ -11,6 +11,7 @@ internal class ListenerImpl(internal var key: Any) : Liste internal var manager: FormulaManagerImpl? = null internal var snapshotImpl: SnapshotImpl? = null + internal var isBatchable: Boolean = false internal lateinit var transition: Transition @@ -18,7 +19,7 @@ internal class ListenerImpl(internal var key: Any) : Liste // TODO: log if null listener (it might be due to formula removal or due to callback removal) val manager = manager ?: return - val deferredTransition = DeferredTransition(this, transition, event) + val deferredTransition = DeferredTransition(this, transition, event, isBatchable) manager.onPendingTransition(deferredTransition) } diff --git a/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt b/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt index 01cefbd4..ace44656 100644 --- a/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt +++ b/formula/src/main/java/com/instacart/formula/internal/SnapshotImpl.kt @@ -47,6 +47,7 @@ internal class SnapshotImpl internal constructor( override fun eventListener( key: Any, useIndex: Boolean, + isBatchable: Boolean, transition: Transition ): Listener { ensureNotRunning() @@ -54,6 +55,7 @@ internal class SnapshotImpl internal constructor( listener.manager = delegate listener.snapshotImpl = this listener.transition = transition + listener.isBatchable = isBatchable return listener }