-
Notifications
You must be signed in to change notification settings - Fork 73
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
Optimize Modifier serialization in guest code #2253
Conversation
|
||
public interface ProtocolWidgetSystemFactory { | ||
/** Create a new [WidgetSystem] connected to a host via [guestAdapter]. */ | ||
public fun create( | ||
guestAdapter: GuestProtocolAdapter, | ||
mismatchHandler: ProtocolMismatchHandler = ProtocolMismatchHandler.Throwing, | ||
): WidgetSystem<Unit> | ||
|
||
@RedwoodCodegenApi | ||
public fun modifierTag(element: Modifier.Element): ModifierTag |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tempted to do one function that returns a Pair<ModifierTag, KSerializer<T>?>
instead of two functions. The only hazard of doing one function is I’d like to avoid allocating a Pair
if possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We generate the KSerializer
for every modifier. We can do two things:
- Switch to subtyping
SerializationStrategy
in the generated type rather thanKSerializer
. We generate custom serializers because we only want the serialization code, so we should only be implementing the serialization interface (no clue why I didn't do this initially). - Define a
ProtocolSerializationStrategy
interface which addsval tag: ModifierTag
toSerializationStrategy
and switch our codegen to use that. This eliminates the need to do a double, type-based lookup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like both of these ideas. I’ll add a commit to this PR to do that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Unfortunately I couldn’t do it exactly as suggested 'cause we don’t have a serializer for stateless modifiers. So instead it’s a Pair<ModifierTag, SerializationStrategy>
. Seems to work okay and read okay.
|
||
/** Returns null if the modifier is stateless and should serialize as null. */ | ||
@RedwoodCodegenApi | ||
public fun <T : Modifier.Element> modifierSerializer(element: T): KSerializer<T>? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my first attempt this returned a non-null KSerializer<T>
, but I ran into a this bug in Kotlinx.serialization. I’d like to contribute a fix for that bug and then later I can change this to return non-null always.
import app.cash.redwood.widget.WidgetSystem | ||
import kotlinx.serialization.KSerializer | ||
|
||
public interface ProtocolWidgetSystemFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With these two new APIs I don’t think the Factory
name fit as well as it did previously.
9a44f86
to
3a8e791
Compare
serializer == null -> null | ||
else -> json.encodeToDynamic(serializer, element) | ||
} | ||
elements.push(js("""[tag,value]""")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The protocol when used normally does not encode null
values here. There's no real harm in it, though, except for the 5 extra bytes on the wire.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might need to move our protocol serialization tests upward to ensure these custom forms mirror it in behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooooh good catch, I missed that. I think it’s a nice improvement to test at the JSON-encoded layer for this, though I don’t think Zipline’s asDynamicFunction()
makes this particularly easy. It’s trying to hide the JSON layer and the test wants to exercise it!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed to not encode null, but I haven’t yet fixed the test case to validate the JSON matches as specified. I’ll need to contemplate how to do that in a way that feels right at this layer.
735934d
to
bec3c19
Compare
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.
bec3c19
to
cedf07f
Compare
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.
CHANGELOG.md
's "Unreleased" section has been updated, if applicable.