Skip to content
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

Create a widget protocol descriptor type #2343

Merged
merged 2 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions redwood-protocol-host/api/redwood-protocol-host.api
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -69,3 +68,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
}

14 changes: 10 additions & 4 deletions redwood-protocol-host/api/redwood-protocol-host.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 <get-childrenTags>(): kotlin/IntArray? // app.cash.redwood.protocol.host/WidgetHostProtocol.childrenTags.<get-childrenTags>|<get-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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public class HostProtocolAdapter<W : Any>(
private val leakDetector: LeakDetector,
) : ChangesSink {
private val factory = when (factory) {
is GeneratedProtocolFactory -> factory
is GeneratedHostProtocol -> factory
}

private val nodes =
Expand All @@ -83,7 +83,8 @@ public class HostProtocolAdapter<W : Any>(
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}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ import app.cash.redwood.protocol.host.HostProtocolAdapter.ReuseNode
*/
@OptIn(RedwoodCodegenApi::class)
internal fun shapesEqual(
factory: GeneratedProtocolFactory<*>,
factory: GeneratedHostProtocol<*>,
a: ReuseNode<*>,
b: ProtocolNode<*>,
): Boolean {
if (!a.eligibleForReuse) return false // This node is ineligible.
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 ->
Expand All @@ -59,7 +60,7 @@ internal fun shapesEqual(
*/
@OptIn(RedwoodCodegenApi::class)
private fun childrenEqual(
factory: GeneratedProtocolFactory<*>,
factory: GeneratedHostProtocol<*>,
aChildren: List<ReuseNode<*>>,
bChildren: List<ProtocolNode<*>>,
childrenTag: ChildrenTag,
Expand All @@ -81,15 +82,15 @@ 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.
if (node.widgetTag == UnknownWidgetTag) return 0L // No 'Create' for this.

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) {
Expand All @@ -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'.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,20 +35,20 @@ public sealed interface ProtocolFactory<W : Any> {
}

/**
* [ProtocolFactory] but containing codegen APIs.
* [ProtocolFactory] but containing codegen APIs for a schema.
*
* @suppress
*/
@RedwoodCodegenApi
public interface GeneratedProtocolFactory<W : Any> : ProtocolFactory<W> {
public interface GeneratedHostProtocol<W : Any> : ProtocolFactory<W> {
/**
* 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<W>?
public fun widget(tag: WidgetTag): WidgetHostProtocol<W>?

/**
* Create a new modifier from the specified [element].
Expand All @@ -57,12 +57,22 @@ public interface GeneratedProtocolFactory<W : Any> : ProtocolFactory<W> {
* 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<W : Any> {
/** Create an instance of this widget wrapped as a [ProtocolNode] with the given [id]. */
public fun createNode(id: Id): ProtocolNode<W>

/**
* 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?
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class ProtocolFactoryTest {
)

val t = assertFailsWith<IllegalArgumentException> {
factory.createNode(Id(1), WidgetTag(345432))
factory.widget(WidgetTag(345432))
}
assertThat(t).hasMessage("Unknown widget tag 345432")
}
Expand All @@ -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")
}

Expand Down Expand Up @@ -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<IllegalArgumentException> {
button.children(ChildrenTag(345432))
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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() }
Expand All @@ -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() }

Expand All @@ -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)
Expand Down
Loading