diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d70ffd2b1..92baa6672 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -274,6 +274,7 @@ kotlin { "androidx.compose.animation.ExperimentalSharedTransitionApi", "androidx.paging.ExperimentalPagingApi", "kotlin.ExperimentalStdlibApi", + "kotlin.concurrent.atomics.ExperimentalAtomicApi", "kotlin.contracts.ExperimentalContracts", "kotlinx.coroutines.ExperimentalCoroutinesApi", "kotlinx.coroutines.FlowPreview", diff --git a/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt index 0be145624..b4408d8c9 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt @@ -1,7 +1,6 @@ package com.hippo.ehviewer.gallery import androidx.collection.SieveCache -import arrow.atomic.value import arrow.fx.coroutines.ExitCase import arrow.fx.coroutines.bracketCase import com.hippo.ehviewer.EhDB @@ -13,8 +12,8 @@ import com.hippo.ehviewer.util.OSUtils import com.hippo.ehviewer.util.detectAds import com.hippo.ehviewer.util.displayString import com.hippo.ehviewer.util.isAtLeastO -import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.atomics.AtomicInt import kotlin.concurrent.read import kotlin.concurrent.write import kotlinx.coroutines.CoroutineScope @@ -50,7 +49,7 @@ abstract class PageLoader(val scope: CoroutineScope, val gid: Long, startPage: I fun decodePreloadRange(index: Int) = index - 3..index + 3 - fun needDecode(index: Int) = index in decodePreloadRange(prevIndex) && + fun needDecode(index: Int) = index in decodePreloadRange(prevIndex.load()) && lock.read { index !in cache } && pages[index].status !is PageStatus.Ready && pages[index].status !is PageStatus.Blocked @@ -79,7 +78,7 @@ abstract class PageLoader(val scope: CoroutineScope, val gid: Long, startPage: I pages.forEach(Page::reset) } - private var prevIndex by AtomicInteger(-1)::value + private val prevIndex = AtomicInt(-1) fun retryPage(index: Int, orgImg: Boolean = false) { notifyPageWait(index) @@ -131,12 +130,12 @@ abstract class PageLoader(val scope: CoroutineScope, val gid: Long, startPage: I } fun request(index: Int) { - val prefetchRange = if (index >= prevIndex) { + val prefetchRange = if (index >= prevIndex.load()) { index + 1..(index + prefetchPageCount).coerceAtMost(size - 1) } else { index - 1 downTo (index - prefetchPageCount).coerceAtLeast(0) } - prevIndex = index + prevIndex.store(index) val image = lock.read { cache[index] } if (image != null) { notifyPageSucceed(index, image, false) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index c37a41db7..aed56e42a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -47,19 +47,21 @@ import com.hippo.ehviewer.ktbuilder.execute import com.hippo.ehviewer.ktbuilder.imageRequest import com.hippo.ehviewer.util.isAtLeastP import com.hippo.ehviewer.util.isAtLeastU +import com.hippo.ehviewer.util.updateAndGet import com.hippo.files.openFileDescriptor import com.hippo.files.toUri import java.nio.ByteBuffer -import java.util.concurrent.atomic.AtomicInteger +import kotlin.concurrent.atomics.AtomicInt +import kotlin.concurrent.atomics.decrementAndFetch import okio.Path import splitties.init.appCtx class Image private constructor(image: CoilImage, private val src: ImageSource) { - val refcnt = AtomicInteger(1) + val refcnt = AtomicInt(1) fun pin() = refcnt.updateAndGet { if (it != 0) it + 1 else 0 } != 0 - fun unpin() = (refcnt.decrementAndGet() == 0).also { if (it) recycle() } + fun unpin() = (refcnt.decrementAndFetch() == 0).also { if (it) recycle() } val intrinsicSize = with(image) { IntSize(width, height) } val allocationSize = image.size diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpeedTracker.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpeedTracker.kt index 883749fb4..8bc5dee4c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpeedTracker.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpeedTracker.kt @@ -2,7 +2,6 @@ package com.hippo.ehviewer.spider import androidx.collection.MutableObjectIntMap import androidx.collection.mutableObjectIntMapOf -import arrow.atomic.Atomic import arrow.fx.coroutines.fixedRate import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.executeSafely @@ -15,6 +14,7 @@ import io.ktor.client.plugins.timeout import io.ktor.client.request.HttpRequestBuilder import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpStatement +import kotlin.concurrent.atomics.AtomicReference import kotlin.math.pow import kotlin.math.roundToInt import kotlin.time.Duration @@ -70,7 +70,7 @@ suspend inline fun timeoutBySpeed( crossinline f: suspend (HttpResponse) -> R, noinline t: OnTimeout = { throw it }, ) = coroutineScope { - val onTimeout = Atomic(t).let { { e: IOException -> it.getAndSet(null)?.invoke(e) } } + val onTimeout = AtomicReference(t).let { { e: IOException -> it.exchange(null)?.invoke(e) } } val watchdog = launch { delay(Settings.connTimeout.seconds) onTimeout(ConnectTimeoutException(url, Settings.connTimeout * 1000L)) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index 870c9e95d..539cde9de 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -40,7 +40,9 @@ import com.hippo.ehviewer.client.parser.GalleryPageUrlParser import com.hippo.ehviewer.util.displayString import com.hippo.files.find import eu.kanade.tachiyomi.util.system.logcat -import java.util.concurrent.atomic.AtomicInteger +import kotlin.concurrent.atomics.AtomicInt +import kotlin.concurrent.atomics.decrementAndFetch +import kotlin.concurrent.atomics.incrementAndFetch import kotlin.time.Duration.Companion.milliseconds import kotlin.time.TimeSource import kotlinx.coroutines.CancellationException @@ -71,8 +73,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS val spiderDen: SpiderDen = SpiderDen(galleryInfo) private val mPageStateLock = Any() - private val mDownloadedPages = AtomicInteger(0) - private val mFinishedPages = AtomicInteger(0) + private val mDownloadedPages = AtomicInt(0) + private val mFinishedPages = AtomicInt(0) private val mSpiderListeners: MutableList = ArrayList() private var mReadReference = 0 @@ -116,8 +118,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS mSpiderListeners.forEach { it.onPageSuccess( index, - mFinishedPages.get(), - mDownloadedPages.get(), + mFinishedPages.load(), + mDownloadedPages.load(), pageStates.size, ) } @@ -130,8 +132,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS it.onPageFailure( index, error, - mFinishedPages.get(), - mDownloadedPages.get(), + mFinishedPages.load(), + mDownloadedPages.load(), pageStates.size, ) } @@ -142,8 +144,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS synchronized(mSpiderListeners) { mSpiderListeners.forEach { it.onFinish( - mFinishedPages.get(), - mDownloadedPages.get(), + mFinishedPages.load(), + mDownloadedPages.load(), pageStates.size, ) } @@ -182,8 +184,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS } i++ } - mDownloadedPages.lazySet(0) - mFinishedPages.lazySet(0) + mDownloadedPages.store(0) + mFinishedPages.store(0) } mWorkerScope.enterDownloadMode() } @@ -401,14 +403,14 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS val oldState = pageStates[index] pageStates[index] = state if (!isStateDone(oldState) && isStateDone(state)) { - mDownloadedPages.incrementAndGet() + mDownloadedPages.incrementAndFetch() } else if (isStateDone(oldState) && !isStateDone(state)) { - mDownloadedPages.decrementAndGet() + mDownloadedPages.decrementAndFetch() } if (oldState != STATE_FINISHED && state == STATE_FINISHED) { - mFinishedPages.incrementAndGet() + mFinishedPages.incrementAndFetch() } else if (oldState == STATE_FINISHED && state != STATE_FINISHED) { - mFinishedPages.decrementAndGet() + mFinishedPages.decrementAndFetch() } } @@ -418,8 +420,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS } else if (state == STATE_FINISHED) { notifyPageSuccess(index) } - if (mDownloadedPages.get() == size) { - if (mFinishedPages.get() == size) archiveJob.start() + if (mDownloadedPages.load() == size) { + if (mFinishedPages.load() == size) archiveJob.start() notifyAllPageDownloaded() } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt index 5219ca9d4..d4841da83 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/PreferenceWidgets.kt @@ -33,7 +33,6 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.dp -import arrow.atomic.Atomic import com.hippo.ehviewer.ui.openBrowser import com.hippo.ehviewer.ui.settings.PreferenceTokens.PreferenceTextPadding import com.hippo.ehviewer.util.ProgressDialog @@ -44,6 +43,7 @@ import com.jamal.composeprefs3.ui.prefs.SwitchPref import com.jamal.composeprefs3.ui.prefs.TextPref import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.spec.DirectionDestinationSpec +import kotlin.concurrent.atomics.AtomicReference import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.math.roundToInt @@ -152,8 +152,8 @@ fun WorkPreference(title: String, summary: String? = null, work: suspend Corouti @Composable fun LauncherPreference(title: String, summary: String? = null, contract: ActivityResultContract, key: I, work: suspend CoroutineScope.(O) -> Unit) { val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val callback = remember { Atomic<(O) -> Unit> {} } - val launcher = rememberLauncherForActivityResult(contract = contract) { callback.getAndSet { }.invoke(it) } + val callback = remember { AtomicReference<(O) -> Unit> {} } + val launcher = rememberLauncherForActivityResult(contract = contract) { callback.exchange { }.invoke(it) } var completed by remember { mutableStateOf(true) } if (!completed) { ProgressDialog() @@ -161,7 +161,7 @@ fun LauncherPreference(title: String, summary: String? = null, contract: Preference(title = title, summary = summary) { coroutineScope.launch { val o = suspendCoroutine { cont -> - callback.set { cont.resume(it) } + callback.store { cont.resume(it) } launcher.launch(key) } completed = false diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/UConfigScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/UConfigScreen.kt index 3a3ddc3ee..3ff0dd184 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/UConfigScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/UConfigScreen.kt @@ -20,8 +20,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import arrow.atomic.Atomic -import arrow.atomic.value import com.google.accompanist.web.WebView import com.google.accompanist.web.rememberWebViewState import com.hippo.ehviewer.R @@ -32,6 +30,7 @@ import com.hippo.ehviewer.util.setDefaultSettings import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import kotlin.concurrent.atomics.AtomicReference private const val APPLY_JS = "javascript:(function(){var apply = document.getElementById(\"apply\").children[0];apply.click();})();" @@ -39,7 +38,7 @@ private const val APPLY_JS = "javascript:(function(){var apply = document.getEle @Composable fun AnimatedVisibilityScope.UConfigScreen(navigator: DestinationsNavigator) = Screen(navigator) { val url = EhUrl.uConfigUrl - var webview by remember { Atomic(null)::value } + val webview = remember { AtomicReference(null) } val snackbarHostState = remember { SnackbarHostState() } Scaffold( topBar = { @@ -53,7 +52,7 @@ fun AnimatedVisibilityScope.UConfigScreen(navigator: DestinationsNavigator) = Sc actions = { IconButton( onClick = { - webview?.loadUrl(APPLY_JS) + webview.load()?.loadUrl(APPLY_JS) navigator.popBackStack() }, ) { @@ -69,7 +68,7 @@ fun AnimatedVisibilityScope.UConfigScreen(navigator: DestinationsNavigator) = Sc state = state, modifier = Modifier.padding(paddingValues).fillMaxSize(), onCreated = { it.setDefaultSettings() }, - factory = { WebView(it).apply { webview = this } }, + factory = { WebView(it).apply { webview.store(this) } }, ) val applyTip = stringResource(id = R.string.apply_tip) LaunchedEffect(Unit) { snackbarHostState.showSnackbar(applyTip) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt index 83f2c75e6..536fe9061 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/SharedElementTransition.kt @@ -16,7 +16,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Shape -import java.util.concurrent.atomic.AtomicInteger +import kotlin.concurrent.atomics.AtomicInt +import kotlin.concurrent.atomics.fetchAndIncrement val NoopTransitionsVisibilityScope = TransitionsVisibilityScope(emptySet()) @@ -73,14 +74,14 @@ fun initSETConnection() { listThumbGenerator connectTo detailThumbGenerator } -private val atomicIncId = AtomicInteger() +private val atomicIncId = AtomicInt(0) class SETNodeGenerator { private val tracker = hashMapOf() private val opposites = mutableListOf() fun summon(contentKey: String): SETNode { - val node = opposites.firstNotNullOfOrNull { it.tracker[contentKey] } ?: SETNode(contentKey = contentKey, uniqueID = "${atomicIncId.getAndIncrement()}") + val node = opposites.firstNotNullOfOrNull { it.tracker[contentKey] } ?: SETNode(contentKey = contentKey, uniqueID = "${atomicIncId.fetchAndIncrement()}") tracker[contentKey] = node return node } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/Atomics.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/Atomics.kt new file mode 100644 index 000000000..f421ebf07 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/Atomics.kt @@ -0,0 +1,28 @@ +package com.hippo.ehviewer.util + +import kotlin.concurrent.atomics.AtomicInt +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +inline fun AtomicInt.loop(action: (Int) -> Unit): Nothing { + contract { callsInPlace(action, InvocationKind.AT_LEAST_ONCE) } + do { + action(load()) + } while (true) +} + +inline fun AtomicInt.update(function: (Int) -> Int, transform: (old: Int, new: Int) -> R): R { + contract { + callsInPlace(function, InvocationKind.AT_LEAST_ONCE) + callsInPlace(transform, InvocationKind.AT_MOST_ONCE) + } + loop { cur -> + val upd = function(cur) + if (compareAndSet(cur, upd)) return transform(cur, upd) + } +} + +inline fun AtomicInt.updateAndGet(function: (Int) -> Int): Int { + contract { callsInPlace(function, InvocationKind.AT_LEAST_ONCE) } + return update(function) { _, new -> new } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt index 88fa64b5b..940314d08 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt @@ -20,16 +20,17 @@ import androidx.core.content.FileProvider import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner -import arrow.atomic.AtomicInt import com.hippo.ehviewer.R import eu.kanade.tachiyomi.util.lang.withUIContext import java.io.File +import kotlin.concurrent.atomics.AtomicInt +import kotlin.concurrent.atomics.fetchAndIncrement import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine // Fuck off the silly Android launcher and callback :) -private val atomicInteger = AtomicInt() +private val atomicInteger = AtomicInt(0) private val Context.lifecycle: Lifecycle get() { @@ -44,7 +45,7 @@ private val Context.lifecycle: Lifecycle } suspend fun Context.awaitActivityResult(contract: ActivityResultContract, input: I): O { - val key = "activity_rq#${atomicInteger.getAndIncrement()}" + val key = "activity_rq#${atomicInteger.fetchAndIncrement()}" var launcher: ActivityResultLauncher? = null var observer: LifecycleEventObserver? = null observer = LifecycleEventObserver { _, event ->