Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Align synchronization declarations between compose modules. #1699

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <R> synchronized(lock: SynchronizedObject, block: () -> R): R
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <R> synchronized(lock: SynchronizedObject, block: () -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return kotlinx.atomicfu.locks.synchronized(lock, block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<V>(value: V) {
internal expect class InternalAtomicReference<V>(value: V) {
fun get(): V

fun set(value: V)
Expand Down Expand Up @@ -60,7 +60,7 @@ internal class InternalMutatorMutex {
fun cancel() = job.cancel()
}

private val currentMutator = AtomicReference<Mutator?>(null)
private val currentMutator = InternalAtomicReference<Mutator?>(null)
private val mutex = Mutex()

private fun tryMutateOrCancel(mutator: Mutator) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package androidx.compose.material

import kotlinx.atomicfu.atomic

internal actual class AtomicReference<V> actual constructor(value: V) {
internal actual class InternalAtomicReference<V> actual constructor(value: V) {
private val delegate = atomic(value)
actual fun get() = delegate.value
actual fun set(value: V) {
Expand Down
6 changes: 5 additions & 1 deletion compose/runtime/runtime/api/desktop/runtime.api
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 6 additions & 2 deletions compose/runtime/runtime/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<FrameAwaiter<*>>()
private var spareList = mutableListOf<FrameAwaiter<*>>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -446,7 +448,7 @@ internal class CompositionImpl(
private val pendingModifications = AtomicReference<Any?>(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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -31,7 +33,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
*/
internal class Latch {

private val lock = SynchronizedObject()
private val lock = makeSynchronizedObject()
private var awaiters = mutableListOf<Continuation<Unit>>()
private var spareList = mutableListOf<Continuation<Unit>>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,7 +114,7 @@ internal class SlotTable : CompositionData, Iterable<CompositionGroup> {
*/
private var readers = 0

private val lock = SynchronizedObject()
private val lock = makeSynchronizedObject()

/** Tracks whether there is an active writer. */
internal var writer = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -30,7 +30,7 @@ import androidx.compose.runtime.synchronized
*/
internal class SnapshotThreadLocal<T> {
private val map = AtomicReference(emptyThreadMap)
private val writeMutex = SynchronizedObject()
private val writeMutex = makeSynchronizedObject()

private var mainThreadValue: T? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 <R> synchronized(lock: SynchronizedObject, block: () -> R): R
Loading