diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt index 057c69be7b..7209a3bfce 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExt.kt @@ -33,7 +33,7 @@ fun createActivityLoadEventEmitter( autoTraceEnabled = autoTraceEnabled, clock = clock, ) - return if (versionChecker.isAtLeast(VERSION_CODES.Q)) { + return if (hasPrePostEvents(versionChecker)) { ActivityLoadEventEmitter(lifecycleEventEmitter) } else { LegacyActivityLoadEventEmitter(lifecycleEventEmitter) @@ -45,6 +45,16 @@ fun createActivityLoadEventEmitter( */ fun traceInstanceId(activity: Activity): Int = activity.hashCode() +/** + * Determine if the current instance of the app will fire render events + */ +fun hasRenderEvent(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.Q) + +/** + * Determine if the current instance of the app will pre and post lifecycle events + */ +fun hasPrePostEvents(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.Q) + /** * Implementation that works with Android 10+ APIs */ diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt index e51f24650a..56185709a8 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitter.kt @@ -1,7 +1,6 @@ package io.embrace.android.embracesdk.internal.capture.activity import android.app.Application.ActivityLifecycleCallbacks -import android.os.Build import io.embrace.android.embracesdk.internal.arch.schema.EmbType import io.embrace.android.embracesdk.internal.spans.PersistableEmbraceSpan import io.embrace.android.embracesdk.internal.spans.SpanService @@ -105,7 +104,7 @@ class UiLoadTraceEmitter( instanceId = instanceId, timestampMs = timestampMs, ) - } else if (hasRenderEvent()) { + } else if (hasRenderEvent(versionChecker)) { startChildSpan( instanceId = instanceId, timestampMs = timestampMs, @@ -222,7 +221,7 @@ class UiLoadTraceEmitter( private fun determineEndEvent(manualEnd: Boolean): TraceCompleteTrigger { return if (manualEnd) { TraceCompleteTrigger.MANUAL - } else if (hasRenderEvent()) { + } else if (hasRenderEvent(versionChecker)) { TraceCompleteTrigger.RENDER } else { TraceCompleteTrigger.RESUME @@ -266,8 +265,6 @@ class UiLoadTraceEmitter( private fun traceCompleteTrigger(instanceId: Int): TraceCompleteTrigger? = activeTraces[instanceId]?.traceCompleteTrigger - private fun hasRenderEvent(): Boolean = versionChecker.isAtLeast(Build.VERSION_CODES.Q) - private fun traceName( activityName: String, uiLoadType: UiLoadType, diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt index 388fc69437..e647a5de0d 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupTraceEmitter.kt @@ -103,7 +103,7 @@ internal class AppStartupTraceEmitter( private val startupRecorded = AtomicBoolean(false) private val dataCollectionComplete = AtomicBoolean(false) - private val endWithFrameDraw: Boolean = versionChecker.isAtLeast(VERSION_CODES.Q) + private val endWithFrameDraw: Boolean = startupHasRenderEvent(versionChecker) override fun applicationInitStart(timestampMs: Long?) { applicationInitStartMs = timestampMs ?: nowMs() @@ -218,7 +218,7 @@ internal class AppStartupTraceEmitter( } val traceEndTimeMs: Long? = - if (versionChecker.isAtLeast(VERSION_CODES.Q)) { + if (endWithFrameDraw) { firstFrameRenderedMs } else { startupActivityResumedMs @@ -472,5 +472,7 @@ internal class AppStartupTraceEmitter( } else { null } + + fun startupHasRenderEvent(versionChecker: VersionChecker) = versionChecker.isAtLeast(VERSION_CODES.Q) } } diff --git a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt index 41e541115f..276e72597b 100644 --- a/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt +++ b/embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/DataCaptureServiceModuleImpl.kt @@ -1,21 +1,22 @@ package io.embrace.android.embracesdk.internal.injection -import android.os.Build +import android.os.Build.VERSION_CODES import io.embrace.android.embracesdk.internal.Systrace import io.embrace.android.embracesdk.internal.capture.activity.UiLoadDataListener import io.embrace.android.embracesdk.internal.capture.activity.UiLoadTraceEmitter import io.embrace.android.embracesdk.internal.capture.activity.createActivityLoadEventEmitter +import io.embrace.android.embracesdk.internal.capture.activity.hasRenderEvent import io.embrace.android.embracesdk.internal.capture.crumbs.ActivityBreadcrumbTracker import io.embrace.android.embracesdk.internal.capture.crumbs.PushNotificationCaptureService import io.embrace.android.embracesdk.internal.capture.startup.AppStartupDataCollector import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter +import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter.Companion.startupHasRenderEvent import io.embrace.android.embracesdk.internal.capture.startup.StartupService import io.embrace.android.embracesdk.internal.capture.startup.StartupServiceImpl import io.embrace.android.embracesdk.internal.capture.startup.StartupTracker import io.embrace.android.embracesdk.internal.capture.webview.EmbraceWebViewService import io.embrace.android.embracesdk.internal.capture.webview.WebViewService import io.embrace.android.embracesdk.internal.config.ConfigService -import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.session.lifecycle.ActivityLifecycleListener import io.embrace.android.embracesdk.internal.ui.FirstDrawDetector import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker @@ -70,10 +71,11 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor( StartupTracker( appStartupDataCollector = appStartupDataCollector, activityLoadEventEmitter = activityLoadEventEmitter, - drawEventEmitter = createFirstDrawDetector( - versionChecker = versionChecker, - logger = initModule.logger, - ) + drawEventEmitter = if (startupHasRenderEvent(versionChecker)) { + FirstDrawDetector(logger = initModule.logger) + } else { + null + } ) } @@ -93,7 +95,11 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor( if (uiLoadEventListener != null) { createActivityLoadEventEmitter( uiLoadEventListener = uiLoadEventListener, - firstDrawDetector = createFirstDrawDetector(versionChecker, initModule.logger), + firstDrawDetector = if (versionChecker.isAtLeast(VERSION_CODES.Q) && hasRenderEvent(versionChecker)) { + FirstDrawDetector(initModule.logger) + } else { + null + }, autoTraceEnabled = configService.autoDataCaptureBehavior.isUiLoadTracingTraceAll(), clock = openTelemetryModule.openTelemetryClock, versionChecker = versionChecker @@ -102,11 +108,4 @@ internal class DataCaptureServiceModuleImpl @JvmOverloads constructor( null } } - - private fun createFirstDrawDetector(versionChecker: VersionChecker, logger: EmbLogger) = - if (versionChecker.isAtLeast(Build.VERSION_CODES.Q)) { - FirstDrawDetector(logger) - } else { - null - } } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt index 0e932395a2..d1a2c1bd5f 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadExtTest.kt @@ -173,7 +173,7 @@ internal class UiLoadExtTest { } start() resume() - if (BuildVersionChecker.isAtLeast(Build.VERSION_CODES.Q)) { + if (hasRenderEvent(BuildVersionChecker)) { drawEventEmitter.draw(activityController.get()) { clock.tick(RENDER_DURATION) } diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt index f9b9078395..0e569595f3 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/activity/UiLoadTraceEmitterTest.kt @@ -1,6 +1,6 @@ package io.embrace.android.embracesdk.internal.capture.activity -import android.os.Build +import android.os.Build.VERSION_CODES import androidx.test.ext.junit.runners.AndroidJUnit4 import io.embrace.android.embracesdk.assertions.assertEmbraceSpanData import io.embrace.android.embracesdk.fakes.FakeClock @@ -30,10 +30,14 @@ internal class UiLoadTraceEmitterTest { private lateinit var spanSink: SpanSink private lateinit var spanService: SpanService private lateinit var traceEmitter: UiLoadTraceEmitter + private var hasRenderEvent: Boolean = false + private var hasPreAndPostEvents: Boolean = false @Before fun setUp() { clock = FakeClock() + hasRenderEvent = hasRenderEvent(BuildVersionChecker) + hasPreAndPostEvents = hasPrePostEvents(BuildVersionChecker) val initModule = FakeInitModule(clock = clock) spanSink = initModule.openTelemetryModule.spanSink spanService = initModule.openTelemetryModule.spanService @@ -45,174 +49,144 @@ internal class UiLoadTraceEmitterTest { ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold ui load trace from another activity in U`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold ui load trace from the same activity in U`() { verifyOpen( lastActivityName = ACTIVITY_NAME, previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold ui load trace from an interrupted opening of another activity in U`() { verifyOpen( previousState = PreviousState.FROM_INTERRUPTED_LOAD, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold ui load trace from an interrupted opening of the same activity in U`() { verifyOpen( lastActivityName = ACTIVITY_NAME, previousState = PreviousState.FROM_INTERRUPTED_LOAD, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold open trace from background in U`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify hot ui load trace in from background in U`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, uiLoadType = UiLoadType.HOT, - firePreAndPost = true, - hasRenderEvent = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify cold ui load trace to be ended manually in U`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.COLD, - firePreAndPost = true, - hasRenderEvent = true, manualEnd = true, ) } - @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) + @Config(sdk = [VERSION_CODES.UPSIDE_DOWN_CAKE]) @Test fun `verify hot ui load trace to be ended manually in U`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.HOT, - firePreAndPost = true, - hasRenderEvent = true, manualEnd = true, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify cold ui load trace in from another activity L`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.COLD, - firePreAndPost = false, - hasRenderEvent = false, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify cold ui load trace from an interrupted opening of another activity in L`() { verifyOpen( previousState = PreviousState.FROM_INTERRUPTED_LOAD, uiLoadType = UiLoadType.COLD, - firePreAndPost = false, - hasRenderEvent = false, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify cold ui load trace from an interrupted opening of the same activity in L`() { verifyOpen( lastActivityName = ACTIVITY_NAME, previousState = PreviousState.FROM_INTERRUPTED_LOAD, uiLoadType = UiLoadType.COLD, - firePreAndPost = false, - hasRenderEvent = false, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify cold ui load trace from background in L`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, uiLoadType = UiLoadType.COLD, - firePreAndPost = false, - hasRenderEvent = false, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify hot ui load trace in L from background`() { verifyOpen( previousState = PreviousState.FROM_BACKGROUND, uiLoadType = UiLoadType.HOT, - firePreAndPost = false, - hasRenderEvent = false, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify cold ui load trace to be ended manually in L`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.COLD, - firePreAndPost = false, - hasRenderEvent = false, manualEnd = true, ) } - @Config(sdk = [Build.VERSION_CODES.LOLLIPOP]) + @Config(sdk = [VERSION_CODES.LOLLIPOP]) @Test fun `verify hot ui load trace to be ended manually in L`() { verifyOpen( previousState = PreviousState.FROM_ACTIVITY, uiLoadType = UiLoadType.HOT, - firePreAndPost = false, - hasRenderEvent = false, manualEnd = true, ) } @@ -224,8 +198,6 @@ internal class UiLoadTraceEmitterTest { lastInstanceId: Int = LAST_ACTIVITY_INSTANCE_ID, previousState: PreviousState, uiLoadType: UiLoadType, - firePreAndPost: Boolean, - hasRenderEvent: Boolean, manualEnd: Boolean = false, ) { openActivity( @@ -235,8 +207,6 @@ internal class UiLoadTraceEmitterTest { lastInstanceId = lastInstanceId, previousState = previousState, uiLoadType = uiLoadType, - firePreAndPost = firePreAndPost, - hasRenderEvent = hasRenderEvent, manualEnd = manualEnd, ).let { timestamps -> val spanMap = spanSink.completedSpans().associateBy { it.name } @@ -336,8 +306,6 @@ internal class UiLoadTraceEmitterTest { lastInstanceId: Int, previousState: PreviousState, uiLoadType: UiLoadType, - firePreAndPost: Boolean, - hasRenderEvent: Boolean, manualEnd: Boolean, ): Triple> { val events = mutableMapOf() @@ -357,7 +325,6 @@ internal class UiLoadTraceEmitterTest { activityCreate( activityName = lastActivityName, instanceId = lastInstanceId, - firePreAndPost = firePreAndPost, ) } @@ -365,12 +332,10 @@ internal class UiLoadTraceEmitterTest { activityCreate( activityName = lastActivityName, instanceId = lastInstanceId, - firePreAndPost = firePreAndPost, ) activityStart( activityName = lastActivityName, instanceId = lastInstanceId, - firePreAndPost = firePreAndPost, ) } } @@ -383,7 +348,6 @@ internal class UiLoadTraceEmitterTest { activityCreate( activityName = activityName, instanceId = instanceId, - firePreAndPost = firePreAndPost, manualEnd = manualEnd, ) } else { @@ -397,7 +361,6 @@ internal class UiLoadTraceEmitterTest { val startEvents = activityStart( activityName = activityName, instanceId = instanceId, - firePreAndPost = firePreAndPost, manualEnd = manualEnd, ).apply { events[LifecycleStage.START] = this @@ -408,12 +371,12 @@ internal class UiLoadTraceEmitterTest { traceEmitter.addAttribute(instanceId, "custom-attribute", "custom-value") val traceStartMs = createEvents?.run { - if (firePreAndPost) { + if (hasPreAndPostEvents) { pre } else { eventStart } - } ?: if (firePreAndPost) { + } ?: if (hasPreAndPostEvents) { startEvents.pre } else { startEvents.eventStart @@ -440,7 +403,6 @@ internal class UiLoadTraceEmitterTest { val resumeEvents = activityResume( instanceId = instanceId, fireEndEvent = hasRenderEvent, - firePreAndPost = firePreAndPost, ).apply { events[LifecycleStage.RESUME] = this } @@ -473,7 +435,6 @@ internal class UiLoadTraceEmitterTest { private fun activityCreate( activityName: String, instanceId: Int, - firePreAndPost: Boolean = true, manualEnd: Boolean = false, ): LifecycleEvents { return runLifecycleEvent( @@ -481,7 +442,6 @@ internal class UiLoadTraceEmitterTest { startCallback = traceEmitter::create, endCallback = traceEmitter::createEnd, activityName = activityName, - firePreAndPost = firePreAndPost, manualEnd = manualEnd, ) } @@ -489,7 +449,6 @@ internal class UiLoadTraceEmitterTest { private fun activityStart( activityName: String, instanceId: Int, - firePreAndPost: Boolean = true, manualEnd: Boolean = false, ): LifecycleEvents { return runLifecycleEvent( @@ -497,7 +456,6 @@ internal class UiLoadTraceEmitterTest { startCallback = traceEmitter::start, endCallback = traceEmitter::startEnd, activityName = activityName, - firePreAndPost = firePreAndPost, manualEnd = manualEnd, ) } @@ -506,7 +464,6 @@ internal class UiLoadTraceEmitterTest { private fun activityResume( instanceId: Int, fireEndEvent: Boolean, - firePreAndPost: Boolean = true, ): LifecycleEvents { return runLifecycleEvent( instanceId = instanceId, @@ -514,7 +471,6 @@ internal class UiLoadTraceEmitterTest { traceEmitter.resume(instanceId, startMs) }, endCallback = if (fireEndEvent) traceEmitter::resumeEnd else fun(_, _) {}, - firePreAndPost = firePreAndPost, ) } @@ -532,18 +488,17 @@ internal class UiLoadTraceEmitterTest { startCallback: (instanceId: Int, activityName: String, startMs: Long, manualEnd: Boolean) -> Unit, endCallback: (instanceId: Int, startMs: Long) -> Unit, activityName: String = "", - firePreAndPost: Boolean = true, manualEnd: Boolean = false, ): LifecycleEvents { val events = LifecycleEvents() - if (firePreAndPost) { + if (hasPreAndPostEvents) { events.pre = clock.now() clock.tick() } events.eventStart = clock.now() startCallback(instanceId, activityName, events.startMs(), manualEnd) events.eventEnd = clock.tick(100L) - if (firePreAndPost) { + if (hasPreAndPostEvents) { events.post = clock.tick() } endCallback(instanceId, events.endMs()) diff --git a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/StartupTrackerTest.kt b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/StartupTrackerTest.kt index af5d82f574..133c21b6c2 100644 --- a/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/StartupTrackerTest.kt +++ b/embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/capture/startup/StartupTrackerTest.kt @@ -12,6 +12,7 @@ import io.embrace.android.embracesdk.fakes.FakeDrawEventEmitter import io.embrace.android.embracesdk.fakes.FakeEmbLogger import io.embrace.android.embracesdk.fakes.FakeNotStartupActivity import io.embrace.android.embracesdk.fakes.FakeSplashScreenActivity +import io.embrace.android.embracesdk.internal.capture.startup.AppStartupTraceEmitter.Companion.startupHasRenderEvent import io.embrace.android.embracesdk.internal.logging.EmbLogger import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker import org.junit.Assert.assertEquals @@ -214,7 +215,7 @@ internal class StartupTrackerTest { val resumeTime = clock.now() controller.resume() clock.tick() - val renderTime = if (BuildVersionChecker.isAtLeast(Build.VERSION_CODES.Q)) { + val renderTime = if (startupHasRenderEvent(BuildVersionChecker)) { drawEventEmitter.draw(controller.get()) { clock.tick() }