Skip to content

Commit

Permalink
Optimize Modifier serialization in guest code (#2253)
Browse files Browse the repository at this point in the history
* Optimize Modifier serialization in guest code

I learned that encoding a type as a JsonElement and then
encoding that as JSON is significantly less efficient than
going directly from the type to JSON. This is due to some
inefficient code in the internals of kotlinx.serialization.

Skipping this intermediate step is useful on its own, but it
also turns out to be significantly more efficient in practice.

* Fix sample code

* Delete unused generated code

* Drop an obsolete test

* Return a pair from ProtocolWidgetSystemFactory

* Generate the tag and serializer as a single symbol

* Don't encode ',null' unnecessarily
  • Loading branch information
squarejesse authored Aug 26, 2024
1 parent ebf4516 commit 1010184
Show file tree
Hide file tree
Showing 12 changed files with 161 additions and 138 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
New:
- Source-based schema parser is now the default. The `useFir` Gradle property has been removed.
- Introduce a `LoadingStrategy` interface to manage `LazyList` preloading.
- Optimize encoding modifiers in Kotlin/JS.

Changed:
- In Treehouse, events from the UI are now serialized on a background thread. This means that there is both a delay and a thread change between when a UI binding sends an event and when that object is converted to JSON. All arguments to events must not be mutable and support property reads on any thread. Best practice is for all event arguments to be completely immutable.
Expand Down
5 changes: 3 additions & 2 deletions redwood-protocol-guest/api/redwood-protocol-guest.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public final class app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter :
public synthetic fun <init> (Lkotlinx/serialization/json/Json;Ljava/lang/String;Lapp/cash/redwood/protocol/guest/ProtocolWidgetSystemFactory;Lapp/cash/redwood/protocol/guest/ProtocolMismatchHandler;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun appendAdd-ARs5Qwk (IIILapp/cash/redwood/protocol/guest/ProtocolWidget;)V
public fun appendCreate-kyz2zXs (II)V
public fun appendModifierChange-z3jyS0k (ILjava/util/List;)V
public fun appendModifierChange-z3jyS0k (ILapp/cash/redwood/Modifier;)V
public fun appendMove-HpxY78w (IIIII)V
public fun appendPropertyChange-DxQz5cw (IILkotlinx/serialization/KSerializer;Ljava/lang/Object;)V
public fun appendPropertyChange-M7EZMwg (III)V
Expand All @@ -25,7 +25,7 @@ public final class app/cash/redwood/protocol/guest/DefaultGuestProtocolAdapter :
public abstract interface class app/cash/redwood/protocol/guest/GuestProtocolAdapter : app/cash/redwood/protocol/EventSink {
public abstract fun appendAdd-ARs5Qwk (IIILapp/cash/redwood/protocol/guest/ProtocolWidget;)V
public abstract fun appendCreate-kyz2zXs (II)V
public abstract fun appendModifierChange-z3jyS0k (ILjava/util/List;)V
public abstract fun appendModifierChange-z3jyS0k (ILapp/cash/redwood/Modifier;)V
public abstract fun appendMove-HpxY78w (IIIII)V
public abstract fun appendPropertyChange-DxQz5cw (IILkotlinx/serialization/KSerializer;Ljava/lang/Object;)V
public abstract fun appendPropertyChange-M7EZMwg (III)V
Expand Down Expand Up @@ -81,6 +81,7 @@ public final class app/cash/redwood/protocol/guest/ProtocolWidgetChildren : app/
public abstract interface class app/cash/redwood/protocol/guest/ProtocolWidgetSystemFactory {
public abstract fun create (Lapp/cash/redwood/protocol/guest/GuestProtocolAdapter;Lapp/cash/redwood/protocol/guest/ProtocolMismatchHandler;)Lapp/cash/redwood/widget/WidgetSystem;
public static synthetic fun create$default (Lapp/cash/redwood/protocol/guest/ProtocolWidgetSystemFactory;Lapp/cash/redwood/protocol/guest/GuestProtocolAdapter;Lapp/cash/redwood/protocol/guest/ProtocolMismatchHandler;ILjava/lang/Object;)Lapp/cash/redwood/widget/WidgetSystem;
public abstract fun modifierTagAndSerializationStrategy (Lapp/cash/redwood/Modifier$Element;)Lkotlin/Pair;
}

public final class app/cash/redwood/protocol/guest/VersionKt {
Expand Down
5 changes: 3 additions & 2 deletions redwood-protocol-guest/api/redwood-protocol-guest.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ abstract interface app.cash.redwood.protocol.guest/GuestProtocolAdapter : app.ca
abstract fun <#A1: kotlin/Any?> appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization/KSerializer<#A1>, #A1) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.KSerializer<0:0>;0:0){0§<kotlin.Any?>}[0]
abstract fun appendAdd(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, app.cash.redwood.protocol.guest/ProtocolWidget) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendAdd|appendAdd(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;app.cash.redwood.protocol.guest.ProtocolWidget){}[0]
abstract fun appendCreate(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendCreate|appendCreate(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0]
abstract fun appendModifierChange(app.cash.redwood.protocol/Id, kotlin.collections/List<app.cash.redwood.protocol/ModifierElement>) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;kotlin.collections.List<app.cash.redwood.protocol.ModifierElement>){}[0]
abstract fun appendModifierChange(app.cash.redwood.protocol/Id, app.cash.redwood/Modifier) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;app.cash.redwood.Modifier){}[0]
abstract fun appendMove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendMove|appendMove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.Int){}[0]
abstract fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/Boolean) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.Boolean){}[0]
abstract fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/UInt) // app.cash.redwood.protocol.guest/GuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.UInt){}[0]
Expand Down Expand Up @@ -53,6 +53,7 @@ abstract interface app.cash.redwood.protocol.guest/ProtocolWidget : app.cash.red
}

abstract interface app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory { // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory|null[0]
abstract fun <#A1: app.cash.redwood/Modifier.Element> modifierTagAndSerializationStrategy(#A1): kotlin/Pair<app.cash.redwood.protocol/ModifierTag, kotlinx.serialization/SerializationStrategy<#A1>?> // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory.modifierTagAndSerializationStrategy|modifierTagAndSerializationStrategy(0:0){0§<app.cash.redwood.Modifier.Element>}[0]
abstract fun create(app.cash.redwood.protocol.guest/GuestProtocolAdapter, app.cash.redwood.protocol.guest/ProtocolMismatchHandler = ...): app.cash.redwood.widget/WidgetSystem<kotlin/Unit> // app.cash.redwood.protocol.guest/ProtocolWidgetSystemFactory.create|create(app.cash.redwood.protocol.guest.GuestProtocolAdapter;app.cash.redwood.protocol.guest.ProtocolMismatchHandler){}[0]
}

Expand All @@ -71,7 +72,7 @@ final class app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter : app.ca
final fun <#A1: kotlin/Any?> appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlinx.serialization/KSerializer<#A1>, #A1) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlinx.serialization.KSerializer<0:0>;0:0){0§<kotlin.Any?>}[0]
final fun appendAdd(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, app.cash.redwood.protocol.guest/ProtocolWidget) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendAdd|appendAdd(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;app.cash.redwood.protocol.guest.ProtocolWidget){}[0]
final fun appendCreate(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendCreate|appendCreate(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0]
final fun appendModifierChange(app.cash.redwood.protocol/Id, kotlin.collections/List<app.cash.redwood.protocol/ModifierElement>) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;kotlin.collections.List<app.cash.redwood.protocol.ModifierElement>){}[0]
final fun appendModifierChange(app.cash.redwood.protocol/Id, app.cash.redwood/Modifier) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendModifierChange|appendModifierChange(app.cash.redwood.protocol.Id;app.cash.redwood.Modifier){}[0]
final fun appendMove(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/ChildrenTag, kotlin/Int, kotlin/Int, kotlin/Int) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendMove|appendMove(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.ChildrenTag;kotlin.Int;kotlin.Int;kotlin.Int){}[0]
final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/Boolean) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.Boolean){}[0]
final fun appendPropertyChange(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/PropertyTag, kotlin/UInt) // app.cash.redwood.protocol.guest/DefaultGuestProtocolAdapter.appendPropertyChange|appendPropertyChange(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.PropertyTag;kotlin.UInt){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package app.cash.redwood.protocol.guest

import app.cash.redwood.Modifier
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.protocol.Change
import app.cash.redwood.protocol.ChangesSink
Expand All @@ -41,7 +42,7 @@ import kotlinx.serialization.json.JsonPrimitive
public class DefaultGuestProtocolAdapter(
public override val json: Json = Json.Default,
hostVersion: RedwoodVersion,
widgetSystemFactory: ProtocolWidgetSystemFactory,
private val widgetSystemFactory: ProtocolWidgetSystemFactory,
private val mismatchHandler: ProtocolMismatchHandler = ProtocolMismatchHandler.Throwing,
) : GuestProtocolAdapter {
private var nextValue = Id.Root.value + 1
Expand Down Expand Up @@ -111,13 +112,22 @@ public class DefaultGuestProtocolAdapter(
changes.add(PropertyChange(id, tag, JsonPrimitive(value)))
}

public override fun appendModifierChange(
id: Id,
elements: List<ModifierElement>,
) {
override fun appendModifierChange(id: Id, value: Modifier) {
val elements = mutableListOf<ModifierElement>()

value.forEach { element ->
elements += modifierElement(element)
}

changes.add(ModifierChange(id, elements))
}

private fun <T : Modifier.Element> modifierElement(element: T): ModifierElement {
val (tag, serializer) = widgetSystemFactory.modifierTagAndSerializationStrategy(element)
if (serializer == null) return ModifierElement(tag)
return ModifierElement(tag, json.encodeToJsonElement(serializer, element))
}

public override fun appendAdd(
id: Id,
tag: ChildrenTag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@
*/
package app.cash.redwood.protocol.guest

import app.cash.redwood.Modifier
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.protocol.ChangesSink
import app.cash.redwood.protocol.ChildrenTag
import app.cash.redwood.protocol.EventSink
import app.cash.redwood.protocol.Id
import app.cash.redwood.protocol.ModifierElement
import app.cash.redwood.protocol.PropertyTag
import app.cash.redwood.protocol.WidgetTag
import app.cash.redwood.widget.Widget
Expand Down Expand Up @@ -106,7 +106,7 @@ public interface GuestProtocolAdapter : EventSink {
@RedwoodCodegenApi
public fun appendModifierChange(
id: Id,
elements: List<ModifierElement>,
value: Modifier,
)

@RedwoodCodegenApi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@
*/
package app.cash.redwood.protocol.guest

import app.cash.redwood.Modifier
import app.cash.redwood.RedwoodCodegenApi
import app.cash.redwood.protocol.ModifierTag
import app.cash.redwood.widget.WidgetSystem
import kotlinx.serialization.SerializationStrategy

public interface ProtocolWidgetSystemFactory {
/** Create a new [WidgetSystem] connected to a host via [guestAdapter]. */
public fun create(
guestAdapter: GuestProtocolAdapter,
mismatchHandler: ProtocolMismatchHandler = ProtocolMismatchHandler.Throwing,
): WidgetSystem<Unit>

/** The serialization strategy is null if the modifier is stateless. */
@RedwoodCodegenApi
public fun <T : Modifier.Element> modifierTagAndSerializationStrategy(
element: T,
): Pair<ModifierTag, SerializationStrategy<T>?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ internal fun ProtocolSchemaSet.generateFileSpecs(type: ProtocolCodegenType): Lis
when (type) {
Guest -> {
add(generateProtocolWidgetSystemFactory(this@generateFileSpecs))
add(generateComposeProtocolModifierSerialization(this@generateFileSpecs))
for (dependency in all) {
add(generateProtocolWidgetFactory(dependency, host = schema))
generateProtocolModifierSerializers(dependency, host = schema)?.let { add(it) }
Expand Down
Loading

0 comments on commit 1010184

Please sign in to comment.