From 63313ff9648f24a0e3e4772771329cf06158a7ec Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 1 Nov 2020 00:41:02 +0600 Subject: [PATCH 1/7] start 0.2.4 --- CHANGELOG.md | 2 ++ gradle.properties | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94ef7aaeeb..0b3b0fdff9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## 0.2.4 + ## 0.2.3 * `Versions` diff --git a/gradle.properties b/gradle.properties index 06be361b781..1a097e9755b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,4 +19,4 @@ github_release_plugin_version=2.2.12 uuidVersion=0.2.2 group=dev.inmo -version=0.2.3 +version=0.2.4 From 98a1ec82db94aa224e2be28147bc044194506283 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 1 Nov 2020 00:41:57 +0600 Subject: [PATCH 2/7] update serialization version --- CHANGELOG.md | 3 +++ gradle.properties | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b3b0fdff9f..694fe0fa745 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 0.2.4 +* `Versions` + * `Serialization`: `1.0.0` -> `1.0.1` + ## 0.2.3 * `Versions` diff --git a/gradle.properties b/gradle.properties index 1a097e9755b..12a32a9606a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ kotlin.incremental.js=true kotlin_version=1.4.10 kotlin_coroutines_version=1.4.0 -kotlin_serialisation_core_version=1.0.0 +kotlin_serialisation_core_version=1.0.1 kotlin_exposed_version=0.28.1 ktor_version=1.4.1 From 7f19b83828e360d07b2e4c8982da27cf4bb86aaf Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 1 Nov 2020 00:43:47 +0600 Subject: [PATCH 3/7] deprecate broadcast channel flow extensions --- CHANGELOG.md | 3 +++ .../kotlin/dev/inmo/micro_utils/coroutines/BroadcastFlow.kt | 2 ++ .../dev/inmo/micro_utils/coroutines/BroadcastStateFlow.kt | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 694fe0fa745..ada66eaee48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ * `Versions` * `Serialization`: `1.0.0` -> `1.0.1` +* `Coroutines` + * `BroadcastFlow` now is deprecated + * `BroadcastStateFlow` now is deprecated ## 0.2.3 diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastFlow.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastFlow.kt index d3ff5b0b277..f4c18906a82 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastFlow.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastFlow.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* @Suppress("FunctionName") +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") fun BroadcastFlow( internalChannelSize: Int = Channel.BUFFERED ): BroadcastFlow { @@ -15,6 +16,7 @@ fun BroadcastFlow( ) } +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") class BroadcastFlow internal constructor( private val channel: BroadcastChannel, private val flow: Flow diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastStateFlow.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastStateFlow.kt index e2ad31a4af3..53d29ae713f 100644 --- a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastStateFlow.kt +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/BroadcastStateFlow.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.* const val defaultBroadcastStateFlowReplayCacheSize = 1 +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") class BroadcastStateFlow internal constructor( parentFlow: Flow, initial: T, @@ -34,17 +35,20 @@ class BroadcastStateFlow internal constructor( } } +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") fun BroadcastChannel.asStateFlow( value: T, scope: CoroutineScope, replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize ): StateFlow = BroadcastStateFlow(asFlow(), value, replayCacheSize, scope) +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") fun BroadcastChannel.asStateFlow( scope: CoroutineScope, replayCacheSize: Int = defaultBroadcastStateFlowReplayCacheSize ): StateFlow = asStateFlow(null, scope, replayCacheSize) +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") fun broadcastStateFlow( initial: T, scope: CoroutineScope, channelSize: Int = Channel.BUFFERED, @@ -55,6 +59,7 @@ fun broadcastStateFlow( it to it.asStateFlow(initial, scope, replayCacheSize) } +@Deprecated("Deprecated due to stabilization of SharedFlow and StateFlow") fun broadcastStateFlow( scope: CoroutineScope, channelSize: Int = Channel.BUFFERED, From b690f68c7facc9ac9c4eb9b85eb0a7864303bbf8 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Sun, 1 Nov 2020 00:54:07 +0600 Subject: [PATCH 4/7] add Flow#subscribe --- CHANGELOG.md | 4 ++ .../coroutines/FlowSubscription.kt | 37 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscription.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index ada66eaee48..94ffe51e19d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ * `Coroutines` * `BroadcastFlow` now is deprecated * `BroadcastStateFlow` now is deprecated + * New extensions for `Flow`s: + * `Flow#subscribe` + * `Flow#subscribeSafely` + * `Flow#subscribeSafelyWithoutExceptions` ## 0.2.3 diff --git a/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscription.kt b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscription.kt new file mode 100644 index 00000000000..474c230f2c0 --- /dev/null +++ b/coroutines/src/commonMain/kotlin/dev/inmo/micro_utils/coroutines/FlowSubscription.kt @@ -0,0 +1,37 @@ +@file:Suppress("NOTHING_TO_INLINE") + +package dev.inmo.micro_utils.coroutines + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* + +/** + * Shortcut for chain if [Flow.onEach] and [Flow.launchIn] + */ +inline fun Flow.subscribe(scope: CoroutineScope, noinline block: suspend (T) -> Unit) = onEach(block).launchIn(scope) + +/** + * Use [subscribe], but all [block]s will be called inside of [safely] function. + * Use [onException] to set up your reaction for [Throwable]s + */ +inline fun Flow.subscribeSafely( + scope: CoroutineScope, + noinline onException: ExceptionHandler = { throw it }, + noinline block: suspend (T) -> Unit +) = subscribe(scope) { + safely(onException) { + block(it) + } +} + +/** + * Use [subscribeSafelyWithoutExceptions], but all exceptions inside of [safely] will be skipped + */ +inline fun Flow.subscribeSafelyWithoutExceptions( + scope: CoroutineScope, + noinline block: suspend (T) -> Unit +) = subscribeSafely( + scope, + {}, + block +) From 5eb48a58bfd9e718226bed33a9c71ef2538dcd04 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 2 Nov 2020 00:12:37 +0600 Subject: [PATCH 5/7] diff utils rewriting --- CHANGELOG.md | 8 + .../dev/inmo/micro_utils/common/DiffUtils.kt | 170 +++++++++++++++++- .../inmo/micro_utils/common/DiffUtilsTests.kt | 80 +++++++++ 3 files changed, 251 insertions(+), 7 deletions(-) create mode 100644 common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ffe51e19d..85a096e0561 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ * `Versions` * `Serialization`: `1.0.0` -> `1.0.1` +* `Common` + * Full rework of `DiffUtils` + * Data class `Diff` has been added + * Extension `Iterable#calculateDiff` has been added + * Extension `Iterable#calculateStrictDiff` as replacement for `Iterable#calculateDiff` with + `strictComparison` mode enabled + * Functions `Diff` (as analog of `Iterable#calculateDiff`) and `StrictDiff` (as analog of + `Iterable#calculateStrictDiff`) * `Coroutines` * `BroadcastFlow` now is deprecated * `BroadcastStateFlow` now is deprecated diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt index 92e0dca214d..39477a272d2 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt @@ -1,10 +1,166 @@ +@file:Suppress("NOTHING_TO_INLINE") + package dev.inmo.micro_utils.common -fun Iterable.syncWith( - other: Iterable, - removed: (List) -> Unit = {}, - added: (List) -> Unit = {} -) { - removed(filter { it !in other }) - added(other.filter { it !in this }) +private inline fun getObject( + additional: MutableList, + iterator: Iterator +): T? = when { + additional.isNotEmpty() -> additional.removeFirst() + iterator.hasNext() -> iterator.next() + else -> null +} + +/** + * Diff object which contains information about differences between two [Iterable]s + * + * @see calculateDiff + */ +data class Diff internal constructor( + val removed: List>, + /** + * Old-New values pairs + */ + val replaced: List, IndexedValue>>, + val added: List> +) + +private inline fun performChanges( + potentialChanges: MutableList?, IndexedValue?>>, + additionalsInOld: MutableList, + additionalsInNew: MutableList, + changedList: MutableList, IndexedValue>>, + removedList: MutableList>, + addedList: MutableList>, + strictComparison: Boolean +) { + var i = -1 + val (oldObject, newObject) = potentialChanges.lastOrNull() ?: return + for ((old, new) in potentialChanges.take(potentialChanges.size - 1)) { + i++ + val oldOneEqualToNewObject = old ?.value === newObject ?.value || (old ?.value == newObject ?.value && !strictComparison) + val newOneEqualToOldObject = new ?.value === oldObject ?.value || (new ?.value == oldObject ?.value && !strictComparison) + if (oldOneEqualToNewObject || newOneEqualToOldObject) { + changedList.addAll( + potentialChanges.take(i).mapNotNull { + if (it.first != null && it.second != null) it as Pair, IndexedValue> else null + } + ) + val newPotentials = potentialChanges.drop(i).take(potentialChanges.size - i) + when { + oldOneEqualToNewObject -> { + newPotentials.first().second ?.let { addedList.add(it) } + newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) -> + addedList.add(newOne!!) + oldOne ?.let { additionalsInOld.add(oldOne.value) } + } + if (newPotentials.size > 1) { + newPotentials.last().first ?.value ?.let { additionalsInOld.add(it) } + } + } + newOneEqualToOldObject -> { + newPotentials.first().first ?.let { removedList.add(it) } + newPotentials.drop(1).take(newPotentials.size - 2).forEach { (oldOne, newOne) -> + removedList.add(oldOne!!) + newOne ?.let { additionalsInNew.add(newOne.value) } + } + if (newPotentials.size > 1) { + newPotentials.last().second ?.value ?.let { additionalsInNew.add(it) } + } + } + } + potentialChanges.clear() + return + } + } + if (potentialChanges.isNotEmpty() && potentialChanges.last().let { it.first == null && it.second == null }) { + potentialChanges.dropLast(1).forEach { (old, new) -> + when { + old != null && new != null -> changedList.add(old to new) + old != null -> removedList.add(old) + new != null -> addedList.add(new) + } + } + } +} + +/** + * Calculating [Diff] object + * + * @param strictComparison If this parameter set to true, objects which are not equal by links will be used as different + * objects. For example, in case of two "Example" string they will be equal by value, but CAN be different by links + */ +fun Iterable.calculateDiff( + other: Iterable, + strictComparison: Boolean = false +): Diff { + var i = -1 + var j = -1 + + val additionalInOld = mutableListOf() + val additionalInNew = mutableListOf() + + val oldIterator = iterator() + val newIterator = other.iterator() + + val potentiallyChangedObjects = mutableListOf?, IndexedValue?>>() + val changedObjects = mutableListOf, IndexedValue>>() + val addedObjects = mutableListOf>() + val removedObjects = mutableListOf>() + + while (true) { + i++ + j++ + + val oldObject = getObject(additionalInOld, oldIterator) + val newObject = getObject(additionalInNew, newIterator) + + if (oldObject == null && newObject == null) { + break + } + + when { + oldObject === newObject || (oldObject == newObject && !strictComparison) -> { + changedObjects.addAll(potentiallyChangedObjects.map { it as Pair, IndexedValue> }) + potentiallyChangedObjects.clear() + } + else -> { + potentiallyChangedObjects.add(oldObject ?.let { IndexedValue(i, oldObject) } to newObject ?.let { IndexedValue(j, newObject) }) + val previousOldsAdditionsSize = additionalInOld.size + val previousNewsAdditionsSize = additionalInNew.size + performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) + i -= (additionalInOld.size - previousOldsAdditionsSize) + j -= (additionalInNew.size - previousNewsAdditionsSize) + } + } + } + potentiallyChangedObjects.add(null to null) + performChanges(potentiallyChangedObjects, additionalInOld, additionalInNew, changedObjects, removedObjects, addedObjects, strictComparison) + + return Diff(removedObjects.toList(), changedObjects.toList(), addedObjects.toList()) +} + +inline fun Diff(old: Iterable, new: Iterable) = old.calculateDiff(new) +inline fun StrictDiff(old: Iterable, new: Iterable) = old.calculateDiff(new, true) + +/** + * This method call [calculateDiff] with strict mode enabled + */ +inline fun Iterable.calculateStrictDiff( + other: Iterable +) = calculateDiff(other, true) + +/** + * Compare one-to-one + */ +@Deprecated("Will be removed or replaced with some new function. Use calculateDiff instead") +inline fun Iterable.syncWith( + other: Iterable, + noinline removed: (List) -> Unit = {}, + noinline added: (List) -> Unit = {} +) { + calculateDiff(other).also { + removed(it.removed.map { it.value }) + added(it.added.map { it.value }) + } } diff --git a/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt new file mode 100644 index 00000000000..f4568073341 --- /dev/null +++ b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt @@ -0,0 +1,80 @@ +package dev.inmo.micro_utils.common + +import kotlin.math.floor +import kotlin.test.Test +import kotlin.test.assertEquals + +class DiffUtilsTests { + @Test + fun testThatSimpleRemoveWorks() { + val oldList = (0 until 10).toList() + val withIndex = oldList.withIndex() + + for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { + for ((i, v) in withIndex) { + if (i + count > oldList.lastIndex) { + continue + } + val removedSublist = oldList.subList(i, i + count) + oldList.calculateNonstrictDiff(oldList - removedSublist).apply { + assertEquals( + removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) }, + removed + ) + } + } + } + } + + /** + * In this test was used [calculateDiff] parameter `strictComparison`. That is required to be sure that the same + * objects with different links will be used as different objects in `strictComparison` mode + */ + @Test + fun testThatSimpleAddWorks() { + val oldList = (0 until 10).map { it.toString() } + val withIndex = oldList.withIndex() + + for (count in 1 .. (floor(oldList.size.toFloat() / 2).toInt())) { + for ((i, v) in withIndex) { + if (i + count > oldList.lastIndex) { + continue + } + val addedSublist = oldList.subList(i, i + count) + val mutable = oldList.toMutableList() + mutable.addAll(i, oldList.subList(i, i + count).map { it.toCharArray().concatToString() }) + oldList.calculateStrictDiff(mutable).apply { + assertEquals( + addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) }, + added + ) + } + } + } + } + + @Test + fun testThatSimpleChangesWorks() { + val oldList = (0 until 10).map { it.toString() } + val withIndex = oldList.withIndex() + + for (step in 0 until oldList.size) { + for ((i, v) in withIndex) { + val mutable = oldList.toMutableList() + val changes = ( + if (step == 0) i until oldList.size else (i until oldList.size step step) + ).map { index -> + IndexedValue(index, mutable[index]) to IndexedValue(index, "changed$index").also { + mutable[index] = it.value + } + } + oldList.calculateNonstrictDiff(mutable).apply { + assertEquals( + changes, + replaced + ) + } + } + } + } +} From f0127b018e6ffa554a899aa792212e4957d1ab1e Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 2 Nov 2020 00:14:25 +0600 Subject: [PATCH 6/7] hotfix --- .../kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt index f4568073341..fdaa2611059 100644 --- a/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt +++ b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt @@ -16,7 +16,7 @@ class DiffUtilsTests { continue } val removedSublist = oldList.subList(i, i + count) - oldList.calculateNonstrictDiff(oldList - removedSublist).apply { + oldList.calculateDiff(oldList - removedSublist).apply { assertEquals( removedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) }, removed @@ -68,7 +68,7 @@ class DiffUtilsTests { mutable[index] = it.value } } - oldList.calculateNonstrictDiff(mutable).apply { + oldList.calculateDiff(mutable).apply { assertEquals( changes, replaced From 864d576e70cc748e2709c265be485ae750d260de Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Mon, 2 Nov 2020 00:19:23 +0600 Subject: [PATCH 7/7] fix add test for common diff utils --- .../kotlin/dev/inmo/micro_utils/common/DiffUtils.kt | 2 +- .../dev/inmo/micro_utils/common/DiffUtilsTests.kt | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt index 39477a272d2..7033edcfb6f 100644 --- a/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt +++ b/common/src/commonMain/kotlin/dev/inmo/micro_utils/common/DiffUtils.kt @@ -148,7 +148,7 @@ inline fun StrictDiff(old: Iterable, new: Iterable) = old.calculateDif */ inline fun Iterable.calculateStrictDiff( other: Iterable -) = calculateDiff(other, true) +) = calculateDiff(other, strictComparison = true) /** * Compare one-to-one diff --git a/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt index fdaa2611059..6bf75aa9dd4 100644 --- a/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt +++ b/common/src/commonTest/kotlin/dev/inmo/micro_utils/common/DiffUtilsTests.kt @@ -26,10 +26,6 @@ class DiffUtilsTests { } } - /** - * In this test was used [calculateDiff] parameter `strictComparison`. That is required to be sure that the same - * objects with different links will be used as different objects in `strictComparison` mode - */ @Test fun testThatSimpleAddWorks() { val oldList = (0 until 10).map { it.toString() } @@ -40,10 +36,10 @@ class DiffUtilsTests { if (i + count > oldList.lastIndex) { continue } - val addedSublist = oldList.subList(i, i + count) + val addedSublist = oldList.subList(i, i + count).map { "added$it" } val mutable = oldList.toMutableList() - mutable.addAll(i, oldList.subList(i, i + count).map { it.toCharArray().concatToString() }) - oldList.calculateStrictDiff(mutable).apply { + mutable.addAll(i, addedSublist) + oldList.calculateDiff(mutable).apply { assertEquals( addedSublist.mapIndexed { j, o -> IndexedValue(i + j, o) }, added