diff --git a/CHANGELOG.md b/CHANGELOG.md index 32e85587991..d5deb599ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 0.20.50 + +* `Versions`: + * `Coroutines`: `1.8.0` -> `1.8.1` + * `KSLog`: `1.3.3` -> `1.3.4` + * `Exposed`: `0.50.0` -> `0.50.1` + * `Ktor`: `2.3.10` -> `2.3.11` +* A lot of inline functions became common functions due to inline with only noinline callbacks in arguments leads to +low performance +* `Coroutines`: + * `SmartMutex`, `SmartSemaphore` and `SmartRWLocker` as their user changed their state flow to `SpecialMutableStateFlow` + ## 0.20.49 * `Repos`: diff --git a/android/pickers/src/androidMain/kotlin/NumberPicker.kt b/android/pickers/src/androidMain/kotlin/NumberPicker.kt index 4226e1d4168..59265e0f855 100644 --- a/android/pickers/src/androidMain/kotlin/NumberPicker.kt +++ b/android/pickers/src/androidMain/kotlin/NumberPicker.kt @@ -7,9 +7,6 @@ import androidx.compose.foundation.gestures.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.ContentAlpha -import androidx.compose.material.IconButton -import androidx.compose.material.ProvideTextStyle import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp @@ -75,7 +72,7 @@ fun NumberPicker( } val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx val animatedStateValue = animatedStateValue(animatedOffset.value) - val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled) + val disabledArrowsColor = arrowsColor.copy(alpha = 0f) val inputFieldShown = if (allowUseManualInput) { remember { mutableStateOf(false) } diff --git a/android/pickers/src/androidMain/kotlin/SetPicker.kt b/android/pickers/src/androidMain/kotlin/SetPicker.kt index b1cfc9fbebd..60b2fbe34cf 100644 --- a/android/pickers/src/androidMain/kotlin/SetPicker.kt +++ b/android/pickers/src/androidMain/kotlin/SetPicker.kt @@ -5,7 +5,6 @@ import androidx.compose.animation.core.exponentialDecay import androidx.compose.foundation.gestures.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState -import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown import androidx.compose.material.icons.filled.KeyboardArrowUp @@ -62,9 +61,7 @@ fun SetPicker( (index - ceil(animatedOffset.value / halvedNumbersColumnHeightPx).toInt()) } val coercedAnimatedOffset = animatedOffset.value % halvedNumbersColumnHeightPx - val boxOffset = (indexAnimatedOffset * halvedNumbersColumnHeightPx) - coercedAnimatedOffset - val disabledArrowsColor = arrowsColor.copy(alpha = ContentAlpha.disabled) - val scrollState = rememberScrollState() + val disabledArrowsColor = arrowsColor.copy(alpha = 0f) Column( modifier = modifier diff --git a/common/build.gradle b/common/build.gradle index 840e2fb8a68..01489bbf4f1 100644 --- a/common/build.gradle +++ b/common/build.gradle @@ -20,23 +20,11 @@ kotlin { } androidMain { dependencies { - api project(":micro_utils.coroutines") api libs.android.fragment } - dependsOn jvmMain } - linuxX64Main { - dependencies { - api libs.okio - } - } - mingwX64Main { - dependencies { - api libs.okio - } - } - linuxArm64Main { + nativeMain { dependencies { api libs.okio } diff --git a/common/src/androidMain/AndroidManifest.xml b/common/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 15e7c2ae675..00000000000 --- a/common/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DateTimeSerializer.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DateTimeSerializer.kt index 245f66b5198..89c2220c882 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DateTimeSerializer.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DateTimeSerializer.kt @@ -8,7 +8,9 @@ import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder -@Serializer(DateTime::class) +/** + * Serializes [DateTime] as its raw [DateTime.unixMillis] and deserializes in the same way + */ object DateTimeSerializer : KSerializer { override val descriptor: SerialDescriptor get() = Double.serializer().descriptor diff --git a/common/src/linuxX64Main/kotlin/ActualMPPFile.kt b/common/src/linuxX64Main/kotlin/ActualMPPFile.kt deleted file mode 100644 index ff5f9619c53..00000000000 --- a/common/src/linuxX64Main/kotlin/ActualMPPFile.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.inmo.micro_utils.common - -import okio.FileSystem -import okio.Path -import okio.use - -actual typealias MPPFile = Path - -/** - * @suppress - */ -actual val MPPFile.filename: FileName - get() = FileName(toString()) -/** - * @suppress - */ -actual val MPPFile.filesize: Long - get() = FileSystem.SYSTEM.openReadOnly(this).use { - it.size() - } -/** - * @suppress - */ -actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator - get() = { - FileSystem.SYSTEM.read(this) { - readByteArray() - } - } -/** - * @suppress - */ -actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator - get() = { - bytesAllocatorSync() - } diff --git a/common/src/linuxX64Main/kotlin/fixed.kt b/common/src/linuxX64Main/kotlin/fixed.kt deleted file mode 100644 index 14e0a168cba..00000000000 --- a/common/src/linuxX64Main/kotlin/fixed.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.inmo.micro_utils.common - -import kotlinx.cinterop.* -import platform.posix.snprintf -import platform.posix.sprintf - -@OptIn(ExperimentalForeignApi::class) -actual fun Float.fixed(signs: Int): Float { - return memScoped { - val buff = allocArray(Float.SIZE_BYTES * 2) - - sprintf(buff, "%.${signs}f", this@fixed) - buff.toKString().toFloat() - } -} - -@OptIn(ExperimentalForeignApi::class) -actual fun Double.fixed(signs: Int): Double { - return memScoped { - val buff = allocArray(Double.SIZE_BYTES * 2) - - sprintf(buff, "%.${signs}f", this@fixed) - buff.toKString().toDouble() - } -} diff --git a/common/src/mingwX64Main/kotlin/ActualMPPFile.kt b/common/src/mingwX64Main/kotlin/ActualMPPFile.kt deleted file mode 100644 index ff5f9619c53..00000000000 --- a/common/src/mingwX64Main/kotlin/ActualMPPFile.kt +++ /dev/null @@ -1,36 +0,0 @@ -package dev.inmo.micro_utils.common - -import okio.FileSystem -import okio.Path -import okio.use - -actual typealias MPPFile = Path - -/** - * @suppress - */ -actual val MPPFile.filename: FileName - get() = FileName(toString()) -/** - * @suppress - */ -actual val MPPFile.filesize: Long - get() = FileSystem.SYSTEM.openReadOnly(this).use { - it.size() - } -/** - * @suppress - */ -actual val MPPFile.bytesAllocatorSync: ByteArrayAllocator - get() = { - FileSystem.SYSTEM.read(this) { - readByteArray() - } - } -/** - * @suppress - */ -actual val MPPFile.bytesAllocator: SuspendByteArrayAllocator - get() = { - bytesAllocatorSync() - } diff --git a/common/src/mingwX64Main/kotlin/fixed.kt b/common/src/mingwX64Main/kotlin/fixed.kt deleted file mode 100644 index 14e0a168cba..00000000000 --- a/common/src/mingwX64Main/kotlin/fixed.kt +++ /dev/null @@ -1,25 +0,0 @@ -package dev.inmo.micro_utils.common - -import kotlinx.cinterop.* -import platform.posix.snprintf -import platform.posix.sprintf - -@OptIn(ExperimentalForeignApi::class) -actual fun Float.fixed(signs: Int): Float { - return memScoped { - val buff = allocArray(Float.SIZE_BYTES * 2) - - sprintf(buff, "%.${signs}f", this@fixed) - buff.toKString().toFloat() - } -} - -@OptIn(ExperimentalForeignApi::class) -actual fun Double.fixed(signs: Int): Double { - return memScoped { - val buff = allocArray(Double.SIZE_BYTES * 2) - - sprintf(buff, "%.${signs}f", this@fixed) - buff.toKString().toDouble() - } -} diff --git a/common/src/linuxArm64Main/kotlin/ActualMPPFile.kt b/common/src/nativeMain/kotlin/ActualMPPFile.kt similarity index 100% rename from common/src/linuxArm64Main/kotlin/ActualMPPFile.kt rename to common/src/nativeMain/kotlin/ActualMPPFile.kt diff --git a/common/src/linuxArm64Main/kotlin/fixed.kt b/common/src/nativeMain/kotlin/fixed.kt similarity index 100% rename from common/src/linuxArm64Main/kotlin/fixed.kt rename to common/src/nativeMain/kotlin/fixed.kt diff --git a/coroutines/build.gradle b/coroutines/build.gradle index 91a9ff9f65f..8c246516b70 100644 --- a/coroutines/build.gradle +++ b/coroutines/build.gradle @@ -22,7 +22,6 @@ kotlin { dependencies { api libs.kt.coroutines.android } - dependsOn(jvmMain) } } } diff --git a/coroutines/compose/src/androidMain/AndroidManifest.xml b/coroutines/compose/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 9b65eb06cff..00000000000 --- a/coroutines/compose/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1 +0,0 @@ - diff --git a/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt b/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt deleted file mode 100644 index c3729ac32cf..00000000000 --- a/coroutines/compose/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/compose/FlowState.kt +++ /dev/null @@ -1,46 +0,0 @@ -package dev.inmo.micro_utils.coroutines.compose - -import androidx.compose.runtime.MutableState -import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers - -/** - * This type works like [MutableState], [kotlinx.coroutines.flow.StateFlow] and [kotlinx.coroutines.flow.MutableSharedFlow]. - * Based on [SpecialMutableStateFlow] - */ -@Deprecated("Will be removed soon") -class FlowState( - initial: T, - internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default) -) : MutableState, - SpecialMutableStateFlow(initial, internalScope) { - private var internalValue: T = initial - override var value: T - get() = internalValue - set(value) { - internalValue = value - tryEmit(value) - } - - override fun onChangeWithoutSync(value: T) { - internalValue = value - super.onChangeWithoutSync(value) - } - - override fun component1(): T = value - - override fun component2(): (T) -> Unit = { tryEmit(it) } - - override fun tryEmit(value: T): Boolean { - internalValue = value - return super.tryEmit(value) - } - - override suspend fun emit(value: T) { - internalValue = value - super.emit(value) - } -} - -//fun MutableState.asFlowState(scope: CoroutineScope = CoroutineScope(Dispatchers.Main)) = FlowState(this, scope) diff --git a/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt b/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt new file mode 100644 index 00000000000..2f4ad81e9f0 --- /dev/null +++ b/coroutines/compose/src/jvmTest/kotlin/FlowStateTests.kt @@ -0,0 +1,24 @@ +import androidx.compose.material.Button +import androidx.compose.material.Text +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.test.* +import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow +import org.jetbrains.annotations.TestOnly +import kotlin.test.Test + +class FlowStateTests { + @OptIn(ExperimentalTestApi::class) + @Test + @TestOnly + fun simpleTest() = runComposeUiTest { + val flowState = SpecialMutableStateFlow(0) + setContent { + Button({ flowState.value++ }) { Text("Click") } + Text(flowState.collectAsState().value.toString()) + } + + onNodeWithText(0.toString()).assertExists() + onNodeWithText("Click").performClick() + onNodeWithText(1.toString()).assertExists() + } +} \ No newline at end of file diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt index 94891715b83..2288349de6b 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/DoWithFirst.kt @@ -16,8 +16,8 @@ class DoWithFirstBuilder( operator fun plus(block: suspend CoroutineScope.() -> T) { deferreds.add(scope.async(start = CoroutineStart.LAZY, block = block)) } - inline fun add(noinline block: suspend CoroutineScope.() -> T) = plus(block) - inline fun include(noinline block: suspend CoroutineScope.() -> T) = plus(block) + fun add(block: suspend CoroutineScope.() -> T) = plus(block) + fun include(block: suspend CoroutineScope.() -> T) = plus(block) fun build() = deferreds.toList() } diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscriptionAsync.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscriptionAsync.kt index 7bde7300015..5a8bea14a88 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscriptionAsync.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscriptionAsync.kt @@ -85,32 +85,32 @@ fun Flow.subscribeAsync( return job } -inline fun Flow.subscribeSafelyAsync( +fun Flow.subscribeSafelyAsync( scope: CoroutineScope, - noinline markerFactory: suspend (T) -> M, - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend (T) -> Unit + markerFactory: suspend (T) -> M, + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safely(onException) { block(it) } } -inline fun Flow.subscribeSafelyWithoutExceptionsAsync( +fun Flow.subscribeSafelyWithoutExceptionsAsync( scope: CoroutineScope, - noinline markerFactory: suspend (T) -> M, - noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - noinline block: suspend (T) -> Unit + markerFactory: suspend (T) -> M, + onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, + block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safelyWithoutExceptions(onException) { block(it) } } -inline fun Flow.subscribeSafelySkippingExceptionsAsync( +fun Flow.subscribeSafelySkippingExceptionsAsync( scope: CoroutineScope, - noinline markerFactory: suspend (T) -> M, - noinline block: suspend (T) -> Unit + markerFactory: suspend (T) -> M, + block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safelyWithoutExceptions({ /* do nothing */}) { block(it) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt index c8ab319ea48..3b1a93e8f0a 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/HandleSafely.kt @@ -51,7 +51,7 @@ class ContextSafelyExceptionHandler( * @see ContextSafelyExceptionHandler * @see ContextSafelyExceptionHandlerKey */ -suspend inline fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey] +suspend fun contextSafelyExceptionHandler() = coroutineContext[ContextSafelyExceptionHandlerKey] /** * This method will set new [coroutineContext] with [ContextSafelyExceptionHandler]. In case if [coroutineContext] @@ -96,9 +96,9 @@ suspend fun safelyWithContextExceptionHandler( * @see safelyWithoutExceptions * @see safelyWithContextExceptionHandler */ -suspend inline fun safely( - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend CoroutineScope.() -> T +suspend fun safely( + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend CoroutineScope.() -> T ): T { return try { supervisorScope(block) @@ -108,26 +108,26 @@ suspend inline fun safely( } } -suspend inline fun runCatchingSafely( - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend CoroutineScope.() -> T +suspend fun runCatchingSafely( + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend CoroutineScope.() -> T ): Result = runCatching { safely(onException, block) } -suspend inline fun T.runCatchingSafely( - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend T.() -> R +suspend fun T.runCatchingSafely( + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend T.() -> R ): Result = runCatching { safely(onException) { block() } } -suspend inline fun safelyWithResult( - noinline block: suspend CoroutineScope.() -> T +suspend fun safelyWithResult( + block: suspend CoroutineScope.() -> T ): Result = runCatchingSafely(defaultSafelyExceptionHandler, block) -suspend inline fun T.safelyWithResult( - noinline block: suspend T.() -> R +suspend fun T.safelyWithResult( + block: suspend T.() -> R ): Result = runCatchingSafely(defaultSafelyExceptionHandler, block) /** @@ -147,21 +147,21 @@ val defaultSafelyWithoutExceptionHandlerWithNull: ExceptionHandler = { * Shortcut for [safely] with exception handler, that as expected must return null in case of impossible creating of * result from exception (instead of throwing it, by default always returns null) */ -suspend inline fun safelyWithoutExceptions( - noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - noinline block: suspend CoroutineScope.() -> T +suspend fun safelyWithoutExceptions( + onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, + block: suspend CoroutineScope.() -> T ): T? = safely(onException, block) -suspend inline fun runCatchingSafelyWithoutExceptions( - noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - noinline block: suspend CoroutineScope.() -> T +suspend fun runCatchingSafelyWithoutExceptions( + onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, + block: suspend CoroutineScope.() -> T ): Result = runCatching { safelyWithoutExceptions(onException, block) } -inline fun CoroutineScope( +fun CoroutineScope( context: CoroutineContext, - noinline defaultExceptionsHandler: ExceptionHandler + defaultExceptionsHandler: ExceptionHandler ) = CoroutineScope( context + ContextSafelyExceptionHandler(defaultExceptionsHandler) ) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt index ff4850d5463..ee3cd890c44 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/LaunchSafely.kt @@ -4,38 +4,38 @@ import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -inline fun CoroutineScope.launchSafely( +fun CoroutineScope.launchSafely( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend CoroutineScope.() -> Unit + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend CoroutineScope.() -> Unit ) = launch(context, start) { safely(onException, block) } -inline fun CoroutineScope.launchSafelyWithoutExceptions( +fun CoroutineScope.launchSafelyWithoutExceptions( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, - noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - noinline block: suspend CoroutineScope.() -> Unit + onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, + block: suspend CoroutineScope.() -> Unit ) = launch(context, start) { safelyWithoutExceptions(onException, block) } -inline fun CoroutineScope.asyncSafely( +fun CoroutineScope.asyncSafely( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, - noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, - noinline block: suspend CoroutineScope.() -> T + onException: ExceptionHandler = defaultSafelyExceptionHandler, + block: suspend CoroutineScope.() -> T ) = async(context, start) { safely(onException, block) } -inline fun CoroutineScope.asyncSafelyWithoutExceptions( +fun CoroutineScope.asyncSafelyWithoutExceptions( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, - noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, - noinline block: suspend CoroutineScope.() -> T + onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, + block: suspend CoroutineScope.() -> T ) = async(context, start) { safelyWithoutExceptions(onException, block) } diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt index c8a962d453c..5127fc6ecde 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartMutex.kt @@ -44,7 +44,7 @@ sealed interface SmartMutex { * @param locked Preset state of [isLocked] and its internal [_lockStateFlow] */ class Mutable(locked: Boolean = false) : SmartMutex { - private val _lockStateFlow = MutableStateFlow(locked) + private val _lockStateFlow = SpecialMutableStateFlow(locked) override val lockStateFlow: StateFlow = _lockStateFlow.asStateFlow() private val internalChangesMutex = Mutex() diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt index e52c07a52c6..29016f00a11 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SmartSemaphore.kt @@ -45,7 +45,7 @@ sealed interface SmartSemaphore { * @param locked Preset state of [freePermits] and its internal [_freePermitsStateFlow] */ class Mutable(private val permits: Int, acquiredPermits: Int = 0) : SmartSemaphore { - private val _freePermitsStateFlow = MutableStateFlow(permits - acquiredPermits) + private val _freePermitsStateFlow = SpecialMutableStateFlow(permits - acquiredPermits) override val permitsStateFlow: StateFlow = _freePermitsStateFlow.asStateFlow() private val internalChangesMutex = Mutex(false) diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt index ecc421aa5cb..80972346b2d 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/SpecialMutableStateFlow.kt @@ -11,76 +11,60 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.internal.SynchronizedObject import kotlinx.coroutines.internal.synchronized +import kotlin.coroutines.CoroutineContext /** * Works like [StateFlow], but guarantee that latest value update will always be delivered to * each active subscriber */ open class SpecialMutableStateFlow( - initialValue: T, - internalScope: CoroutineScope = CoroutineScope(Dispatchers.Default) + initialValue: T ) : MutableStateFlow, FlowCollector, MutableSharedFlow { @OptIn(InternalCoroutinesApi::class) private val syncObject = SynchronizedObject() - protected val internalSharedFlow: MutableSharedFlow = MutableSharedFlow( - replay = 0, - extraBufferCapacity = 2, - onBufferOverflow = BufferOverflow.DROP_OLDEST - ) - protected val publicSharedFlow: MutableSharedFlow = MutableSharedFlow( + protected val sharingFlow: MutableSharedFlow = MutableSharedFlow( replay = 1, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST ) - protected var _value: T = initialValue - override var value: T - get() = _value + @OptIn(InternalCoroutinesApi::class) + override var value: T = initialValue set(value) { - doOnChangeAction(value) + synchronized(syncObject) { + if (field != value) { + field = value + sharingFlow.tryEmit(value) + } + } } - protected val job = internalSharedFlow.subscribe(internalScope) { - doOnChangeAction(it) - } override val replayCache: List - get() = publicSharedFlow.replayCache + get() = sharingFlow.replayCache override val subscriptionCount: StateFlow - get() = publicSharedFlow.subscriptionCount + get() = sharingFlow.subscriptionCount + + init { + sharingFlow.tryEmit(initialValue) + } - @OptIn(InternalCoroutinesApi::class) override fun compareAndSet(expect: T, update: T): Boolean { - return synchronized(syncObject) { - if (expect == _value && update != _value) { - doOnChangeAction(update) - } - expect == _value - } - } - - protected open fun onChangeWithoutSync(value: T) { - _value = value - publicSharedFlow.tryEmit(value) - } - @OptIn(InternalCoroutinesApi::class) - protected open fun doOnChangeAction(value: T) { - synchronized(syncObject) { - if (_value != value) { - onChangeWithoutSync(value) - } + if (expect == value) { + value = update } + return expect == value } @ExperimentalCoroutinesApi - override fun resetReplayCache() = publicSharedFlow.resetReplayCache() + override fun resetReplayCache() = sharingFlow.resetReplayCache() override fun tryEmit(value: T): Boolean { - return internalSharedFlow.tryEmit(value) + return compareAndSet(this.value, value) } override suspend fun emit(value: T) { - internalSharedFlow.emit(value) + compareAndSet(this.value, value) } - override suspend fun collect(collector: FlowCollector) = publicSharedFlow.collect(collector) + override suspend fun collect(collector: FlowCollector) = sharingFlow.collect(collector) } diff --git a/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt b/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt new file mode 100644 index 00000000000..8e3aa105621 --- /dev/null +++ b/coroutines/src/commonTest/kotlin/SpecialMutableStateFlowTests.kt @@ -0,0 +1,33 @@ +import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow +import dev.inmo.micro_utils.coroutines.asDeferred +import dev.inmo.micro_utils.coroutines.subscribe +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SpecialMutableStateFlowTests { + @Test + fun simpleTest() = runTest { + val specialMutableStateFlow = SpecialMutableStateFlow(0) + specialMutableStateFlow.value = 1 + specialMutableStateFlow.first { it == 1 } + assertEquals(1, specialMutableStateFlow.value) + } + @Test + fun specialTest() = runTest { + val specialMutableStateFlow = SpecialMutableStateFlow(0) + lateinit var subscriberJob: Job + subscriberJob = specialMutableStateFlow.subscribe(this) { + when (it) { + 1 -> specialMutableStateFlow.value = 2 + 2 -> subscriberJob.cancel() + } + } + specialMutableStateFlow.value = 1 + subscriberJob.join() + assertEquals(2, specialMutableStateFlow.value) + } +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7e2261078a4..d41c4edf0a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -15,5 +15,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.20.49 -android_code_version=255 +version=0.20.50 +android_code_version=256 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a524c01bc50..c28a0158cd1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,18 +2,18 @@ kt = "1.9.23" kt-serialization = "1.6.3" -kt-coroutines = "1.8.0" +kt-coroutines = "1.8.1" -kslog = "1.3.3" +kslog = "1.3.4" jb-compose = "1.6.2" -jb-exposed = "0.50.0" +jb-exposed = "0.50.1" jb-dokka = "1.9.20" korlibs = "5.4.0" uuid = "0.8.4" -ktor = "2.3.10" +ktor = "2.3.11" gh-release = "2.5.2" @@ -26,7 +26,7 @@ kotlin-poet = "1.16.0" versions = "0.51.0" -android-gradle = "8.4.0" +android-gradle = "8.3.2" dexcount = "4.0.0" android-coreKtx = "1.13.1" diff --git a/ktor/client/build.gradle b/ktor/client/build.gradle index bea1eee5581..a3e994d247d 100644 --- a/ktor/client/build.gradle +++ b/ktor/client/build.gradle @@ -15,9 +15,6 @@ kotlin { api libs.ktor.client } } - androidMain { - dependsOn jvmMain - } linuxX64Main { dependencies { diff --git a/ktor/common/build.gradle b/ktor/common/build.gradle index 59af6eb7af8..9f1a98fd5bd 100644 --- a/ktor/common/build.gradle +++ b/ktor/common/build.gradle @@ -16,8 +16,5 @@ kotlin { api libs.ktor.io } } - androidMain { - dependsOn jvmMain - } } } diff --git a/language_codes/build.gradle b/language_codes/build.gradle index 41e41961f03..d425197852e 100644 --- a/language_codes/build.gradle +++ b/language_codes/build.gradle @@ -5,11 +5,3 @@ plugins { } apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64ProjectPresetPath" - -kotlin { - sourceSets { - androidMain { - dependsOn jvmMain - } - } -} diff --git a/language_codes/src/commonMain/kotlin/dev/inmo/micro_utils/language_codes/LanguageCodes.kt b/language_codes/src/commonMain/kotlin/dev/inmo/micro_utils/language_codes/LanguageCodes.kt index bff6adc83f9..2e28dfa8459 100644 --- a/language_codes/src/commonMain/kotlin/dev/inmo/micro_utils/language_codes/LanguageCodes.kt +++ b/language_codes/src/commonMain/kotlin/dev/inmo/micro_utils/language_codes/LanguageCodes.kt @@ -1,3 +1,5 @@ +@file:Suppress("SERIALIZER_TYPE_INCOMPATIBLE") // for suppressing of @Serializable(IetfLangSerializer::class) on inheritors of IetfLang + package dev.inmo.micro_utils.language_codes import kotlinx.serialization.Serializable diff --git a/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle b/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle index e94f8e848c3..f70a1d5d842 100644 --- a/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle +++ b/mppJvmJsAndroidLinuxMingwLinuxArm64Project.gradle @@ -60,6 +60,12 @@ kotlin { implementation kotlin('test-junit') } } + nativeMain.dependsOn commonMain + linuxX64Main.dependsOn nativeMain + mingwX64Main.dependsOn nativeMain + linuxArm64Main.dependsOn nativeMain + + androidMain.dependsOn jvmMain } } diff --git a/mppJvmJsLinuxMingwLinuxArm64Project.gradle b/mppJvmJsLinuxMingwLinuxArm64Project.gradle index b0cec0306a2..622393cf7e6 100644 --- a/mppJvmJsLinuxMingwLinuxArm64Project.gradle +++ b/mppJvmJsLinuxMingwLinuxArm64Project.gradle @@ -45,6 +45,13 @@ kotlin { implementation kotlin('test-junit') } } + + nativeMain.dependsOn commonMain + linuxX64Main.dependsOn nativeMain + mingwX64Main.dependsOn nativeMain + linuxArm64Main.dependsOn nativeMain + + androidMain.dependsOn jvmMain } } diff --git a/mppJvmJsLinuxMingwProject.gradle b/mppJvmJsLinuxMingwProject.gradle index bbefe2b5817..b16cba2f6df 100644 --- a/mppJvmJsLinuxMingwProject.gradle +++ b/mppJvmJsLinuxMingwProject.gradle @@ -55,6 +55,10 @@ kotlin { } } + nativeMain.dependsOn commonMain + linuxX64Main.dependsOn nativeMain + mingwX64Main.dependsOn nativeMain + androidMain.dependsOn jvmMain } } diff --git a/mppProjectWithSerializationAndCompose.gradle b/mppProjectWithSerializationAndCompose.gradle index 776cde94da1..31bc1e0a5dc 100644 --- a/mppProjectWithSerializationAndCompose.gradle +++ b/mppProjectWithSerializationAndCompose.gradle @@ -28,8 +28,8 @@ kotlin { commonMain { dependencies { implementation kotlin('stdlib') - api libs.kt.serialization implementation compose.runtime + api libs.kt.serialization } } commonTest { @@ -47,6 +47,7 @@ kotlin { jvmTest { dependencies { implementation kotlin('test-junit') + implementation compose.uiTest } } jsMain { @@ -61,14 +62,20 @@ kotlin { } } androidUnitTest { + dependencies { + implementation kotlin('test-junit') + implementation libs.android.test.junit + implementation libs.android.espresso + implementation compose.uiTest + } + } + androidInstrumentedTest { dependencies { implementation kotlin('test-junit') implementation libs.android.test.junit implementation libs.android.espresso } } - - androidMain.dependsOn jvmMain } } diff --git a/repos/common/build.gradle b/repos/common/build.gradle index bf9c4a857da..8079f0b47e8 100644 --- a/repos/common/build.gradle +++ b/repos/common/build.gradle @@ -28,7 +28,6 @@ kotlin { api internalProject("micro_utils.common") api internalProject("micro_utils.coroutines") } - dependsOn jvmMain } } } diff --git a/resources/build.gradle b/resources/build.gradle index ba3a7ca9d9e..178fbdff3f5 100644 --- a/resources/build.gradle +++ b/resources/build.gradle @@ -13,9 +13,5 @@ kotlin { api project(":micro_utils.language_codes") } } - - androidMain { - dependsOn(jvmMain) - } } }