Skip to content

Commit

Permalink
Expose guest Redwood version to host (#1939)
Browse files Browse the repository at this point in the history
In order to ensure the guest is fully setup such that we can query this, we have to defer creating the host-side bridge until we have received some changes.
  • Loading branch information
JakeWharton authored Apr 5, 2024
1 parent 8dd7283 commit 67ba864
Show file tree
Hide file tree
Showing 15 changed files with 75 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ public final class app/cash/redwood/treehouse/StandardAppLifecycle : app/cash/re
public synthetic fun <init> (Lapp/cash/redwood/protocol/guest/ProtocolBridge$Factory;Lkotlinx/serialization/json/Json;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public final fun getFrameClock ()Landroidx/compose/runtime/MonotonicFrameClock;
public fun getGuestProtocolVersion-7jYel6c ()Ljava/lang/String;
public fun sendFrame (J)V
public fun start (Lapp/cash/redwood/treehouse/AppLifecycle$Host;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ public final class app/cash/redwood/treehouse/StandardAppLifecycle : app/cash/re
public synthetic fun <init> (Lapp/cash/redwood/protocol/guest/ProtocolBridge$Factory;Lkotlinx/serialization/json/Json;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun close ()V
public final fun getFrameClock ()Landroidx/compose/runtime/MonotonicFrameClock;
public fun getGuestProtocolVersion-7jYel6c ()Ljava/lang/String;
public fun sendFrame (J)V
public fun start (Lapp/cash/redwood/treehouse/AppLifecycle$Host;)V
}
Expand Down
2 changes: 2 additions & 0 deletions redwood-treehouse-guest/api/redwood-treehouse-guest.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ final class app.cash.redwood.treehouse/StandardAppLifecycle : app.cash.redwood.t
final fun start(app.cash.redwood.treehouse/AppLifecycle.Host) // app.cash.redwood.treehouse/StandardAppLifecycle.start|start(app.cash.redwood.treehouse.AppLifecycle.Host){}[0]
final val frameClock // app.cash.redwood.treehouse/StandardAppLifecycle.frameClock|{}frameClock[0]
final fun <get-frameClock>(): androidx.compose.runtime/MonotonicFrameClock // app.cash.redwood.treehouse/StandardAppLifecycle.frameClock.<get-frameClock>|<get-frameClock>(){}[0]
final val guestProtocolVersion // app.cash.redwood.treehouse/StandardAppLifecycle.guestProtocolVersion|{}guestProtocolVersion[0]
final fun <get-guestProtocolVersion>(): app.cash.redwood.protocol/RedwoodVersion // app.cash.redwood.treehouse/StandardAppLifecycle.guestProtocolVersion.<get-guestProtocolVersion>|<get-guestProtocolVersion>(){}[0]
}
final fun (app.cash.redwood.treehouse/TreehouseUi).app.cash.redwood.treehouse/asZiplineTreehouseUi(app.cash.redwood.treehouse/StandardAppLifecycle): app.cash.redwood.treehouse/ZiplineTreehouseUi // app.cash.redwood.treehouse/asZiplineTreehouseUi|[email protected](app.cash.redwood.treehouse.StandardAppLifecycle){}[0]
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import app.cash.redwood.protocol.RedwoodVersion
import app.cash.redwood.protocol.WidgetTag
import app.cash.redwood.protocol.guest.ProtocolBridge
import app.cash.redwood.protocol.guest.ProtocolMismatchHandler
import app.cash.redwood.protocol.guest.guestRedwoodVersion
import app.cash.redwood.treehouse.AppLifecycle.Host
import app.cash.zipline.ZiplineApiMismatchException
import kotlin.coroutines.CoroutineContext
Expand All @@ -38,6 +39,9 @@ public class StandardAppLifecycle(
private var started = false
private lateinit var host: Host

override val guestProtocolVersion: RedwoodVersion
get() = guestRedwoodVersion

internal val hostProtocolVersion: RedwoodVersion get() {
return try {
host.hostProtocolVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package app.cash.redwood.treehouse

import app.cash.redwood.protocol.RedwoodVersion
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
Expand Down Expand Up @@ -55,6 +56,8 @@ internal abstract class CodeSession<A : AppService>(

abstract val json: Json

abstract val guestProtocolVersion: RedwoodVersion

fun start() {
dispatchers.checkUi()
scope.launch(dispatchers.zipline) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package app.cash.redwood.treehouse
import app.cash.redwood.protocol.Change
import app.cash.redwood.protocol.Event
import app.cash.redwood.protocol.EventSink
import app.cash.redwood.protocol.RedwoodVersion
import app.cash.redwood.protocol.host.ProtocolBridge
import app.cash.redwood.protocol.host.ProtocolFactory
import app.cash.redwood.ui.OnBackPressedCallback
Expand Down Expand Up @@ -313,7 +312,10 @@ private class ViewContentCodeBinding<A : AppService>(
/** Only accessed on [TreehouseDispatchers.ui]. Null before [initView] and after [cancel]. */
private var viewOrNull: TreehouseView<*>? = null

/** Only accessed on [TreehouseDispatchers.ui]. Null before [initView] and after [cancel]. */
/**
* Only accessed on [TreehouseDispatchers.ui].
* Null before [initView]+[receiveChangesOnUiDispatcher] and after [cancel].
*/
private var bridgeOrNull: ProtocolBridge<*>? = null

/** Only accessed on [TreehouseDispatchers.zipline]. */
Expand Down Expand Up @@ -351,18 +353,6 @@ private class ViewContentCodeBinding<A : AppService>(

view.saveCallback = this

@Suppress("UNCHECKED_CAST") // We don't have a type parameter for the widget type.
bridgeOrNull = ProtocolBridge(
// TODO Wire through guest version. Wanted this from AppLifecycle but it's bound too late.
guestVersion = RedwoodVersion.Unknown,
container = view.children as Widget.Children<Any>,
factory = view.widgetSystem.widgetFactory(
json = codeSession.json,
protocolMismatchHandler = eventPublisher.widgetProtocolMismatchHandler,
) as ProtocolFactory<Any>,
eventSink = this,
)

// Apply all the changes received before we had a view to apply them to.
while (true) {
val changes = changesAwaitingInitView.removeFirstOrNull() ?: break
Expand All @@ -388,14 +378,12 @@ private class ViewContentCodeBinding<A : AppService>(
}

private fun receiveChangesOnUiDispatcher(changes: List<Change>) {
val view = viewOrNull
val bridge = bridgeOrNull

if (canceled) {
return
}

if (view == null || bridge == null) {
val view = viewOrNull
if (view == null) {
if (changesAwaitingInitView.isEmpty()) {
// Unblock coroutines suspended on TreehouseAppContent.awaitContent().
val currentState = stateFlow.value
Expand All @@ -414,6 +402,21 @@ private class ViewContentCodeBinding<A : AppService>(
return
}

var bridge = bridgeOrNull
if (bridge == null) {
@Suppress("UNCHECKED_CAST") // We don't have a type parameter for the widget type.
bridge = ProtocolBridge(
guestVersion = codeSession.guestProtocolVersion,
container = view.children as Widget.Children<Any>,
factory = view.widgetSystem.widgetFactory(
json = codeSession.json,
protocolMismatchHandler = eventPublisher.widgetProtocolMismatchHandler,
) as ProtocolFactory<Any>,
eventSink = this,
)
bridgeOrNull = bridge
}

if (changesCount++ == 0) {
view.reset()
codeEventPublisher.onCodeLoaded(view, isInitialLaunch)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
*/
package app.cash.redwood.treehouse

import app.cash.redwood.protocol.RedwoodVersion
import app.cash.zipline.Zipline
import app.cash.zipline.ZiplineApiMismatchException
import app.cash.zipline.ZiplineScope
import app.cash.zipline.withScope
import kotlinx.coroutines.CoroutineScope
Expand All @@ -35,12 +37,22 @@ internal class ZiplineCodeSession<A : AppService>(
appService = appService,
) {
private val ziplineScope = ZiplineScope()
private lateinit var appLifecycle: AppLifecycle

override val json: Json
get() = zipline.json

override val guestProtocolVersion: RedwoodVersion
get() {
return try {
appLifecycle.guestProtocolVersion
} catch (_: ZiplineApiMismatchException) {
RedwoodVersion.Unknown
}
}

override fun ziplineStart() {
val appLifecycle = appService.withScope(ziplineScope).appLifecycle
appLifecycle = appService.withScope(ziplineScope).appLifecycle

val host = RealAppLifecycleHost(
appLifecycle = appLifecycle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package app.cash.redwood.treehouse

import app.cash.redwood.protocol.RedwoodVersion
import app.cash.redwood.treehouse.AppLifecycle.Host
import assertk.all
import assertk.assertThat
Expand Down Expand Up @@ -42,6 +43,7 @@ abstract class AbstractFrameClockTest {

val frameTimes = Channel<Long>(Channel.UNLIMITED)
val appLifecycle = object : AppLifecycle {
override val guestProtocolVersion get() = RedwoodVersion.Unknown
override fun start(host: Host) {
}
override fun sendFrame(timeNanos: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package app.cash.redwood.treehouse

import app.cash.redwood.protocol.RedwoodVersion
import app.cash.redwood.protocol.host.hostRedwoodVersion

internal class FakeAppService private constructor(
private val name: String,
private val eventLog: EventLog,
Expand All @@ -26,6 +29,10 @@ internal class FakeAppService private constructor(
get() = mutableUis.toList()

override val appLifecycle = object : AppLifecycle {
override val guestProtocolVersion: RedwoodVersion
// Use latest host version as the guest version to avoid any compatibility behavior.
get() = hostRedwoodVersion

override fun start(host: AppLifecycle.Host) {
eventLog += "$name.appLifecycle.start()"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package app.cash.redwood.treehouse

import app.cash.redwood.protocol.RedwoodVersion
import app.cash.redwood.protocol.host.hostRedwoodVersion
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.json.Json

Expand All @@ -33,6 +35,10 @@ internal class FakeCodeSession(
override val json: Json
get() = Json

override val guestProtocolVersion: RedwoodVersion
// Use latest host version as the guest version to avoid any compatibility behavior.
get() = hostRedwoodVersion

override fun ziplineStart() {
eventLog += "$name.start()"
}
Expand Down
1 change: 1 addition & 0 deletions redwood-treehouse/api/android/redwood-treehouse.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
public abstract interface class app/cash/redwood/treehouse/AppLifecycle : app/cash/zipline/ZiplineService {
public static final field Companion Lapp/cash/redwood/treehouse/AppLifecycle$Companion;
public abstract fun getGuestProtocolVersion-7jYel6c ()Ljava/lang/String;
public abstract fun sendFrame (J)V
public abstract fun start (Lapp/cash/redwood/treehouse/AppLifecycle$Host;)V
}
Expand Down
1 change: 1 addition & 0 deletions redwood-treehouse/api/jvm/redwood-treehouse.api
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
public abstract interface class app/cash/redwood/treehouse/AppLifecycle : app/cash/zipline/ZiplineService {
public static final field Companion Lapp/cash/redwood/treehouse/AppLifecycle$Companion;
public abstract fun getGuestProtocolVersion-7jYel6c ()Ljava/lang/String;
public abstract fun sendFrame (J)V
public abstract fun start (Lapp/cash/redwood/treehouse/AppLifecycle$Host;)V
}
Expand Down
2 changes: 2 additions & 0 deletions redwood-treehouse/api/redwood-treehouse.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ abstract interface app.cash.redwood.treehouse/AppLifecycle : app.cash.zipline/Zi
abstract fun <get-hostProtocolVersion>(): app.cash.redwood.protocol/RedwoodVersion // app.cash.redwood.treehouse/AppLifecycle.Host.hostProtocolVersion.<get-hostProtocolVersion>|<get-hostProtocolVersion>(){}[0]
final object Companion // app.cash.redwood.treehouse/AppLifecycle.Host.Companion|null[0]
}
abstract val guestProtocolVersion // app.cash.redwood.treehouse/AppLifecycle.guestProtocolVersion|{}guestProtocolVersion[0]
abstract fun <get-guestProtocolVersion>(): app.cash.redwood.protocol/RedwoodVersion // app.cash.redwood.treehouse/AppLifecycle.guestProtocolVersion.<get-guestProtocolVersion>|<get-guestProtocolVersion>(){}[0]
final object Companion // app.cash.redwood.treehouse/AppLifecycle.Companion|null[0]
}
abstract interface app.cash.redwood.treehouse/AppService : app.cash.zipline/ZiplineService { // app.cash.redwood.treehouse/AppService|null[0]
Expand Down
3 changes: 3 additions & 0 deletions redwood-treehouse/api/zipline-api.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ functions = [

# fun start(app.cash.redwood.treehouse.AppLifecycle.Host): kotlin.Unit
"Xd61ecfL",

# val guestProtocolVersion: app.cash.redwood.protocol.RedwoodVersion
"0iFPbldz",
]

[app.cash.redwood.treehouse.AppLifecycle.Host]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ import kotlinx.serialization.Contextual

@ObjCName("AppLifecycle", exact = true)
public interface AppLifecycle : ZiplineService {
/**
* The Redwood version of the guest.
* This may be used to alter the behavior to work around bugs discovered in the future, and to
* ensure the serialized protocol remains compatible with what the guest expects.
*/
public val guestProtocolVersion: RedwoodVersion

public fun start(host: Host)

public fun sendFrame(timeNanos: @Contextual Long)
Expand All @@ -34,7 +41,7 @@ public interface AppLifecycle : ZiplineService {
/**
* The Redwood version of the host.
* This may be used to alter the behavior to work around bugs discovered in the future, and to
* ensure the serialized protocol is remains compatible with what the host expects.
* ensure the serialized protocol remains compatible with what the host expects.
*/
public val hostProtocolVersion: RedwoodVersion

Expand Down

0 comments on commit 67ba864

Please sign in to comment.