diff --git a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/ComposeSceneTest.kt b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/ComposeSceneTest.kt index 4d3a8ee377a5c..74391510ff456 100644 --- a/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/ComposeSceneTest.kt +++ b/compose/ui/ui/src/desktopTest/kotlin/androidx/compose/ui/ComposeSceneTest.kt @@ -39,6 +39,7 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -57,6 +58,8 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.ContentDrawScope import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.toComposeImageBitmap +import androidx.compose.ui.graphics.toPixelMap import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.keyEvent @@ -152,6 +155,22 @@ class ComposeSceneTest { assertFalse(hasRenders()) } + // https://github.com/JetBrains/compose-multiplatform/issues/3137 + @Test + fun `rendering of Text state change`() = renderingTest(width = 400, height = 200) { + var text by mutableStateOf("before") + setContent { + Text(text) + } + awaitNextRender() + val before = surface.makeImageSnapshot().toComposeImageBitmap().toPixelMap().buffer + + text = "after" + awaitNextRender() + val after = surface.makeImageSnapshot().toComposeImageBitmap().toPixelMap().buffer + assertThat(after).isNotEqualTo(before) + } + @Test(timeout = 5000) fun `rendering of Layout state change`() = renderingTest(width = 40, height = 40) { var width by mutableStateOf(10) diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt index 64f87252a230f..4617b7bcc71f8 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/ComposeScene.skiko.kt @@ -150,12 +150,10 @@ class ComposeScene internal constructor( check(!isClosed) { "ComposeScene is closed" } isInvalidationDisabled = true val result = try { - // We must see the actual state before we will do [block] - // TODO(https://github.com/JetBrains/compose-jb/issues/1854) get rid of synchronized. - synchronized(GlobalSnapshotManager.sync) { - Snapshot.sendApplyNotifications() - } - snapshotChanges.perform() + // Try to get see the up-to-date state before running block + // Note that this doesn't guarantee it, if sendApplyNotifications is called concurrently + // in a different thread than this code. + sendAndPerformSnapshotChanges() block() } finally { isInvalidationDisabled = false @@ -413,16 +411,26 @@ class ComposeScene internal constructor( return mainOwner.contentSize } + /** + * Sends any pending apply notifications and performs the changes they cause. + */ + private fun sendAndPerformSnapshotChanges() { + Snapshot.sendApplyNotifications() + snapshotChanges.perform() + } + /** * Render the current content on [canvas]. Passed [nanoTime] will be used to drive all * animations in the content (or any other code, which uses [withFrameNanos] */ fun render(canvas: Canvas, nanoTime: Long): Unit = postponeInvalidation { recomposeDispatcher.flush() - frameClock.sendFrame(nanoTime) + frameClock.sendFrame(nanoTime) // Recomposition + sendAndPerformSnapshotChanges() // Apply changes from recomposition phase to layout phase needLayout = false forEachOwner { it.measureAndLayout() } pointerPositionUpdater.update() + sendAndPerformSnapshotChanges() // Apply changes from layout phase to draw phase needDraw = false forEachOwner { it.draw(canvas) } forEachOwner { it.clearInvalidObservations() }