Skip to content

1247: Partial Tree Rerendering #1248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 6, 2025
Merged

Conversation

steve-the-edwards
Copy link
Contributor

@steve-the-edwards steve-the-edwards commented Jan 20, 2025

Re-render the node only if:

  1. The state (including props) of this node or props has changed.
  2. The state of one of this node's descendants has changed.

In order to do this, we have to propagate the action cascade all the way up to the root to record a single path of changed nodes.

Note we currently do not re-render if the workflow instance (the behavior definition instance, not the node) changed (unless the state also changed!), assuming the new workflow is still compatible with the old rendering because its identifier did not change.

Closes #1247.

@steve-the-edwards steve-the-edwards changed the base branch from main to sedwards/cache-render-context January 21, 2025 20:32
@steve-the-edwards steve-the-edwards force-pushed the sedwards/cache-render-context branch 2 times, most recently from 6f2d156 to 74fb6fb Compare January 23, 2025 15:30
@steve-the-edwards steve-the-edwards force-pushed the sedwards/cache-render-context branch from 74fb6fb to c7d7c15 Compare January 23, 2025 20:22
@steve-the-edwards steve-the-edwards force-pushed the sedwards/cache-render-context branch 2 times, most recently from 0a36563 to 717d484 Compare January 24, 2025 16:24
@steve-the-edwards steve-the-edwards force-pushed the sedwards/partial-tree branch 3 times, most recently from 7d7098f to b6e677c Compare January 24, 2025 19:49
return flow.value
context.runningWorker(rerenderWorker) { output: T ->
action("rerenderUpdate") {
state = output
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prior to this state was not actually changing - so this would not work in the optimized runtime.

@steve-the-edwards steve-the-edwards force-pushed the sedwards/partial-tree branch 2 times, most recently from 8647d68 to e99c6b6 Compare January 27, 2025 14:55
@steve-the-edwards steve-the-edwards force-pushed the sedwards/cache-render-context branch from 717d484 to 646ebf7 Compare January 28, 2025 15:03
Base automatically changed from sedwards/cache-render-context to main January 28, 2025 16:24
@steve-the-edwards steve-the-edwards force-pushed the sedwards/partial-tree branch 5 times, most recently from 3a5f35a to f888f1d Compare January 30, 2025 16:24
@steve-the-edwards steve-the-edwards force-pushed the sedwards/partial-tree branch 2 times, most recently from 2a074ea to b40368b Compare February 20, 2025 16:30
@steve-the-edwards steve-the-edwards changed the base branch from main to sedwards/fix-conflate February 20, 2025 16:30
Base automatically changed from sedwards/fix-conflate to main February 21, 2025 21:53
@steve-the-edwards steve-the-edwards changed the title 1247: WIP: Partial Tree Rerendering 1247: Partial Tree Rerendering Mar 4, 2025
@steve-the-edwards steve-the-edwards marked this pull request as ready for review March 4, 2025 19:43
@@ -211,12 +216,14 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
*/
private fun updateCachedWorkflowInstance(
workflow: StatefulWorkflow<PropsT, StateT, OutputT, RenderingT>
) {
): Boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Add a note to the kdoc explaining this new return value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer returning as we just call this relying on its idempotency.

!lastRendering.isInitialized ||
subtreeStateDidChange
) {
if (!didUpdateCachedInstance) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be simpler to just call updateCachedWorkflowInstance unconditionally here? It's idempotent, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, we check equality there, so we could call it again

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just call it again now.

@@ -289,4 +322,17 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
SideEffectNode(key, job)
}
}

@JvmInline
internal value class Box<T>(private val _value: Any? = Uninitialized) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This seems like a super generic and potentially useful helper, I'd pull it out.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted into workflow-core as NullableInitBox with its own tests.


@Suppress("UNCHECKED_CAST")
fun getOrThrow(): T {
check(isInitialized)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Give this a descriptive error message.

setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES)
setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES),
setOf(RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING),
setOf(CONFLATE_STALE_RENDERINGS, RENDER_ONLY_WHEN_STATE_CHANGES, PARTIAL_TREE_RENDERING),
).asSequence()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in scope but… why isn't this just sequenceOf(…)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure tbh. the way it was written originally is likely just a mistake?

// If we are using the optimization, always return to the parent, so we carry a path that
// notes that the subtree did change all the way to the root.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this comment, but I'm missing the middle reason why the optimization requires propagating change status all the way up but we don't need that without the optimization.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need that without the optimization because there is nothing to output from the root of the runtime.

The output has propagated as far as it needs to causing all corresponding state changes.

However, the root and the path down to the changed nodes must always re-render now, so this is the implementation detail of how we get subtreeStateDidChange = true on that entire path to the root.

@@ -260,6 +285,7 @@ internal class WorkflowNode<PropsT, StateT, OutputT, RenderingT>(
* Applies [action] to this workflow's [state] and then passes the resulting [ActionApplied]
* via [emitAppliedActionToParent] to the parent, with additional information as to whether or
* not this action has changed the current node's state.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snip

* Check [isInitialized] to see if the value has been assigned.
*/
@JvmInline
public value class NullableInitBox<T>(private val _value: Any? = Uninitialized) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't take in at first that this is a value class. Cool!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was a @zach-klippenstein special when we were noodling on this.

steve-the-edwards and others added 4 commits March 6, 2025 10:18
If PARTIAL_TREE_RENDERING is in the RuntimeConfig then,
Track whether or not the state (or state of a descendant) changed in the WorkflowNode. Pass lastRendering if its not.

Also adds tests for this behavior and expands the test runtime matrix.
Also adds shards for the new runtime for instrumentation tests.
@steve-the-edwards steve-the-edwards merged commit 073d468 into main Mar 6, 2025
39 checks passed
@steve-the-edwards steve-the-edwards deleted the sedwards/partial-tree branch March 6, 2025 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Partial Tree Re-rendering
3 participants