From 972782ff30cfcf18a8a5fa059faf43b8ea89e2ea Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Mon, 30 Sep 2024 15:23:42 -0400 Subject: [PATCH] Create a widget protocol descriptor type This has knowledge of the child IDs for this widget as well as the ability to create instances of it. In the future this will also hold more information such as property IDs so that we can deserialize them on the Zipline thread. --- .../api/redwood-protocol-host.api | 10 +- .../api/redwood-protocol-host.klib.api | 14 +- .../protocol/host/HostProtocolAdapter.kt | 5 +- .../cash/redwood/protocol/host/NodeReuse.kt | 15 +- .../redwood/protocol/host/ProtocolFactory.kt | 28 ++- .../protocol/host/ProtocolFactoryTest.kt | 17 +- .../tooling/codegen/protocolHostGeneration.kt | 186 +++++++++++------- .../app/cash/redwood/tooling/codegen/types.kt | 3 +- ...ocolNodeFactory.kt => FakeHostProtocol.kt} | 16 +- .../treehouse/FakeTreehouseWidgetSystem.kt | 2 +- 10 files changed, 184 insertions(+), 112 deletions(-) rename redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/{FakeProtocolNodeFactory.kt => FakeHostProtocol.kt} (66%) diff --git a/redwood-protocol-host/api/redwood-protocol-host.api b/redwood-protocol-host/api/redwood-protocol-host.api index 7ed924dade..a9325cd25c 100644 --- a/redwood-protocol-host/api/redwood-protocol-host.api +++ b/redwood-protocol-host/api/redwood-protocol-host.api @@ -1,7 +1,6 @@ -public abstract interface class app/cash/redwood/protocol/host/GeneratedProtocolFactory : app/cash/redwood/protocol/host/ProtocolFactory { +public abstract interface class app/cash/redwood/protocol/host/GeneratedHostProtocol : app/cash/redwood/protocol/host/ProtocolFactory { public abstract fun createModifier (Lapp/cash/redwood/protocol/ModifierElement;)Lapp/cash/redwood/Modifier; - public abstract fun createNode-kyz2zXs (II)Lapp/cash/redwood/protocol/host/ProtocolNode; - public abstract fun widgetChildren-WCEpcRY (I)[I + public abstract fun widget-WCEpcRY (I)Lapp/cash/redwood/protocol/host/WidgetHostProtocol; } public final class app/cash/redwood/protocol/host/HostProtocolAdapter : app/cash/redwood/protocol/ChangesSink { @@ -68,3 +67,8 @@ public final class app/cash/redwood/protocol/host/VersionKt { public static final fun getHostRedwoodVersion ()Ljava/lang/String; } +public abstract interface class app/cash/redwood/protocol/host/WidgetHostProtocol { + public abstract fun createNode-ou3jOuA (I)Lapp/cash/redwood/protocol/host/ProtocolNode; + public abstract fun getChildrenTags ()[I +} + diff --git a/redwood-protocol-host/api/redwood-protocol-host.klib.api b/redwood-protocol-host/api/redwood-protocol-host.klib.api index d0cbc89516..b5a69e1225 100644 --- a/redwood-protocol-host/api/redwood-protocol-host.klib.api +++ b/redwood-protocol-host/api/redwood-protocol-host.klib.api @@ -14,10 +14,16 @@ abstract fun interface app.cash.redwood.protocol.host/UiEventSink { // app.cash. abstract fun sendEvent(app.cash.redwood.protocol.host/UiEvent) // app.cash.redwood.protocol.host/UiEventSink.sendEvent|sendEvent(app.cash.redwood.protocol.host.UiEvent){}[0] } -abstract interface <#A: kotlin/Any> app.cash.redwood.protocol.host/GeneratedProtocolFactory : app.cash.redwood.protocol.host/ProtocolFactory<#A> { // app.cash.redwood.protocol.host/GeneratedProtocolFactory|null[0] - abstract fun createModifier(app.cash.redwood.protocol/ModifierElement): app.cash.redwood/Modifier // app.cash.redwood.protocol.host/GeneratedProtocolFactory.createModifier|createModifier(app.cash.redwood.protocol.ModifierElement){}[0] - abstract fun createNode(app.cash.redwood.protocol/Id, app.cash.redwood.protocol/WidgetTag): app.cash.redwood.protocol.host/ProtocolNode<#A>? // app.cash.redwood.protocol.host/GeneratedProtocolFactory.createNode|createNode(app.cash.redwood.protocol.Id;app.cash.redwood.protocol.WidgetTag){}[0] - abstract fun widgetChildren(app.cash.redwood.protocol/WidgetTag): kotlin/IntArray? // app.cash.redwood.protocol.host/GeneratedProtocolFactory.widgetChildren|widgetChildren(app.cash.redwood.protocol.WidgetTag){}[0] +abstract interface <#A: kotlin/Any> app.cash.redwood.protocol.host/GeneratedHostProtocol : app.cash.redwood.protocol.host/ProtocolFactory<#A> { // app.cash.redwood.protocol.host/GeneratedHostProtocol|null[0] + abstract fun createModifier(app.cash.redwood.protocol/ModifierElement): app.cash.redwood/Modifier // app.cash.redwood.protocol.host/GeneratedHostProtocol.createModifier|createModifier(app.cash.redwood.protocol.ModifierElement){}[0] + abstract fun widget(app.cash.redwood.protocol/WidgetTag): app.cash.redwood.protocol.host/WidgetHostProtocol<#A>? // app.cash.redwood.protocol.host/GeneratedHostProtocol.widget|widget(app.cash.redwood.protocol.WidgetTag){}[0] +} + +abstract interface <#A: kotlin/Any> app.cash.redwood.protocol.host/WidgetHostProtocol { // app.cash.redwood.protocol.host/WidgetHostProtocol|null[0] + abstract val childrenTags // app.cash.redwood.protocol.host/WidgetHostProtocol.childrenTags|{}childrenTags[0] + abstract fun (): kotlin/IntArray? // app.cash.redwood.protocol.host/WidgetHostProtocol.childrenTags.|(){}[0] + + abstract fun createNode(app.cash.redwood.protocol/Id): app.cash.redwood.protocol.host/ProtocolNode<#A> // app.cash.redwood.protocol.host/WidgetHostProtocol.createNode|createNode(app.cash.redwood.protocol.Id){}[0] } abstract interface app.cash.redwood.protocol.host/ProtocolMismatchHandler { // app.cash.redwood.protocol.host/ProtocolMismatchHandler|null[0] diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt index f66d6e15e7..28b927497e 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/HostProtocolAdapter.kt @@ -57,7 +57,7 @@ public class HostProtocolAdapter( private val leakDetector: LeakDetector, ) : ChangesSink { private val factory = when (factory) { - is GeneratedProtocolFactory -> factory + is GeneratedHostProtocol -> factory } private val nodes = @@ -83,7 +83,8 @@ public class HostProtocolAdapter( val id = change.id when (change) { is Create -> { - val node = factory.createNode(id, change.tag) ?: continue + val widgetProtocol = factory.widget(change.tag) ?: continue + val node = widgetProtocol.createNode(id) val old = nodes.put(change.id.value, node) require(old == null) { "Insert attempted to replace existing widget with ID ${change.id.value}" diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt index 4b71cbd614..977e8c1b4a 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/NodeReuse.kt @@ -31,7 +31,7 @@ import app.cash.redwood.protocol.host.HostProtocolAdapter.ReuseNode */ @OptIn(RedwoodCodegenApi::class) internal fun shapesEqual( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, a: ReuseNode<*>, b: ProtocolNode<*>, ): Boolean { @@ -39,7 +39,8 @@ internal fun shapesEqual( if (a.widgetTag == UnknownWidgetTag) return false // No 'Create' for this. if (b.widgetTag != a.widgetTag) return false // Widget types don't match. - val widgetChildren = factory.widgetChildren(a.widgetTag) + val widgetChildren = factory.widget(a.widgetTag) + ?.childrenTags ?: return true // Widget has no children. return widgetChildren.all { childrenTag -> @@ -59,7 +60,7 @@ internal fun shapesEqual( */ @OptIn(RedwoodCodegenApi::class) private fun childrenEqual( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, aChildren: List>, bChildren: List>, childrenTag: ChildrenTag, @@ -81,7 +82,7 @@ private fun childrenEqual( /** Returns a hash of this node, or 0L if this node isn't eligible for reuse. */ @OptIn(RedwoodCodegenApi::class) internal fun shapeHash( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, node: ReuseNode<*>, ): Long { if (!node.eligibleForReuse) return 0L // This node is ineligible. @@ -89,7 +90,7 @@ internal fun shapeHash( var result = node.widgetTag.value.toLong() - factory.widgetChildren(node.widgetTag)?.forEach { childrenTag -> + factory.widget(node.widgetTag)?.childrenTags?.forEach { childrenTag -> result = (result * 37L) + childrenTag var childCount = 0 for (child in node.children) { @@ -106,11 +107,11 @@ internal fun shapeHash( /** Returns the same hash as [shapeHash], but on an already-built [ProtocolNode]. */ @OptIn(RedwoodCodegenApi::class) internal fun shapeHash( - factory: GeneratedProtocolFactory<*>, + factory: GeneratedHostProtocol<*>, node: ProtocolNode<*>, ): Long { var result = node.widgetTag.value.toLong() - factory.widgetChildren(node.widgetTag)?.forEach { childrenTag -> + factory.widget(node.widgetTag)?.childrenTags?.forEach { childrenTag -> result = (result * 37L) + childrenTag val children = node.children(ChildrenTag(childrenTag)) ?: return@forEach // This acts like a 'continue'. diff --git a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt index 284b4edbd7..0906c6f473 100644 --- a/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt +++ b/redwood-protocol-host/src/commonMain/kotlin/app/cash/redwood/protocol/host/ProtocolFactory.kt @@ -35,20 +35,20 @@ public sealed interface ProtocolFactory { } /** - * [ProtocolFactory] but containing codegen APIs. + * [ProtocolFactory] but containing codegen APIs for a schema. * * @suppress */ @RedwoodCodegenApi -public interface GeneratedProtocolFactory : ProtocolFactory { +public interface GeneratedHostProtocol : ProtocolFactory { /** - * Create a new protocol node with [id] of the specified [tag]. + * Look up host protocol information for a widget with the given [tag]. * * Invalid [tag] values can either produce an exception or result in `null` being returned. * If `null` is returned, the caller should make every effort to ignore this node and * continue executing. */ - public fun createNode(id: Id, tag: WidgetTag): ProtocolNode? + public fun widget(tag: WidgetTag): WidgetHostProtocol? /** * Create a new modifier from the specified [element]. @@ -57,12 +57,22 @@ public interface GeneratedProtocolFactory : ProtocolFactory { * or result in the unit [`Modifier`][Modifier.Companion] being returned. */ public fun createModifier(element: ModifierElement): Modifier +} + +/** + * Protocol APIs for a widget definition. + * + * @suppress + */ +@RedwoodCodegenApi +public interface WidgetHostProtocol { + /** Create an instance of this widget wrapped as a [ProtocolNode] with the given [id]. */ + public fun createNode(id: Id): ProtocolNode /** - * Look up known children tags for the given widget [tag]. These are stored as a bare [IntArray] - * for efficiency, but are otherwise an array of [ChildrenTag] instances. - * - * @return `null` when widget has no children + * Look up known children tags for this widget. These are stored as a bare [IntArray] + * for efficiency, but are otherwise an array of [ChildrenTag] instances. A value of + * `null` indicates no children. */ - public fun widgetChildren(tag: WidgetTag): IntArray? + public val childrenTags: IntArray? } diff --git a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt index f49c965f18..e3e23b8f11 100644 --- a/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt +++ b/redwood-protocol-host/src/commonTest/kotlin/app/cash/redwood/protocol/host/ProtocolFactoryTest.kt @@ -60,7 +60,7 @@ class ProtocolFactoryTest { ) val t = assertFailsWith { - factory.createNode(Id(1), WidgetTag(345432)) + factory.widget(WidgetTag(345432)) } assertThat(t).hasMessage("Unknown widget tag 345432") } @@ -76,8 +76,7 @@ class ProtocolFactoryTest { mismatchHandler = handler, ) - assertThat(factory.createNode(Id(1), WidgetTag(345432))).isNull() - + assertThat(factory.widget(WidgetTag(345432))).isNull() assertThat(handler.events.single()).isEqualTo("Unknown widget 345432") } @@ -216,7 +215,7 @@ class ProtocolFactoryTest { RedwoodLazyLayout = RedwoodLazyLayoutTestingWidgetFactory(), ), ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) val t = assertFailsWith { button.children(ChildrenTag(345432)) @@ -235,7 +234,7 @@ class ProtocolFactoryTest { mismatchHandler = handler, ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) assertThat(button.children(ChildrenTag(345432))).isNull() assertThat(handler.events.single()).isEqualTo("Unknown children 345432 for 4") @@ -255,7 +254,7 @@ class ProtocolFactoryTest { ), json = json, ) - val textInput = factory.createNode(Id(1), WidgetTag(5))!! + val textInput = factory.widget(WidgetTag(5))!!.createNode(Id(1)) val throwingEventSink = UiEventSink { error(it) } textInput.apply(PropertyChange(Id(1), PropertyTag(2), JsonPrimitive("PT10S")), throwingEventSink) @@ -271,7 +270,7 @@ class ProtocolFactoryTest { RedwoodLazyLayout = RedwoodLazyLayoutTestingWidgetFactory(), ), ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) val change = PropertyChange(Id(1), PropertyTag(345432)) val eventSink = UiEventSink { throw UnsupportedOperationException() } @@ -291,7 +290,7 @@ class ProtocolFactoryTest { ), mismatchHandler = handler, ) - val button = factory.createNode(Id(1), WidgetTag(4))!! + val button = factory.widget(WidgetTag(4))!!.createNode(Id(1)) button.apply(PropertyChange(Id(1), PropertyTag(345432))) { throw UnsupportedOperationException() } @@ -312,7 +311,7 @@ class ProtocolFactoryTest { ), json = json, ) - val textInput = factory.createNode(Id(1), WidgetTag(5))!! + val textInput = factory.widget(WidgetTag(5))!!.createNode(Id(1)) val eventSink = RecordingUiEventSink() textInput.apply(PropertyChange(Id(1), PropertyTag(4), JsonPrimitive(true)), eventSink) diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt index ddf53707e9..f273bef382 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/protocolHostGeneration.kt @@ -52,27 +52,19 @@ public class ExampleProtocolFactory( override val widgetSystem: ExampleWidgetSystem, private val json: Json = Json.Default, private val mismatchHandler: ProtocolMismatchHandler = ProtocolMismatchHandler.Throwing, -) : GeneratedProtocolFactory { - private val childrenTags: IntObjectMap = MutableIntObjectMap(4).apply { - put(1, intArrayOf(1)) - put(3, intArrayOf(1)) - put(1_000_001, intArrayOf(1, 2)) - put(1_000_002, intArrayOf(1)) +) : GeneratedHostProtocol { + private val widgets: IntObjectMap = + MutableIntObjectMap(4).apply { + put(1, ButtonHostProtocol(widgetSystem, json, mismatchHandler)) + put(3, TextHostProtocol(widgetSystem, json, mismatchHandler)) + put(1_000_001, RowHostProtocol(widgetSystem, json, mismatchHandler)) + put(1_000_002, ColumnHostProtocol(widgetSystem, json, mismatchHandler)) } - override fun widgetChildren(tag: WidgetTag): IntArray? { - return childrenTags[tag.value] - } - - override fun createNode(id: Id, tag: WidgetTag): ProtocolNode? = when (tag.value) { - 1 -> TextProtocolNode(id, delegate.Sunspot.Text(), json, mismatchHandler) - 2 -> ButtonProtocolNode(id, delegate.Sunspot.Button(), json, mismatchHandler) - 1_000_001 -> RedwoodLayoutRowProtocolNode(id, delegate.RedwoodLayout.Row(), json, mismatchHandler) - 1_000_002 -> RedwoodLayoutColumnProtocolNode(id, delegate.RedwoodLayout.Column(), json, mismatchHandler) - else -> { - mismatchHandler.onUnknownWidget(tag) - null - } + override fun widget(tag: WidgetTag): WidgetHostProtocol? { + widgets[tag.value]?.let { return it } + mismatchHandler.onUnknownWidget(tag) + return null } override fun createModifier(element: ModifierElement): Modifier { @@ -98,7 +90,7 @@ internal fun generateProtocolFactory( addType( TypeSpec.classBuilder(type) .addTypeVariable(typeVariableW) - .addSuperinterface(ProtocolHost.GeneratedProtocolFactory.parameterizedBy(typeVariableW)) + .addSuperinterface(ProtocolHost.GeneratedProtocolHost.parameterizedBy(typeVariableW)) .optIn(Stdlib.ExperimentalObjCName, Redwood.RedwoodCodegenApi) .addAnnotation( AnnotationSpec.builder(Stdlib.ObjCName) @@ -138,32 +130,31 @@ internal fun generateProtocolFactory( ) .addProperty( PropertySpec.builder( - "childrenTags", - AndroidxCollection.IntObjectMap.parameterizedBy(INT_ARRAY), + "widgets", + AndroidxCollection.IntObjectMap + .parameterizedBy( + ProtocolHost.WidgetHostProtocol + .parameterizedBy(typeVariableW), + ), PRIVATE, ) .initializer( buildCodeBlock { - val allParentWidgets = schemaSet.all - .flatMap { it.widgets } - .filter { it.traits.any { it is ProtocolChildren } } - .sortedBy { it.tag } + val allWidgets = schemaSet.all + .flatMap { schema -> schema.widgets.map { it to schema } } + .sortedBy { it.first.tag } beginControlFlow( "%T<%T>(%L).apply", AndroidxCollection.MutableIntObjectMap, - INT_ARRAY, - allParentWidgets.size, + ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW), + allWidgets.size, ) - for (parentWidget in allParentWidgets) { + for ((widget, widgetSchema) in allWidgets) { addStatement( - "put(%L, %M(%L))", - parentWidget.tag, - MemberName("kotlin", "intArrayOf"), - parentWidget.traits - .filterIsInstance() - .sortedBy { it.tag } - .joinToCode { CodeBlock.of("%L", it.tag) }, + "put(%L, %T(widgetSystem, json, mismatchHandler))", + widget.tag, + schema.widgetHostProtocolType(widget, widgetSchema), ) } endControlFlow() @@ -172,41 +163,16 @@ internal fun generateProtocolFactory( .build(), ) .addFunction( - FunSpec.builder("widgetChildren") + FunSpec.builder("widget") .addModifiers(OVERRIDE) .addParameter("tag", WidgetTag) - .returns(INT_ARRAY.copy(nullable = true)) - .addStatement("return childrenTags[tag.value]") - .build(), - ) - .addFunction( - FunSpec.builder("createNode") - .addModifiers(OVERRIDE) - .addParameter("id", Id) - .addParameter("tag", WidgetTag) .returns( - ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW) + ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW) .copy(nullable = true), ) - .beginControlFlow("return when (tag.value)") - .apply { - for (widgetSchema in schemaSet.all.sortedBy { it.widgets.firstOrNull()?.tag ?: 0 }) { - for (widget in widgetSchema.widgets.sortedBy { it.tag }) { - addStatement( - "%L -> %T(id, widgetSystem.%N.%N(), json, mismatchHandler)", - widget.tag, - schema.protocolNodeType(widget, widgetSchema), - widgetSchema.type.flatName, - widget.type.flatName, - ) - } - } - } - .beginControlFlow("else ->") + .addStatement("widgets[tag.value]?.let { return it }") .addStatement("mismatchHandler.onUnknownWidget(tag)") - .addStatement("null") - .endControlFlow() - .endControlFlow() + .addStatement("return null") .build(), ) .addFunction( @@ -247,7 +213,21 @@ internal fun generateProtocolFactory( } /* -internal class ProtocolButton( +internal class ButtonHostProtocol( + private val widgetSystem: EmojiSearchWidgetSystem, + private val json: Json, + private val mismatchHandler: ProtocolMismatchHandler, +) : WidgetHostProtocol { + override val childrenTags: IntArray? + get() = null + + override fun createNode(id: Id): ProtocolNode { + val widget = widgetSystem.RedwoodLayout.Box() + return BoxProtocolNode(id, widget, json, mismatchHandler) + } +} + +private class ButtonProtocolNode( id: Id, widget: Button, private val json: Json, @@ -299,17 +279,79 @@ internal fun generateProtocolNode( widgetSchema: ProtocolSchema, widget: ProtocolWidget, ): FileSpec { - val type = generatingSchema.protocolNodeType(widget, widgetSchema) val widgetType = widgetSchema.widgetType(widget).parameterizedBy(typeVariableW) + val widgetSystem = generatingSchema.getWidgetSystemType().parameterizedBy(typeVariableW) + val widgetProtocolType = generatingSchema.widgetHostProtocolType(widget, widgetSchema) + val widgetNodeType = widgetProtocolType.peerClass("${widget.type.flatName}ProtocolNode") val (childrens, properties) = widget.traits.partition { it is ProtocolChildren } - return buildFileSpec(type) { + return buildFileSpec(widgetProtocolType) { addAnnotation(suppressDeprecations) addType( - TypeSpec.classBuilder(type) + TypeSpec.classBuilder(widgetProtocolType) .addModifiers(INTERNAL) .addTypeVariable(typeVariableW) + .addSuperinterface(ProtocolHost.WidgetHostProtocol.parameterizedBy(typeVariableW)) + .addAnnotation(Redwood.RedwoodCodegenApi) + .primaryConstructor( + FunSpec.constructorBuilder() + .addParameter("widgetSystem", widgetSystem) + .addParameter("json", KotlinxSerialization.Json) + .addParameter("mismatchHandler", ProtocolHost.ProtocolMismatchHandler) + .build(), + ) + .addProperty( + PropertySpec.builder("widgetSystem", widgetSystem, PRIVATE) + .initializer("widgetSystem") + .build(), + ) + .addProperty( + PropertySpec.builder("json", KotlinxSerialization.Json, PRIVATE) + .initializer("json") + .build(), + ) + .addProperty( + PropertySpec.builder("mismatchHandler", ProtocolHost.ProtocolMismatchHandler, PRIVATE) + .initializer("mismatchHandler") + .build(), + ) + .addProperty( + PropertySpec.builder("childrenTags", INT_ARRAY.copy(nullable = true), OVERRIDE) + .getter( + FunSpec.getterBuilder() + .apply { + val childrens = widget.traits.filterIsInstance() + if (childrens.isEmpty()) { + addStatement("return null") + } else { + addStatement( + "return %M(%L)", + MemberName("kotlin", "intArrayOf"), + childrens.joinToCode { CodeBlock.of("%L", it.tag) }, + ) + } + } + .build(), + ) + .build(), + ) + .addFunction( + FunSpec.builder("createNode") + .addModifiers(OVERRIDE) + .addParameter("id", Protocol.Id) + .returns(ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW)) + .addStatement("val widget = widgetSystem.%L.%L()", widgetSchema.type.flatName, widget.type.flatName) + .addStatement("return %T(id, widget, json, mismatchHandler)", widgetNodeType) + .build(), + ) + .build(), + + ) + addType( + TypeSpec.classBuilder(widgetNodeType) + .addModifiers(PRIVATE) + .addTypeVariable(typeVariableW) .superclass(ProtocolHost.ProtocolNode.parameterizedBy(typeVariableW)) .addAnnotation(Redwood.RedwoodCodegenApi) .primaryConstructor( @@ -535,7 +577,7 @@ internal fun generateProtocolNode( // This explicit string builder usage allows sharing of strings in dex. // See https://jakewharton.com/the-economics-of-generated-code/#string-duplication. .beginControlFlow("return buildString") - .addStatement("append(%S)", type.simpleName) + .addStatement("append(%S)", widgetNodeType.simpleName) .addStatement("""append("(id=")""") .addStatement("append(id.value)") .addStatement("""append(", tag=")""") @@ -719,8 +761,8 @@ internal fun generateProtocolModifierImpls( } } -private fun Schema.protocolNodeType(widget: Widget, widgetSchema: ProtocolSchema): ClassName { - return ClassName(hostProtocolPackage(widgetSchema), "Protocol${widget.type.flatName}") +private fun Schema.widgetHostProtocolType(widget: Widget, widgetSchema: ProtocolSchema): ClassName { + return ClassName(hostProtocolPackage(widgetSchema), "${widget.type.flatName}HostProtocol") } private fun Schema.modifierImplType(modifier: ProtocolModifier, modifierSchema: ProtocolSchema): ClassName { diff --git a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt index b85ff8a231..f80a52342f 100644 --- a/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt +++ b/redwood-tooling-codegen/src/main/kotlin/app/cash/redwood/tooling/codegen/types.kt @@ -54,9 +54,10 @@ internal object ProtocolHost { ClassName("app.cash.redwood.protocol.host", "ProtocolMismatchHandler") val ProtocolNode = ClassName("app.cash.redwood.protocol.host", "ProtocolNode") val ProtocolChildren = ClassName("app.cash.redwood.protocol.host", "ProtocolChildren") - val GeneratedProtocolFactory = ClassName("app.cash.redwood.protocol.host", "GeneratedProtocolFactory") + val GeneratedProtocolHost = ClassName("app.cash.redwood.protocol.host", "GeneratedHostProtocol") val UiEvent = ClassName("app.cash.redwood.protocol.host", "UiEvent") val UiEventSink = ClassName("app.cash.redwood.protocol.host", "UiEventSink") + val WidgetHostProtocol = ClassName("app.cash.redwood.protocol.host", "WidgetHostProtocol") } internal object Redwood { diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt similarity index 66% rename from redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt rename to redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt index 2ff5792bf4..913cacd90d 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeProtocolNodeFactory.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeHostProtocol.kt @@ -20,14 +20,22 @@ import app.cash.redwood.RedwoodCodegenApi import app.cash.redwood.protocol.Id import app.cash.redwood.protocol.ModifierElement import app.cash.redwood.protocol.WidgetTag -import app.cash.redwood.protocol.host.GeneratedProtocolFactory +import app.cash.redwood.protocol.host.GeneratedHostProtocol import app.cash.redwood.protocol.host.ProtocolNode +import app.cash.redwood.protocol.host.WidgetHostProtocol import app.cash.redwood.widget.WidgetSystem @OptIn(RedwoodCodegenApi::class) -internal class FakeProtocolNodeFactory : GeneratedProtocolFactory { +internal class FakeHostProtocol : GeneratedHostProtocol { override val widgetSystem: WidgetSystem = FakeWidgetSystem() - override fun createNode(id: Id, tag: WidgetTag): ProtocolNode = FakeProtocolNode(id, tag) + override fun widget(tag: WidgetTag): WidgetHostProtocol? = FakeWidgetHostProtocol(tag) override fun createModifier(element: ModifierElement): Modifier = Modifier - override fun widgetChildren(tag: WidgetTag): IntArray? = null +} + +@OptIn(RedwoodCodegenApi::class) +private class FakeWidgetHostProtocol( + private val tag: WidgetTag, +) : WidgetHostProtocol { + override fun createNode(id: Id): ProtocolNode = FakeProtocolNode(id, tag) + override val childrenTags: IntArray? get() = null } diff --git a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt index e68b3351bb..4b663d73d7 100644 --- a/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt +++ b/redwood-treehouse-host/src/commonTest/kotlin/app/cash/redwood/treehouse/FakeTreehouseWidgetSystem.kt @@ -22,5 +22,5 @@ internal class FakeTreehouseWidgetSystem : TreehouseView.WidgetSystem