All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.peanuuutz.fork.ui.util.MutatorMutex.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * 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 net.peanuuutz.fork.ui.util

import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlin.coroutines.cancellation.CancellationException

@Immutable
@JvmInline
value class MutationPriority private constructor(private val value: Int) {
    @Stable
    operator fun compareTo(other: MutationPriority): Int {
        return value compareTo other.value
    }

    override fun toString(): String {
        return "MutationPriority." + when (this) {
            Default -> "Default"
            User -> "User"
            Force -> "Force"
            else -> error("Unknown MutationPriority $value")
        }
    }

    companion object {
        val Default: MutationPriority = MutationPriority(0)

        val User: MutationPriority = MutationPriority(1)

        val Force: MutationPriority = MutationPriority(2)
    }
}

@Stable
class MutatorMutex {
    suspend fun  mutate(
        priority: MutationPriority = MutationPriority.Default,
        block: suspend () -> R
    ): R {
        return coroutineScope {
            val newMutator = Mutator(priority, coroutineContext[Job]!!)
            newMutator.tryMutate()
            mutex.withLock {
                try {
                    block()
                } finally {
                    currentMutator.compareAndSet(newMutator, null)
                }
            }
        }
    }

    suspend fun  mutate(
        receiver: T,
        priority: MutationPriority = MutationPriority.Default,
        block: suspend T.() -> R
    ): R {
        return coroutineScope {
            val newMutator = Mutator(priority, coroutineContext[Job]!!)
            newMutator.tryMutate()
            mutex.withLock {
                try {
                    receiver.block()
                } finally {
                    currentMutator.compareAndSet(newMutator, null)
                }
            }
        }
    }

    // ======== Internal ========

    private val mutex: Mutex = Mutex()

    private val currentMutator: AtomicRef = atomic(null)

    private fun Mutator.tryMutate() {
        while (true) {
            val oldMutator = currentMutator.value
            if (oldMutator == null || canInterrupt(oldMutator)) {
                if (currentMutator.compareAndSet(oldMutator, this)) {
                    oldMutator?.interrupt()
                    break
                }
            } else {
                throw CancellationException("Current mutator has higher priority than $priority")
            }
        }
    }

    private class Mutator(
        val priority: MutationPriority,
        val job: Job
    ) {
        fun canInterrupt(other: Mutator): Boolean {
            return priority >= other.priority
        }

        fun interrupt() {
            job.cancel()
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy