package dev.inmo.micro_utils.coroutines import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock private class SubscribeAsyncReceiver( val scope: CoroutineScope, output: suspend SubscribeAsyncReceiver.(T) -> Unit ) { private val dataChannel: Channel = Channel(Channel.UNLIMITED) val channel: SendChannel get() = dataChannel init { scope.launchSafelyWithoutExceptions { for (data in dataChannel) { output(data) } } } fun isEmpty(): Boolean = dataChannel.isEmpty } private sealed interface AsyncSubscriptionCommand { suspend operator fun invoke(markersMap: MutableMap>) } private data class AsyncSubscriptionCommandData( val data: T, val scope: CoroutineScope, val markerFactory: suspend (T) -> M, val block: suspend (T) -> Unit, val onEmpty: suspend (M) -> Unit ) : AsyncSubscriptionCommand { override suspend fun invoke(markersMap: MutableMap>) { val marker = markerFactory(data) markersMap.getOrPut(marker) { SubscribeAsyncReceiver(scope.LinkedSupervisorScope()) { safelyWithoutExceptions { block(it) } if (isEmpty()) { onEmpty(marker) } } }.channel.send(data) } } private data class AsyncSubscriptionCommandClearReceiver( val marker: M ) : AsyncSubscriptionCommand { override suspend fun invoke(markersMap: MutableMap>) { val receiver = markersMap[marker] if (receiver ?.isEmpty() == true) { markersMap.remove(marker) receiver.scope.cancel() } } } fun Flow.subscribeAsync( scope: CoroutineScope, markerFactory: suspend (T) -> M, block: suspend (T) -> Unit ): Job { val subscope = scope.LinkedSupervisorScope() val markersMap = mutableMapOf>() val actor = subscope.actor>(Channel.UNLIMITED) { it.invoke(markersMap) } val job = subscribeSafelyWithoutExceptions(subscope) { data -> val dataCommand = AsyncSubscriptionCommandData(data, subscope, markerFactory, block) { marker -> actor.send( AsyncSubscriptionCommandClearReceiver(marker) ) } actor.send(dataCommand) } job.invokeOnCompletion { if (subscope.isActive) subscope.cancel() } return job } inline fun Flow.subscribeSafelyAsync( scope: CoroutineScope, noinline markerFactory: suspend (T) -> M, noinline onException: ExceptionHandler = defaultSafelyExceptionHandler, noinline block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safely(onException) { block(it) } } inline fun Flow.subscribeSafelyWithoutExceptionsAsync( scope: CoroutineScope, noinline markerFactory: suspend (T) -> M, noinline onException: ExceptionHandler = defaultSafelyWithoutExceptionHandlerWithNull, noinline block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safelyWithoutExceptions(onException) { block(it) } } inline fun Flow.subscribeSafelySkippingExceptionsAsync( scope: CoroutineScope, noinline markerFactory: suspend (T) -> M, noinline block: suspend (T) -> Unit ) = subscribeAsync(scope, markerFactory) { safelyWithoutExceptions({ /* do nothing */}) { block(it) } }