From 017828db70c904d2c50e5cb12b9ef4ff6b27cef7 Mon Sep 17 00:00:00 2001 From: Nikita Lipsky Date: Wed, 23 Oct 2024 20:03:28 +0300 Subject: [PATCH] Align synchronization declarations between compose modules. --- .../foundation/platform/Synchronization.kt | 28 ++++++++++ .../selection/MultiWidgetSelectionDelegate.kt | 55 ++++++++++--------- .../platform/Synchronization.skiko.kt | 34 ++++++++++++ .../compose/material/InternalMutatorMutex.kt | 4 +- .../androidx/compose/material/AtomicActual.kt | 2 +- .../runtime/runtime/api/desktop/runtime.api | 6 +- compose/runtime/runtime/build.gradle | 8 ++- .../compose/runtime/BroadcastFrameClock.kt | 4 +- .../androidx/compose/runtime/Composition.kt | 4 +- .../androidx/compose/runtime/Effects.kt | 4 +- .../kotlin/androidx/compose/runtime/Latch.kt | 4 +- .../compose/runtime/PausableComposition.kt | 2 + .../compose/runtime/RecomposeScopeImpl.kt | 4 +- .../androidx/compose/runtime/Recomposer.kt | 5 +- .../androidx/compose/runtime/SlotTable.kt | 4 +- .../runtime/internal/SnapshotThreadLocal.kt | 6 +- .../Synchronization.kt} | 11 +++- .../compose/runtime/snapshots/Snapshot.kt | 6 +- .../runtime/snapshots/SnapshotStateList.kt | 6 +- .../runtime/snapshots/SnapshotStateMap.kt | 6 +- .../snapshots/SnapshotStateObserver.kt | 6 +- .../runtime/snapshots/SnapshotStateSet.kt | 6 +- .../Synchronization.darwin.kt} | 2 +- ...entityHashCode.jvm.kt => ActualJvm.jvm.kt} | 20 +++++-- .../compose/runtime/Synchronization.kt | 8 ++- .../platform/Synchronization.desktop.kt} | 11 ++-- .../Synchronization.linux.kt} | 2 +- .../Synchronization.mingwX64.kt} | 6 +- .../{ => platform}/SynchronizationTest.kt | 2 +- .../runtime/PausableCompositionTests.kt | 2 + .../Synchronization.posix.kt} | 6 +- .../runtime/platform/Synchronization.web.kt} | 10 ++-- compose/ui/ui-test/build.gradle | 3 + .../ui/test/UncaughtExceptionHandler.kt | 9 +-- .../ui/test/platform/Synchronization.kt | 28 ++++++++++ .../test/UncaughtExceptionHandler.jsMain.kt | 19 ------- ...ameDeferringContinuationInterceptor.jvm.kt | 4 +- .../ui/test/IdlingResourceRegistry.jvm.kt | 4 +- .../ui/test/TestMonotonicFrameClock.jvm.kt | 6 +- .../ui/test/UncaughtExceptionHandler.jvm.kt | 20 ------- .../UncaughtExceptionHandler.nativeMain.kt | 19 ------- .../ui/test/ComposeRootRegistry.skiko.kt | 4 +- .../compose/ui/test/ComposeUiTest.skiko.kt | 9 ++- .../ui/test/platform/Synchronization.skiko.kt | 31 +++++++++++ .../test/UncaughtExceptionHandler.wasmMain.kt | 19 ------- compose/ui/ui-text/build.gradle | 3 +- .../compose/ui/text/TextLayoutResult.kt | 4 +- .../ui/text/font/FontFamilyResolver.kt | 4 +- .../font/FontListFontFamilyTypefaceAdapter.kt | 4 +- .../ui/text/platform/Synchronization.kt | 14 +++-- .../ui/text/platform/Synchronization.skiko.kt | 35 ++++++++++++ compose/ui/ui/build.gradle | 2 +- .../androidx/compose/ui/autofill/Autofill.kt | 5 +- .../compose/ui/graphics/vector/ImageVector.kt | 7 +-- .../pointer/SuspendingPointerInputFilter.kt | 4 +- .../compose/ui/platform/Synchronization.kt | 15 ++--- .../node/SnapshotInvalidationTracker.skiko.kt | 10 ++-- .../FlushCoroutineDispatcher.skiko.kt | 6 +- .../ui/platform/Synchronization.skiko.kt | 35 ++++++++++++ 59 files changed, 397 insertions(+), 210 deletions(-) create mode 100644 compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/platform/Synchronization.kt create mode 100644 compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/platform/Synchronization.skiko.kt rename compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/{SynchronizedObject.kt => platform/Synchronization.kt} (64%) rename compose/runtime/runtime/src/darwinMain/kotlin/runtime/{SynchronizedObject.darwin.kt => platform/Synchronization.darwin.kt} (94%) rename compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/{IdentityHashCode.jvm.kt => ActualJvm.jvm.kt} (67%) rename compose/runtime/runtime/src/{webMain/kotlin/androidx/compose/runtime/internal/SynchronizedObject.web.kt => desktopMain/kotlin/androidx/compose/runtime/platform/Synchronization.desktop.kt} (71%) rename compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/{SynchronizedObject.linux.kt => platform/Synchronization.linux.kt} (94%) rename compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/{SynchronizedObject.mingwX64.kt => platform/Synchronization.mingwX64.kt} (92%) rename compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/{ => platform}/SynchronizationTest.kt (98%) rename compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/{SynchronizedObject.posix.kt => platform/Synchronization.posix.kt} (97%) rename compose/runtime/runtime/src/{jvmMain/kotlin/androidx/compose/runtime/SynchronizedObject.jvm.kt => webMain/kotlin/androidx/compose/runtime/platform/Synchronization.web.kt} (82%) create mode 100644 compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/platform/Synchronization.kt delete mode 100644 compose/ui/ui-test/src/jsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jsMain.kt delete mode 100644 compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt delete mode 100644 compose/ui/ui-test/src/nativeMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.nativeMain.kt create mode 100644 compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/platform/Synchronization.skiko.kt delete mode 100644 compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.wasmMain.kt create mode 100644 compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/Synchronization.skiko.kt create mode 100644 compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/Synchronization.skiko.kt diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/platform/Synchronization.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/platform/Synchronization.kt new file mode 100644 index 0000000000000..b5d86191e6975 --- /dev/null +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/platform/Synchronization.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.foundation.platform + +internal expect class SynchronizedObject + +/** + * Returns [ref] as a [SynchronizedObject] on platforms where [Any] is a valid [SynchronizedObject], + * or a new [SynchronizedObject] instance if [ref] is null or this is not supported on the current + * platform. + */ +internal expect inline fun makeSynchronizedObject(ref: Any? = null): SynchronizedObject + +internal expect inline fun synchronized(lock: SynchronizedObject, block: () -> R): R diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt index ddbb17b6b96c8..3709350940169 100644 --- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt +++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/MultiWidgetSelectionDelegate.kt @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Android Open Source Project + * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package androidx.compose.foundation.text.selection +import androidx.compose.foundation.platform.makeSynchronizedObject +import androidx.compose.foundation.platform.synchronized import androidx.compose.foundation.text.getLineHeight import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect @@ -25,15 +27,13 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.TextLayoutResult import androidx.compose.ui.text.TextRange import kotlin.math.max -import kotlinx.atomicfu.locks.SynchronizedObject -import kotlinx.atomicfu.locks.synchronized internal class MultiWidgetSelectionDelegate( override val selectableId: Long, private val coordinatesCallback: () -> LayoutCoordinates?, private val layoutResultCallback: () -> TextLayoutResult? ) : Selectable { - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject(this) private var _previousTextLayoutResult: TextLayoutResult? = null @@ -48,30 +48,33 @@ internal class MultiWidgetSelectionDelegate( * instance check is enough to accomplish whether a text layout has changed in a meaningful way. */ private val TextLayoutResult.lastVisibleOffset: Int - get() = synchronized(lock) { - if (_previousTextLayoutResult !== this) { - val lastVisibleLine = - when { - !didOverflowHeight || multiParagraph.didExceedMaxLines -> lineCount - 1 - else -> { // size.height < multiParagraph.height - var finalVisibleLine = - getLineForVerticalPosition(size.height.toFloat()) - .coerceAtMost(lineCount - 1) - // if final visible line's top is equal to or larger than text layout - // result's height, we need to check above lines one by one until we - // find - // a line that fits in boundaries. - while ( - finalVisibleLine >= 0 && getLineTop(finalVisibleLine) >= size.height - ) finalVisibleLine-- - finalVisibleLine.coerceAtLeast(0) + get() = + synchronized(lock) { + if (_previousTextLayoutResult !== this) { + val lastVisibleLine = + when { + !didOverflowHeight || multiParagraph.didExceedMaxLines -> lineCount - 1 + else -> { // size.height < multiParagraph.height + var finalVisibleLine = + getLineForVerticalPosition(size.height.toFloat()) + .coerceAtMost(lineCount - 1) + // if final visible line's top is equal to or larger than text + // layout + // result's height, we need to check above lines one by one until we + // find + // a line that fits in boundaries. + while ( + finalVisibleLine >= 0 && + getLineTop(finalVisibleLine) >= size.height + ) finalVisibleLine-- + finalVisibleLine.coerceAtLeast(0) + } } - } - _previousLastVisibleOffset = getLineEnd(lastVisibleLine, true) - _previousTextLayoutResult = this + _previousLastVisibleOffset = getLineEnd(lastVisibleLine, true) + _previousTextLayoutResult = this + } + _previousLastVisibleOffset } - _previousLastVisibleOffset - } override fun appendSelectableInfoToBuilder(builder: SelectionLayoutBuilder) { val layoutCoordinates = getLayoutCoordinates() ?: return diff --git a/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/platform/Synchronization.skiko.kt b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/platform/Synchronization.skiko.kt new file mode 100644 index 0000000000000..b2cb315a5cae6 --- /dev/null +++ b/compose/foundation/foundation/src/skikoMain/kotlin/androidx/compose/foundation/platform/Synchronization.skiko.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("SynchronizationKt") + +package androidx.compose.foundation.platform + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + +internal actual class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() + +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + +@Suppress("BanInlineOptIn") +@OptIn(ExperimentalContracts::class) +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return kotlinx.atomicfu.locks.synchronized(lock, block) +} \ No newline at end of file diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt index f9939ea3b9d18..0ffc3db67cd5e 100644 --- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt +++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.sync.withLock * This is an internal copy of androidx.compose.foundation.MutatorMutex with an additional tryMutate * method. Do not modify, except for tryMutate. ** */ -internal expect class AtomicReference(value: V) { +internal expect class InternalAtomicReference(value: V) { fun get(): V fun set(value: V) @@ -60,7 +60,7 @@ internal class InternalMutatorMutex { fun cancel() = job.cancel() } - private val currentMutator = AtomicReference(null) + private val currentMutator = InternalAtomicReference(null) private val mutex = Mutex() private fun tryMutateOrCancel(mutator: Mutator) { diff --git a/compose/material/material/src/skikoMain/kotlin/androidx/compose/material/AtomicActual.kt b/compose/material/material/src/skikoMain/kotlin/androidx/compose/material/AtomicActual.kt index bc02bcc2e834a..56975e2e0ebd8 100644 --- a/compose/material/material/src/skikoMain/kotlin/androidx/compose/material/AtomicActual.kt +++ b/compose/material/material/src/skikoMain/kotlin/androidx/compose/material/AtomicActual.kt @@ -18,7 +18,7 @@ package androidx.compose.material import kotlinx.atomicfu.atomic -internal actual class AtomicReference actual constructor(value: V) { +internal actual class InternalAtomicReference actual constructor(value: V) { private val delegate = atomic(value) actual fun get() = delegate.value actual fun set(value: V) { diff --git a/compose/runtime/runtime/api/desktop/runtime.api b/compose/runtime/runtime/api/desktop/runtime.api index 291c059e1639f..a24f0d18e26a1 100644 --- a/compose/runtime/runtime/api/desktop/runtime.api +++ b/compose/runtime/runtime/api/desktop/runtime.api @@ -18,7 +18,7 @@ public final class androidx/compose/runtime/ActualDesktop_desktopKt { public final class androidx/compose/runtime/ActualJvm_jvmKt { public static final fun identityHashCode (Ljava/lang/Object;)I - public static final fun synchronized (Landroidx/compose/runtime/SynchronizedObject;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; + public static final synthetic fun synchronized (Landroidx/compose/runtime/SynchronizedObject;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; } public abstract interface class androidx/compose/runtime/Applier { @@ -904,6 +904,10 @@ public abstract interface annotation class androidx/compose/runtime/internal/Sta public abstract fun parameters ()I } +public final class androidx/compose/runtime/platform/Synchronization_desktopKt { + public static final fun synchronized (Landroidx/compose/runtime/SynchronizedObject;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; +} + public final class androidx/compose/runtime/reflect/ComposableMethod { public static final field $stable I public final fun asMethod ()Ljava/lang/reflect/Method; diff --git a/compose/runtime/runtime/build.gradle b/compose/runtime/runtime/build.gradle index e9bb7c02dbbf1..7dcd6cd86dd83 100644 --- a/compose/runtime/runtime/build.gradle +++ b/compose/runtime/runtime/build.gradle @@ -116,7 +116,6 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { commonMain.dependencies { implementation(libs.kotlinStdlibCommon) implementation(libs.kotlinCoroutinesCore) - implementation(libs.atomicFu) implementation(project(":annotation:annotation")) implementation(project(":collection:collection")) } @@ -149,7 +148,12 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { } nonAndroidMain.dependsOn(commonMain) - nonJvmMain.dependsOn(commonMain) + nonJvmMain { + dependsOn(commonMain) + dependencies { + implementation(libs.atomicFu) + } + } webMain.dependsOn(nonAndroidMain) webMain.dependsOn(nonJvmMain) diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt index 8e42d1adee2c7..61b7dff94d8eb 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/BroadcastFrameClock.kt @@ -17,6 +17,8 @@ package androidx.compose.runtime import androidx.compose.runtime.internal.AtomicInt +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.snapshots.fastForEach import kotlin.coroutines.Continuation import kotlin.coroutines.resumeWithException @@ -42,7 +44,7 @@ class BroadcastFrameClock(private val onNewAwaiters: (() -> Unit)? = null) : Mon } } - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject() private var failureCause: Throwable? = null private var awaiters = mutableListOf>() private var spareList = mutableListOf>() diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt index f674dbe096ff2..ba74b8273d583 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt @@ -25,6 +25,8 @@ import androidx.compose.runtime.collection.fastForEach import androidx.compose.runtime.internal.AtomicReference import androidx.compose.runtime.internal.RememberEventDispatcher import androidx.compose.runtime.internal.trace +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.snapshots.ReaderKind import androidx.compose.runtime.snapshots.StateObjectImpl import androidx.compose.runtime.snapshots.fastAll @@ -446,7 +448,7 @@ internal class CompositionImpl( private val pendingModifications = AtomicReference(null) // Held when making changes to self or composer - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject() /** * A set of remember observers that were potentially abandoned between [composeContent] or diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt index 10ce4193fb097..c55db08fc65ac 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt @@ -17,6 +17,8 @@ package androidx.compose.runtime import androidx.compose.runtime.internal.PlatformOptimizedCancellationException +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import kotlin.concurrent.Volatile import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -426,7 +428,7 @@ internal class RememberedCoroutineScope( private val parentContext: CoroutineContext, private val overlayContext: CoroutineContext, ) : CoroutineScope, RememberObserver { - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject(this) // The goal of this implementation is to make cancellation as cheap as possible if the // coroutineContext property was never accessed, consisting only of taking a monitor lock and diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Latch.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Latch.kt index be2049861e988..df2bdf9972ebb 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Latch.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Latch.kt @@ -16,6 +16,8 @@ package androidx.compose.runtime +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlinx.coroutines.suspendCancellableCoroutine @@ -31,7 +33,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine */ internal class Latch { - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject() private var awaiters = mutableListOf>() private var spareList = mutableListOf>() diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableComposition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableComposition.kt index 0eff8d6deb25b..50989b9c8afb0 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableComposition.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/PausableComposition.kt @@ -22,6 +22,8 @@ import androidx.collection.emptyScatterSet import androidx.collection.mutableIntListOf import androidx.collection.mutableObjectListOf import androidx.compose.runtime.internal.RememberEventDispatcher +import androidx.compose.runtime.platform.SynchronizedObject +import androidx.compose.runtime.platform.synchronized /** * A [PausableComposition] is a sub-composition that can be composed incrementally as it supports diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt index 48657d555c009..e27707206ecdf 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/RecomposeScopeImpl.kt @@ -19,6 +19,8 @@ package androidx.compose.runtime import androidx.collection.MutableObjectIntMap import androidx.collection.MutableScatterMap import androidx.collection.ScatterSet +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.snapshots.fastAny import androidx.compose.runtime.snapshots.fastForEach import androidx.compose.runtime.tooling.CompositionObserverHandle @@ -74,7 +76,7 @@ internal interface RecomposeScopeOwner { fun recordReadOf(value: Any) } -private val callbackLock = SynchronizedObject() +private val callbackLock = makeSynchronizedObject() /** * A RecomposeScope is created for a region of the composition that can be recomposed independently diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt index e3470768b59e2..e696a7a1a7175 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt @@ -28,6 +28,9 @@ import androidx.compose.runtime.internal.AtomicReference import androidx.compose.runtime.internal.SnapshotThreadLocal import androidx.compose.runtime.internal.logError import androidx.compose.runtime.internal.trace +import androidx.compose.runtime.platform.SynchronizedObject +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.snapshots.MutableSnapshot import androidx.compose.runtime.snapshots.ReaderKind import androidx.compose.runtime.snapshots.Snapshot @@ -199,7 +202,7 @@ class Recomposer(effectCoroutineContext: CoroutineContext) : CompositionContext( PendingWork } - private val stateLock = SynchronizedObject() + private val stateLock = makeSynchronizedObject() // Begin properties guarded by stateLock private var runnerJob: Job? = null diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt index 5c622d62e8a2e..ad12fa464e959 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt @@ -19,6 +19,8 @@ package androidx.compose.runtime import androidx.collection.MutableIntObjectMap import androidx.collection.MutableIntSet import androidx.collection.MutableObjectList +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.snapshots.fastAny import androidx.compose.runtime.snapshots.fastFilterIndexed import androidx.compose.runtime.snapshots.fastForEach @@ -112,7 +114,7 @@ internal class SlotTable : CompositionData, Iterable { */ private var readers = 0 - private val lock = SynchronizedObject() + private val lock = makeSynchronizedObject() /** Tracks whether there is an active writer. */ internal var writer = false diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/SnapshotThreadLocal.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/SnapshotThreadLocal.kt index f379b1cdb4f40..e38919124b97a 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/SnapshotThreadLocal.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/internal/SnapshotThreadLocal.kt @@ -16,8 +16,8 @@ package androidx.compose.runtime.internal -import androidx.compose.runtime.SynchronizedObject -import androidx.compose.runtime.synchronized +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized /** * This is similar to a [ThreadLocal] but has lower overhead because it avoids a weak reference. @@ -30,7 +30,7 @@ import androidx.compose.runtime.synchronized */ internal class SnapshotThreadLocal { private val map = AtomicReference(emptyThreadMap) - private val writeMutex = SynchronizedObject() + private val writeMutex = makeSynchronizedObject() private var mainThreadValue: T? = null diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SynchronizedObject.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/platform/Synchronization.kt similarity index 64% rename from compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SynchronizedObject.kt rename to compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/platform/Synchronization.kt index fb289c8d410eb..7b427ee1daf94 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SynchronizedObject.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/platform/Synchronization.kt @@ -14,9 +14,16 @@ * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform -internal expect class SynchronizedObject() +internal expect class SynchronizedObject + +/** + * Returns [ref] as a [SynchronizedObject] on platforms where [Any] is a valid [SynchronizedObject], + * or a new [SynchronizedObject] instance if [ref] is null or this is not supported on the current + * platform. + */ +internal expect inline fun makeSynchronizedObject(ref: Any? = null): SynchronizedObject @PublishedApi internal expect inline fun synchronized(lock: SynchronizedObject, block: () -> R): R diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt index e593c27bb5691..3a97f4518f3e7 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt @@ -22,7 +22,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.ExperimentalComposeRuntimeApi import androidx.compose.runtime.InternalComposeApi -import androidx.compose.runtime.SynchronizedObject import androidx.compose.runtime.checkPrecondition import androidx.compose.runtime.collection.wrapIntoSet import androidx.compose.runtime.internal.AtomicInt @@ -30,13 +29,14 @@ import androidx.compose.runtime.internal.AtomicReference import androidx.compose.runtime.internal.JvmDefaultWithCompatibility import androidx.compose.runtime.internal.SnapshotThreadLocal import androidx.compose.runtime.internal.currentThreadId +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.requirePrecondition import androidx.compose.runtime.snapshots.Snapshot.Companion.takeMutableSnapshot import androidx.compose.runtime.snapshots.Snapshot.Companion.takeSnapshot import androidx.compose.runtime.snapshots.tooling.creatingSnapshot import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnApplied import androidx.compose.runtime.snapshots.tooling.dispatchObserverOnDispose -import androidx.compose.runtime.synchronized import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -1841,7 +1841,7 @@ private val threadSnapshot = SnapshotThreadLocal() * A global synchronization object. This synchronization object should be taken before modifying any * of the fields below. */ -@PublishedApi internal val lock = SynchronizedObject() +@PublishedApi internal val lock = makeSynchronizedObject() @PublishedApi internal inline fun sync(block: () -> T): T = synchronized(lock, block) diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt index 2760b72d939bf..2cbdb3be56860 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateList.kt @@ -17,11 +17,11 @@ package androidx.compose.runtime.snapshots import androidx.compose.runtime.Stable -import androidx.compose.runtime.SynchronizedObject import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentList import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentListOf +import androidx.compose.runtime.platform.makeSynchronizedObject import androidx.compose.runtime.requirePrecondition -import androidx.compose.runtime.synchronized +import androidx.compose.runtime.platform.synchronized import kotlin.jvm.JvmName /** @@ -298,7 +298,7 @@ fun SnapshotStateList(size: Int, init: (index: Int) -> T): SnapshotStateList * In code the requires this lock and calls `writable` (or other operation that acquires the * snapshot global lock), this lock *MUST* be acquired first to avoid deadlocks. */ -private val sync = SynchronizedObject() +private val sync = makeSynchronizedObject() private fun modificationError(): Nothing = error("Cannot modify a state list through an iterator") diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt index ac20fb6c5e1e2..6e6164c440590 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateMap.kt @@ -17,10 +17,10 @@ package androidx.compose.runtime.snapshots import androidx.compose.runtime.Stable -import androidx.compose.runtime.SynchronizedObject import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentMap import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentHashMapOf -import androidx.compose.runtime.synchronized +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import kotlin.jvm.JvmName /** @@ -319,7 +319,7 @@ private class SnapshotMapValueSet(map: SnapshotStateMap) : * In code the requires this lock and calls `writable` (or other operation that acquires the * snapshot global lock), this lock *MUST* be acquired first to avoid deadlocks. */ -private val sync = SynchronizedObject() +private val sync = makeSynchronizedObject() private abstract class StateMapMutableIterator( val map: SnapshotStateMap, diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt index 33f98c1a79ce0..2f771215cfdef 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt @@ -21,7 +21,6 @@ import androidx.collection.MutableScatterMap import androidx.collection.MutableScatterSet import androidx.compose.runtime.DerivedState import androidx.compose.runtime.DerivedStateObserver -import androidx.compose.runtime.SynchronizedObject import androidx.compose.runtime.TestOnly import androidx.compose.runtime.collection.ScopeMap import androidx.compose.runtime.collection.fastForEach @@ -31,9 +30,10 @@ import androidx.compose.runtime.internal.AtomicReference import androidx.compose.runtime.internal.currentThreadId import androidx.compose.runtime.internal.currentThreadName import androidx.compose.runtime.observeDerivedStateRecalculations +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import androidx.compose.runtime.requirePrecondition import androidx.compose.runtime.structuralEqualityPolicy -import androidx.compose.runtime.synchronized /** * Helper class to efficiently observe snapshot state reads. See [observeReads] for more details. @@ -172,7 +172,7 @@ class SnapshotStateObserver(private val onChangedExecutor: (callback: () -> Unit * The list only grows. */ private val observedScopeMaps = mutableVectorOf() - private val observedScopeMapsLock = SynchronizedObject() + private val observedScopeMapsLock = makeSynchronizedObject() /** * Helper for synchronized iteration over [observedScopeMaps]. All observed reads should happen diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateSet.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateSet.kt index 7598626b2495a..703ea30441927 100644 --- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateSet.kt +++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateSet.kt @@ -17,10 +17,10 @@ package androidx.compose.runtime.snapshots import androidx.compose.runtime.Stable -import androidx.compose.runtime.SynchronizedObject import androidx.compose.runtime.external.kotlinx.collections.immutable.PersistentSet import androidx.compose.runtime.external.kotlinx.collections.immutable.persistentSetOf -import androidx.compose.runtime.synchronized +import androidx.compose.runtime.platform.makeSynchronizedObject +import androidx.compose.runtime.platform.synchronized import kotlin.jvm.JvmName /** @@ -222,7 +222,7 @@ class SnapshotStateSet : StateObject, MutableSet, RandomAccess { * In code that requires this lock and calls `writable` (or other operation that acquires the * snapshot global lock), this lock *MUST* be acquired first to avoid deadlocks. */ -private val sync = SynchronizedObject() +private val sync = makeSynchronizedObject() private class StateSetIterator(val set: SnapshotStateSet, val iterator: Iterator) : MutableIterator { diff --git a/compose/runtime/runtime/src/darwinMain/kotlin/runtime/SynchronizedObject.darwin.kt b/compose/runtime/runtime/src/darwinMain/kotlin/runtime/platform/Synchronization.darwin.kt similarity index 94% rename from compose/runtime/runtime/src/darwinMain/kotlin/runtime/SynchronizedObject.darwin.kt rename to compose/runtime/runtime/src/darwinMain/kotlin/runtime/platform/Synchronization.darwin.kt index 64712df8a57b1..68eab974e23eb 100644 --- a/compose/runtime/runtime/src/darwinMain/kotlin/runtime/SynchronizedObject.darwin.kt +++ b/compose/runtime/runtime/src/darwinMain/kotlin/runtime/platform/Synchronization.darwin.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform internal actual val PTHREAD_MUTEX_ERRORCHECK: Int = platform.posix.PTHREAD_MUTEX_ERRORCHECK diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/IdentityHashCode.jvm.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt similarity index 67% rename from compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/IdentityHashCode.jvm.kt rename to compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt index f12fea70a94a1..3748ab41d743d 100644 --- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/IdentityHashCode.jvm.kt +++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/ActualJvm.jvm.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019 The Android Open Source Project + * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,23 @@ * limitations under the License. */ -@file:JvmName("ActualJvm_jvmKt") -@file:JvmMultifileClass - package androidx.compose.runtime +import kotlin.DeprecationLevel.HIDDEN + // TODO https://youtrack.jetbrains.com/issue/CMP-719/Make-expect-fun-identityHashCodeinstance-Any-Int-internal @InternalComposeApi @Deprecated("Made internal. It wasn't supposed to be public") fun identityHashCode(instance: Any?): Int = - androidx.compose.runtime.internal.identityHashCode(instance) \ No newline at end of file + androidx.compose.runtime.internal.identityHashCode(instance) + +internal class SynchronizedObject + +@PublishedApi +@JvmName("synchronized") +@Deprecated( + level = HIDDEN, + message = "not expected to be referenced directly as the old version had to be inlined" +) +internal inline fun oldSynchronized2(lock: SynchronizedObject, block: () -> R): R = + androidx.compose.runtime.platform.synchronized(lock, block) diff --git a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/Synchronization.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/Synchronization.kt index 20873e2f17a7a..830446e5770f4 100644 --- a/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/Synchronization.kt +++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/Synchronization.kt @@ -20,5 +20,9 @@ import kotlin.DeprecationLevel.* @PublishedApi @JvmName("synchronized") -@Deprecated(level = HIDDEN, message = "Use SynchronizedObjectKt.synchronized() instead") -internal fun oldSynchronized(lock: SynchronizedObject, block: () -> R): R = synchronized(lock, block) \ No newline at end of file +@Deprecated( + level = HIDDEN, + message = "not expected to be referenced directly as the old version had to be inlined" +) +internal inline fun oldSynchronized(lock: SynchronizedObject, block: () -> R): R = + androidx.compose.runtime.platform.synchronized(lock, block) \ No newline at end of file diff --git a/compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/internal/SynchronizedObject.web.kt b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/platform/Synchronization.desktop.kt similarity index 71% rename from compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/internal/SynchronizedObject.web.kt rename to compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/platform/Synchronization.desktop.kt index 10a1a5f0e0ea7..af3ada93d6f03 100644 --- a/compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/internal/SynchronizedObject.web.kt +++ b/compose/runtime/runtime/src/desktopMain/kotlin/androidx/compose/runtime/platform/Synchronization.desktop.kt @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package androidx.compose.runtime +package androidx.compose.runtime.platform @Suppress("ACTUAL_WITHOUT_EXPECT") // https://youtrack.jetbrains.com/issue/KT-37316 -internal actual typealias SynchronizedObject = Any +internal actual typealias SynchronizedObject = androidx.compose.runtime.SynchronizedObject + +@Suppress("NOTHING_TO_INLINE") +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() @PublishedApi -internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = block() +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = + kotlin.synchronized(lock, block) diff --git a/compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/SynchronizedObject.linux.kt b/compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/platform/Synchronization.linux.kt similarity index 94% rename from compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/SynchronizedObject.linux.kt rename to compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/platform/Synchronization.linux.kt index 2250bc0d73e8a..3b75e07e248e5 100644 --- a/compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/SynchronizedObject.linux.kt +++ b/compose/runtime/runtime/src/linuxMain/kotlin/androidx/compose/runtime/platform/Synchronization.linux.kt @@ -14,6 +14,6 @@ * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform internal actual val PTHREAD_MUTEX_ERRORCHECK: Int = platform.posix.PTHREAD_MUTEX_ERRORCHECK.toInt() diff --git a/compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/SynchronizedObject.mingwX64.kt b/compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/platform/Synchronization.mingwX64.kt similarity index 92% rename from compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/SynchronizedObject.mingwX64.kt rename to compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/platform/Synchronization.mingwX64.kt index 1ec31765fec03..b5a1bc8fca5db 100644 --- a/compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/SynchronizedObject.mingwX64.kt +++ b/compose/runtime/runtime/src/mingwX64Main/kotlin/androidx/compose/runtime/platform/Synchronization.mingwX64.kt @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform import androidx.compose.runtime.internal.currentThreadId import kotlinx.atomicfu.* +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + @PublishedApi @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { @@ -42,7 +44,7 @@ internal actual inline fun synchronized(lock: SynchronizedObject, block: () * Using a posix mutex is [problematic for mingwX64](https://youtrack.jetbrains.com/issue/KT-70449/Posix-declarations-differ-much-for-mingwX64-and-LinuxDarwin-targets), * so we just use a simple spin lock for mingwX64 (maybe reconsidered in case of problems). */ -internal actual class SynchronizedObject actual constructor() { +internal actual class SynchronizedObject { companion object { private const val NO_OWNER = -1L diff --git a/compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/SynchronizationTest.kt b/compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/platform/SynchronizationTest.kt similarity index 98% rename from compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/SynchronizationTest.kt rename to compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/platform/SynchronizationTest.kt index 1e049e3bf3266..6cee1528ab611 100644 --- a/compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/SynchronizationTest.kt +++ b/compose/runtime/runtime/src/nativeTest/kotlin/androidx/compose/runtime/platform/SynchronizationTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform import kotlin.concurrent.AtomicInt import kotlin.native.concurrent.* diff --git a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/PausableCompositionTests.kt b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/PausableCompositionTests.kt index 80058fcf655ed..28842faf083a0 100644 --- a/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/PausableCompositionTests.kt +++ b/compose/runtime/runtime/src/nonEmulatorCommonTest/kotlin/androidx/compose/runtime/PausableCompositionTests.kt @@ -25,6 +25,8 @@ import androidx.compose.runtime.mock.ViewApplier import androidx.compose.runtime.mock.compositionTest import androidx.compose.runtime.mock.validate import androidx.compose.runtime.mock.view +import androidx.compose.runtime.platform.SynchronizedObject +import androidx.compose.runtime.platform.synchronized import kotlin.coroutines.resume import kotlin.test.Ignore import kotlin.test.Test diff --git a/compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/SynchronizedObject.posix.kt b/compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/platform/Synchronization.posix.kt similarity index 97% rename from compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/SynchronizedObject.posix.kt rename to compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/platform/Synchronization.posix.kt index 4a1e43c3c282f..0d8c9cf5523a0 100644 --- a/compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/SynchronizedObject.posix.kt +++ b/compose/runtime/runtime/src/posixMain/kotlin/androidx/compose/runtime/platform/Synchronization.posix.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package androidx.compose.runtime +package androidx.compose.runtime.platform import androidx.compose.runtime.internal.currentThreadId import kotlin.native.ref.createCleaner @@ -62,7 +62,7 @@ internal expect val PTHREAD_MUTEX_ERRORCHECK: Int * On the other hand, it does not just spin lock in case of contention, * protecting from an occasional battery drain. */ -internal actual class SynchronizedObject actual constructor() { +internal actual class SynchronizedObject { companion object { private const val NO_OWNER = -1L @@ -156,6 +156,8 @@ internal actual class SynchronizedObject actual constructor() { } } +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + @PublishedApi @Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE") internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { diff --git a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/SynchronizedObject.jvm.kt b/compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/platform/Synchronization.web.kt similarity index 82% rename from compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/SynchronizedObject.jvm.kt rename to compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/platform/Synchronization.web.kt index bb691113ad63c..dd26a5b24030d 100644 --- a/compose/runtime/runtime/src/jvmMain/kotlin/androidx/compose/runtime/SynchronizedObject.jvm.kt +++ b/compose/runtime/runtime/src/webMain/kotlin/androidx/compose/runtime/platform/Synchronization.web.kt @@ -14,13 +14,11 @@ * limitations under the License. */ -@file:JvmName("ActualJvm_jvmKt") -@file:JvmMultifileClass - -package androidx.compose.runtime +package androidx.compose.runtime.platform internal actual class SynchronizedObject +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + @PublishedApi -internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = - kotlin.synchronized(lock, block) +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = block() diff --git a/compose/ui/ui-test/build.gradle b/compose/ui/ui-test/build.gradle index b972fc541063a..e2e5be873cc7e 100644 --- a/compose/ui/ui-test/build.gradle +++ b/compose/ui/ui-test/build.gradle @@ -150,6 +150,9 @@ if (AndroidXComposePlugin.isMultiplatformEnabled(project)) { skikoMain { dependsOn(commonMain) + dependencies { + implementation(libs.atomicFu) + } } desktopMain.dependsOn(skikoMain) diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.kt index bce044ead9c12..8f6e3e240df9d 100644 --- a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.kt +++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.kt @@ -16,6 +16,8 @@ package androidx.compose.ui.test +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized import kotlinx.coroutines.CoroutineExceptionHandler import kotlin.coroutines.AbstractCoroutineContextElement import kotlin.coroutines.CoroutineContext @@ -43,9 +45,10 @@ internal class UncaughtExceptionHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { private var exception: Throwable? = null + private val lock = makeSynchronizedObject(this) override fun handleException(context: CoroutineContext, exception: Throwable) { - synchronized(this) { + synchronized(lock) { if (this.exception == null) { this.exception = exception } else { @@ -67,7 +70,7 @@ internal class UncaughtExceptionHandler : * points to fail the test asap after the exception was caught. */ fun throwUncaught() { - synchronized(this) { + synchronized(lock) { val exception = exception if (exception != null) { this.exception = null @@ -76,5 +79,3 @@ internal class UncaughtExceptionHandler : } } } - -internal expect inline fun synchronized(lock: Any, block: () -> T): T diff --git a/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/platform/Synchronization.kt b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/platform/Synchronization.kt new file mode 100644 index 0000000000000..d4de626c3d0ae --- /dev/null +++ b/compose/ui/ui-test/src/commonMain/kotlin/androidx/compose/ui/test/platform/Synchronization.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.compose.ui.test.platform + +internal expect class SynchronizedObject + +/** + * Returns [ref] as a [SynchronizedObject] on platforms where [Any] is a valid [SynchronizedObject], + * or a new [SynchronizedObject] instance if [ref] is null or this is not supported on the current + * platform. + */ +internal expect inline fun makeSynchronizedObject(ref: Any? = null): SynchronizedObject + +internal expect inline fun synchronized(lock: SynchronizedObject, block: () -> R): R diff --git a/compose/ui/ui-test/src/jsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jsMain.kt b/compose/ui/ui-test/src/jsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jsMain.kt deleted file mode 100644 index 8e0d5ebcfa7fe..0000000000000 --- a/compose/ui/ui-test/src/jsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jsMain.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.ui.test - -internal actual inline fun synchronized(lock: Any, block: () -> T) = block() diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt index 3d973094e9bfc..74df735b843e1 100644 --- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt +++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/FrameDeferringContinuationInterceptor.jvm.kt @@ -18,6 +18,8 @@ package androidx.compose.ui.test import androidx.compose.ui.test.FrameDeferringContinuationInterceptor.FrameDeferredContinuation import androidx.compose.ui.test.internal.DelayPropagatingContinuationInterceptorWrapper +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized import kotlin.coroutines.Continuation import kotlin.coroutines.ContinuationInterceptor import kotlin.coroutines.CoroutineContext @@ -35,7 +37,7 @@ internal class FrameDeferringContinuationInterceptor(parentInterceptor: Continua DelayPropagatingContinuationInterceptorWrapper(parentInterceptor) { private val parentDispatcher = parentInterceptor as? CoroutineDispatcher private val toRunTrampolined = ArrayDeque>() - private val lock = Any() + private val lock = makeSynchronizedObject() private var isDeferringContinuations = false val hasTrampolinedTasks: Boolean diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt index e8d1d1a34b08d..47286dcd12707 100644 --- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt +++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/IdlingResourceRegistry.jvm.kt @@ -17,6 +17,8 @@ package androidx.compose.ui.test import androidx.annotation.VisibleForTesting +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -31,7 +33,7 @@ internal constructor(private val pollScopeOverride: CoroutineScope?) : IdlingRes // Publicly facing constructor, that doesn't override the poll scope @OptIn(InternalTestApi::class) constructor() : this(null) - private val lock = Any() + private val lock = makeSynchronizedObject() // All registered IdlingResources, both idle and busy ones private val idlingResources = mutableSetOf() diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt index a1633eb2d0325..7f0f3a8d77f0a 100644 --- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt +++ b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/TestMonotonicFrameClock.jvm.kt @@ -17,6 +17,8 @@ package androidx.compose.ui.test import androidx.compose.runtime.MonotonicFrameClock +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized import kotlin.coroutines.ContinuationInterceptor import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope @@ -67,7 +69,7 @@ class TestMonotonicFrameClock( requireNotNull(coroutineScope.coroutineContext[ContinuationInterceptor]) { "TestMonotonicFrameClock's coroutineScope must have a ContinuationInterceptor" } - private val lock = Any() + private val lock = makeSynchronizedObject() private var awaiters = mutableListOf<(Long) -> Unit>() private var spareAwaiters = mutableListOf<(Long) -> Unit>() private var scheduledFrameDispatch = false @@ -135,7 +137,7 @@ class TestMonotonicFrameClock( // waiting for it. val frameTime: Long val toRun = - kotlin.synchronized(lock) { + synchronized(lock) { check(scheduledFrameDispatch) { "frame dispatch not scheduled" } frameTime = delayController.currentTime * 1_000_000 diff --git a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt b/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt deleted file mode 100644 index bb3196d3c9575..0000000000000 --- a/compose/ui/ui-test/src/jvmMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.jvm.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.ui.test - -internal actual inline fun synchronized(lock: Any, block: () -> T) = - kotlin.synchronized(lock, block) \ No newline at end of file diff --git a/compose/ui/ui-test/src/nativeMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.nativeMain.kt b/compose/ui/ui-test/src/nativeMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.nativeMain.kt deleted file mode 100644 index a9bddad4222d8..0000000000000 --- a/compose/ui/ui-test/src/nativeMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.nativeMain.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.ui.test - -internal actual inline fun synchronized(lock: Any, block: () -> T): T = block() // TODO: implement using atomicfu diff --git a/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.skiko.kt b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.skiko.kt index abf645a053a3e..602a4178d909c 100644 --- a/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.skiko.kt +++ b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeRootRegistry.skiko.kt @@ -19,6 +19,8 @@ package androidx.compose.ui.test import androidx.compose.ui.InternalComposeUiApi import androidx.compose.ui.platform.PlatformContext import androidx.compose.ui.platform.PlatformRootForTest +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized /** * Registry where all views implementing [PlatformRootForTest] should be registered while they @@ -27,7 +29,7 @@ import androidx.compose.ui.platform.PlatformRootForTest */ @OptIn(InternalComposeUiApi::class) internal class ComposeRootRegistry : PlatformContext.RootForTestListener { - private val lock = Any() + private val lock = makeSynchronizedObject() private val roots = mutableSetOf() /** diff --git a/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeUiTest.skiko.kt b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeUiTest.skiko.kt index d4596592ccd03..7ab77cb38a2d0 100644 --- a/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeUiTest.skiko.kt +++ b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/ComposeUiTest.skiko.kt @@ -36,6 +36,8 @@ import androidx.compose.ui.platform.WindowInfo import androidx.compose.ui.scene.ComposeScene import androidx.compose.ui.scene.CanvasLayersComposeScene import androidx.compose.ui.semantics.SemanticsNode +import androidx.compose.ui.test.platform.makeSynchronizedObject +import androidx.compose.ui.test.platform.synchronized import androidx.compose.ui.text.input.EditCommand import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeOptions @@ -188,6 +190,7 @@ class SkikoComposeUiTest @InternalTestApi constructor( private val testContext = TestContext(testOwner) private val idlingResources = mutableSetOf() + private val idlingResourcesLock = makeSynchronizedObject(idlingResources) fun runTest(block: SkikoComposeUiTest.() -> R): R { return composeRootRegistry.withRegistry { @@ -330,18 +333,18 @@ class SkikoComposeUiTest @InternalTestApi constructor( } override fun registerIdlingResource(idlingResource: IdlingResource) { - synchronized(idlingResources) { + synchronized(idlingResourcesLock) { idlingResources.add(idlingResource) } } override fun unregisterIdlingResource(idlingResource: IdlingResource) { - synchronized(idlingResources) { + synchronized(idlingResourcesLock) { idlingResources.remove(idlingResource) } } - private fun areAllResourcesIdle() = synchronized(idlingResources) { + private fun areAllResourcesIdle() = synchronized(idlingResourcesLock) { idlingResources.all { it.isIdleNow } } diff --git a/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/platform/Synchronization.skiko.kt b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/platform/Synchronization.skiko.kt new file mode 100644 index 0000000000000..7c96ae6bba7fb --- /dev/null +++ b/compose/ui/ui-test/src/skikoMain/kotlin/androidx/compose/ui/test/platform/Synchronization.skiko.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.compose.ui.test.platform + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +internal actual class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() + +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + +@Suppress("BanInlineOptIn") +@OptIn(ExperimentalContracts::class) +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return kotlinx.atomicfu.locks.synchronized(lock, block) +} diff --git a/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.wasmMain.kt b/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.wasmMain.kt deleted file mode 100644 index 8e0d5ebcfa7fe..0000000000000 --- a/compose/ui/ui-test/src/wasmJsMain/kotlin/androidx/compose/ui/test/UncaughtExceptionHandler.wasmMain.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.compose.ui.test - -internal actual inline fun synchronized(lock: Any, block: () -> T) = block() diff --git a/compose/ui/ui-text/build.gradle b/compose/ui/ui-text/build.gradle index 02ff73959949b..85cdf679a2116 100644 --- a/compose/ui/ui-text/build.gradle +++ b/compose/ui/ui-text/build.gradle @@ -38,7 +38,6 @@ if(!AndroidXComposePlugin.isMultiplatformEnabled(project)) { */ implementation(libs.kotlinStdlibCommon) implementation(libs.kotlinCoroutinesCore) - implementation(libs.atomicFu) api(project(":compose:ui:ui-graphics")) api(project(":compose:ui:ui-unit")) @@ -105,7 +104,6 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { commonMain.dependencies { implementation(libs.kotlinStdlibCommon) implementation(libs.kotlinCoroutinesCore) - implementation(libs.atomicFu) api(project(":compose:ui:ui-graphics")) api(project(":compose:ui:ui-unit")) @@ -129,6 +127,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { dependsOn(commonMain) dependencies { api(libs.skikoCommon) + implementation(libs.atomicFu) } } diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextLayoutResult.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextLayoutResult.kt index 11cef1974affc..25c00f0f2ec3a 100644 --- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextLayoutResult.kt +++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextLayoutResult.kt @@ -24,7 +24,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.createFontFamilyResolver import androidx.compose.ui.text.font.toFontFamily import androidx.compose.ui.text.platform.SynchronizedObject -import androidx.compose.ui.text.platform.createSynchronizedObject +import androidx.compose.ui.text.platform.makeSynchronizedObject import androidx.compose.ui.text.platform.synchronized import androidx.compose.ui.text.style.ResolvedTextDirection import androidx.compose.ui.text.style.TextOverflow @@ -272,7 +272,7 @@ private constructor(private val fontFamilyResolver: FontFamily.Resolver) : Font. // call getFontResourceLoader, and evaluate if FontFamily.Resolver is being correctly cached // (via e.g. remember) var cache = mutableMapOf() - val lock: SynchronizedObject = createSynchronizedObject() + val lock: SynchronizedObject = makeSynchronizedObject() fun from(fontFamilyResolver: FontFamily.Resolver): Font.ResourceLoader { synchronized(lock) { diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt index 004da07829a2c..cf6da9e84d2d6 100644 --- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt +++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamilyResolver.kt @@ -18,7 +18,7 @@ package androidx.compose.ui.text.font import androidx.collection.SieveCache import androidx.compose.runtime.State -import androidx.compose.ui.text.platform.createSynchronizedObject +import androidx.compose.ui.text.platform.makeSynchronizedObject import androidx.compose.ui.text.platform.synchronized import androidx.compose.ui.util.fastMap @@ -165,7 +165,7 @@ internal sealed interface TypefaceResult : State { } internal class TypefaceRequestCache { - internal val lock = createSynchronizedObject() + internal val lock = makeSynchronizedObject() // @GuardedBy("lock") private val resultCache = SieveCache(16, 16) diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt index 54c0aa1b5a160..c5f0b0cf56ad2 100644 --- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt +++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.text.platform.FontCacheManagementDispatcher -import androidx.compose.ui.text.platform.createSynchronizedObject +import androidx.compose.ui.text.platform.makeSynchronizedObject import androidx.compose.ui.text.platform.synchronized import androidx.compose.ui.util.fastDistinctBy import androidx.compose.ui.util.fastFilter @@ -367,7 +367,7 @@ internal class AsyncTypefaceCache { // @GuardedBy("cacheLock") private val permanentCache = mutableScatterMapOf() - private val cacheLock = createSynchronizedObject() + private val cacheLock = makeSynchronizedObject() fun put( font: Font, diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/platform/Synchronization.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/platform/Synchronization.kt index 84ccd5d9cdbbd..c43546447a45e 100644 --- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/platform/Synchronization.kt +++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/platform/Synchronization.kt @@ -16,12 +16,14 @@ package androidx.compose.ui.text.platform -// TODO: Replace with another copy for expect/actual posix implementation +internal expect class SynchronizedObject -internal class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() - -internal fun createSynchronizedObject() = SynchronizedObject() +/** + * Returns [ref] as a [SynchronizedObject] on platforms where [Any] is a valid [SynchronizedObject], + * or a new [SynchronizedObject] instance if [ref] is null or this is not supported on the current + * platform. + */ +internal expect inline fun makeSynchronizedObject(ref: Any? = null): SynchronizedObject @PublishedApi -internal inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = - kotlinx.atomicfu.locks.synchronized(lock, block) +internal expect inline fun synchronized(lock: SynchronizedObject, block: () -> R): R diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/Synchronization.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/Synchronization.skiko.kt new file mode 100644 index 0000000000000..ba491010311c2 --- /dev/null +++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/platform/Synchronization.skiko.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("SynchronizationKt") + +package androidx.compose.ui.text.platform + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + +internal actual class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() + +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + +@PublishedApi +@Suppress("BanInlineOptIn") +@OptIn(ExperimentalContracts::class) +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return kotlinx.atomicfu.locks.synchronized(lock, block) +} diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle index 8ef3105a4546e..63170fa1dfbf4 100644 --- a/compose/ui/ui/build.gradle +++ b/compose/ui/ui/build.gradle @@ -159,7 +159,6 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { implementation(project(":collection:collection")) implementation(libs.kotlinStdlibCommon) implementation(libs.kotlinCoroutinesCore) - implementation(libs.atomicFu) // when updating the runtime version please also update the runtime-saveable version implementation(project(":compose:runtime:runtime")) @@ -205,6 +204,7 @@ if(AndroidXComposePlugin.isMultiplatformEnabled(project)) { api(project(":compose:ui:ui-graphics")) api(project(":compose:ui:ui-text")) api(libs.skikoCommon) + implementation(libs.atomicFu) } } uikitMain { diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt index 343bc20c4bd0a..9c45245821984 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/autofill/Autofill.kt @@ -17,7 +17,7 @@ package androidx.compose.ui.autofill import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.platform.createSynchronizedObject +import androidx.compose.ui.platform.makeSynchronizedObject import androidx.compose.ui.platform.synchronized /** @@ -73,7 +73,8 @@ class AutofillNode( /*@GuardedBy("this")*/ private var previousId = 0 - private val lock = createSynchronizedObject() + private val lock = makeSynchronizedObject(this) + private fun generateId() = synchronized(lock) { ++previousId } } diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/ImageVector.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/ImageVector.kt index 1329879b2c202..85b3e679a36ef 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/ImageVector.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/ImageVector.kt @@ -25,10 +25,9 @@ import androidx.compose.ui.graphics.PathFillType import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeJoin import androidx.compose.ui.internal.checkPrecondition +import androidx.compose.ui.platform.makeSynchronizedObject import androidx.compose.ui.platform.synchronized import androidx.compose.ui.unit.Dp -import kotlinx.atomicfu.locks.SynchronizedObject -import kotlinx.atomicfu.locks.synchronized /** * Vector graphics object that is generated as a result of [ImageVector.Builder] It can be composed @@ -379,10 +378,10 @@ internal constructor( companion object { private var imageVectorCount = 0 - private val sync = SynchronizedObject() + private val lock = makeSynchronizedObject(this) internal fun generateImageVectorId(): Int { - synchronized(sync) { + synchronized(lock) { return imageVectorCount++ } } diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt index 3f7c06cf51db9..3230f8ff94d1f 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/SuspendingPointerInputFilter.kt @@ -27,7 +27,7 @@ import androidx.compose.ui.node.requireLayoutNode import androidx.compose.ui.platform.InspectorInfo import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.ViewConfiguration -import androidx.compose.ui.platform.createSynchronizedObject +import androidx.compose.ui.platform.makeSynchronizedObject import androidx.compose.ui.platform.synchronized import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize @@ -546,7 +546,7 @@ internal class SuspendingPointerInputModifierNodeImpl( private val pointerHandlers = mutableVectorOf>() - private val pointerHandlersLock = createSynchronizedObject() + private val pointerHandlersLock = makeSynchronizedObject(pointerHandlers) /** * Scratch list for dispatching to handlers for a particular phase. Used to hold a copy of the diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Synchronization.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Synchronization.kt index 875155e4360ea..5ea5ba0879c14 100644 --- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Synchronization.kt +++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/platform/Synchronization.kt @@ -16,12 +16,13 @@ package androidx.compose.ui.platform -// TODO: Replace with another copy for expect/actual posix implementation +internal expect class SynchronizedObject -internal class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() - -internal fun createSynchronizedObject() = SynchronizedObject() +/** + * Returns [ref] as a [SynchronizedObject] on platforms where [Any] is a valid [SynchronizedObject], + * or a new [SynchronizedObject] instance if [ref] is null or this is not supported on the current + * platform. + */ +internal expect inline fun makeSynchronizedObject(ref: Any? = null): SynchronizedObject -@PublishedApi -internal inline fun synchronized(lock: SynchronizedObject, block: () -> R): R = - kotlinx.atomicfu.locks.synchronized(lock, block) +internal expect inline fun synchronized(lock: SynchronizedObject, block: () -> R): R diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/node/SnapshotInvalidationTracker.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/node/SnapshotInvalidationTracker.skiko.kt index b1ba0ea1d372b..bd3465ab0b528 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/node/SnapshotInvalidationTracker.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/node/SnapshotInvalidationTracker.skiko.kt @@ -17,7 +17,7 @@ package androidx.compose.ui.node import androidx.compose.runtime.snapshots.Snapshot -import androidx.compose.ui.platform.createSynchronizedObject +import androidx.compose.ui.platform.makeSynchronizedObject import androidx.compose.ui.getCurrentThreadId import androidx.compose.ui.platform.synchronized import kotlinx.atomicfu.atomic @@ -105,7 +105,7 @@ internal class SnapshotInvalidationTracker( private class CommandList( private var onNewCommand: () -> Unit ) { - private val sync = createSynchronizedObject() + private val lock = makeSynchronizedObject() private val list = mutableListOf<() -> Unit>() private val listCopy = mutableListOf<() -> Unit>() @@ -114,7 +114,7 @@ private class CommandList( * * Can be called concurrently from multiple threads. */ - val hasCommands: Boolean get() = synchronized(sync) { + val hasCommands: Boolean get() = synchronized(lock) { list.isNotEmpty() } @@ -124,7 +124,7 @@ private class CommandList( * Can be called concurrently from multiple threads. */ fun add(command: () -> Unit) { - synchronized(sync) { + synchronized(lock) { list.add(command) } onNewCommand() @@ -137,7 +137,7 @@ private class CommandList( * and concurrent [add]. */ fun perform() { - synchronized(sync) { + synchronized(lock) { listCopy.addAll(list) list.clear() } diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/FlushCoroutineDispatcher.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/FlushCoroutineDispatcher.skiko.kt index e5b4b09705854..fcdbade02d9ce 100644 --- a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/FlushCoroutineDispatcher.skiko.kt +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/FlushCoroutineDispatcher.skiko.kt @@ -42,12 +42,12 @@ internal class FlushCoroutineDispatcher( private val scope = CoroutineScope(scope.coroutineContext.minusKey(Job)) private var immediateTasks = ArrayDeque() private val delayedTasks = ArrayDeque() - private val immediateTasksLock = createSynchronizedObject() - private val delayedTasksLock = createSynchronizedObject() + private val immediateTasksLock = makeSynchronizedObject() + private val delayedTasksLock = makeSynchronizedObject() private var immediateTasksSwap = ArrayDeque() @Volatile private var isPerformingRun = false - private val runLock = createSynchronizedObject() + private val runLock = makeSynchronizedObject() override fun dispatch(context: CoroutineContext, block: Runnable) { synchronized(immediateTasksLock) { diff --git a/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/Synchronization.skiko.kt b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/Synchronization.skiko.kt new file mode 100644 index 0000000000000..170c16393e806 --- /dev/null +++ b/compose/ui/ui/src/skikoMain/kotlin/androidx/compose/ui/platform/Synchronization.skiko.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@file:JvmName("SynchronizationKt") + +package androidx.compose.ui.platform + +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.jvm.JvmName + +internal actual class SynchronizedObject : kotlinx.atomicfu.locks.SynchronizedObject() + +internal actual inline fun makeSynchronizedObject(ref: Any?) = SynchronizedObject() + +@PublishedApi +@Suppress("BanInlineOptIn") +@OptIn(ExperimentalContracts::class) +internal actual inline fun synchronized(lock: SynchronizedObject, block: () -> R): R { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return kotlinx.atomicfu.locks.synchronized(lock, block) +}