package com.github.insanusmokrassar.PsychomatrixBase.utils.extensions import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import java.util.concurrent.TimeUnit private sealed class DebounceAction { abstract val value: T } private data class AddValue(override val value: T) : DebounceAction() private data class RemoveJob(override val value: T, val job: Job) : DebounceAction() fun ReceiveChannel.debounceByValue( delayMillis: Long, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), resultBroadcastChannelCapacity: Int = 32 ): ReceiveChannel { val outChannel = Channel(resultBroadcastChannelCapacity) val values = HashMap() val channel = Channel>(Channel.UNLIMITED) scope.launch { for (action in channel) { when (action) { is AddValue -> { val msg = action.value values[msg] ?.cancel() lateinit var job: Job job = launch { delay(delayMillis) outChannel.send(msg) channel.send(RemoveJob(msg, job)) } values[msg] = job } is RemoveJob -> if (values[action.value] == action.job) { values.remove(action.value) } } } } scope.launch { for (msg in this@debounceByValue) { channel.send(AddValue(msg)) } } return outChannel } typealias AccumulatedValues = Pair> fun ReceiveChannel>.accumulateByKey( delayMillis: Long, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), resultBroadcastChannelCapacity: Int = 32 ): ReceiveChannel> { val outChannel = Channel>(resultBroadcastChannelCapacity) val values = HashMap>() val jobs = HashMap() val channel = Channel>>(Channel.UNLIMITED) scope.launch { for (action in channel) { val (key, value) = action.value when (action) { is AddValue -> { jobs[key] ?.cancel() (values[key] ?: mutableListOf().also { values[key] = it }).add(value) lateinit var job: Job job = launch { delay(delayMillis) values[key] ?.let { outChannel.send(key to it) channel.send(RemoveJob(key to value, job)) } } jobs[key] = job } is RemoveJob -> if (values[key] == action.job) { values.remove(key) jobs.remove(key) } } } } scope.launch { for (msg in this@accumulateByKey) { channel.send(AddValue(msg)) } } return outChannel } fun ReceiveChannel.subscribeChecking( throwableHandler: (Throwable) -> Boolean = { it.printStackTrace() true }, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), by: suspend (T) -> Boolean ): Job { val channel = this return scope.launch { for (data in channel) { launch { try { if (!by(data)) { channel.cancel() } } catch (e: Throwable) { if (!throwableHandler(e)) { channel.cancel() } } } } } } fun ReceiveChannel.subscribe( throwableHandler: (Throwable) -> Boolean = { it.printStackTrace() true }, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), by: suspend (T) -> Unit ): Job { return subscribeChecking(throwableHandler, scope) { by(it) true } } fun ReceiveChannel.debounce( delay: Long, timeUnit: TimeUnit = TimeUnit.MILLISECONDS, scope: CoroutineScope = CoroutineScope(Dispatchers.Default), resultBroadcastChannelCapacity: Int = 1 ): BroadcastChannel { return BroadcastChannel(resultBroadcastChannelCapacity).also { outBroadcast -> var lastReceived: Job? = null subscribe(scope = scope) { lastReceived ?.cancel() lastReceived = scope.launch { delay(timeUnit.toMillis(delay)) outBroadcast.send(it) } } } }