From 8399ad14a945bf37fe96fe30bf9bfdeb7eaba7f3 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 1 Oct 2019 23:11:47 +0100 Subject: [PATCH 01/31] Mpp implementation of basic elements --- build.gradle | 6 +-- mvicore-base/build.gradle | 34 +++++++++++++ .../kotlin/com/badoo/mvicore/common/base.kt | 13 +++++ .../com/badoo/mvicore/common/binder/Binder.kt | 25 ++++++++++ .../badoo/mvicore/common/binder/Connector.kt | 5 ++ .../mvicore/common/binder/NotNullConnector.kt | 25 ++++++++++ .../badoo/mvicore/common/binder/connection.kt | 45 +++++++++++++++++ .../com/badoo/mvicore/common/cancellable.kt | 19 ++++++++ .../common/element/ActorReducerFeature.kt | 37 ++++++++++++++ .../mvicore/common/element/BaseFeature.kt | 46 ++++++++++++++++++ .../mvicore/common/element/ReducerFeature.kt | 38 +++++++++++++++ .../badoo/mvicore/common/element/elements.kt | 17 +++++++ .../mvicore/common/middleware/Middleware.kt | 41 ++++++++++++++++ .../common/middleware/StandaloneMiddleware.kt | 44 +++++++++++++++++ .../kotlin/com/badoo/mvicore/common/sink.kt | 3 ++ .../kotlin/com/badoo/mvicore/common/source.kt | 40 ++++++++++++++++ .../kotlin/com/badoo/mvicore/common/source.kt | 48 +++++++++++++++++++ .../kotlin/com/badoo/mvicore/common/util.kt | 11 +++++ .../kotlin/com/badoo/mvicore/common/baseJs.kt | 15 ++++++ .../com/badoo/mvicore/common/baseJvm.kt | 5 ++ settings.gradle | 3 ++ 21 files changed, 517 insertions(+), 3 deletions(-) create mode 100644 mvicore-base/build.gradle create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt create mode 100644 mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt create mode 100644 mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt create mode 100644 mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt create mode 100644 mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt diff --git a/build.gradle b/build.gradle index c86fee0a..22166e94 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. - buildscript { - ext.kotlinVersion = '1.3.40' + ext.kotlinVersion = '1.3.50' repositories { google() jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.17" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' diff --git a/mvicore-base/build.gradle b/mvicore-base/build.gradle new file mode 100644 index 00000000..02197675 --- /dev/null +++ b/mvicore-base/build.gradle @@ -0,0 +1,34 @@ +plugins { + id 'org.jetbrains.kotlin.multiplatform' +} + +kotlin { + jvm() + js() + //TODO native + + sourceSets { + commonMain { + dependencies { + implementation kotlin('stdlib-common') + } + } + commonTest { + dependencies { + implementation kotlin('test-common') + implementation kotlin('test-annotations-common') + } + } + jsMain { + dependencies { + implementation kotlin('stdlib-js') + } + } + jvmMain { + dependencies { + implementation kotlin('stdlib') + implementation kotlin('test-junit') + } + } + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt new file mode 100644 index 00000000..4cd6e82a --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt @@ -0,0 +1,13 @@ +package com.badoo.mvicore.common + +expect class AtomicRef(initialValue: V) { + fun compareAndSet(expect: V, update: V): Boolean + fun get(): V +} + +internal fun AtomicRef.update(update: (T) -> T) { + do { + val oldValue = get() + val result = compareAndSet(oldValue, update(oldValue)) + } while (!result) +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt new file mode 100644 index 00000000..d5c85a3d --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -0,0 +1,25 @@ +package com.badoo.mvicore.common.binder + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.lifecycle.Lifecycle + +class Binder(private val lifecycle: Lifecycle? = null): Cancellable { + private val cancellables = mutableListOf() + + fun bind(connection: Pair, Sink>) { + val (from, to) = connection + bind(Connection(from = from, to = to)) + } + + fun bind(connection: Connection) { + val out = connection.connector?.let { it(connection.from!!) } ?: (connection.from as Source) + cancellables += out.connect(connection.to) + } + + override fun cancel() { + cancellables.forEach { it.cancel() } + cancellables.clear() + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt new file mode 100644 index 00000000..70906dd9 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt @@ -0,0 +1,5 @@ +package com.badoo.mvicore.common.binder + +import com.badoo.mvicore.common.Source + +interface Connector: (Source) -> Source diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt new file mode 100644 index 00000000..2d936976 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt @@ -0,0 +1,25 @@ +package com.badoo.mvicore.common.binder + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source + +internal class NotNullConnector(private val mapper: (Out) -> In?): Connector { + override fun invoke(element: Source): Source { + return MapNotNullSource(element, mapper) + } + + override fun toString(): String = + mapper.toString() +} + +private class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { + override fun connect(sink: Sink): Cancellable = + delegate.connect { + mapper(it)?.let { sink(it) } + } + + override fun cancel() { + delegate.cancel() + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt new file mode 100644 index 00000000..26d2da1b --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt @@ -0,0 +1,45 @@ +package com.badoo.mvicore.common.binder + +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source + +data class Connection( + val from: Source? = null, + val to: Sink, + val connector: Connector? = null, + val name: String? = null +) { + companion object { + private const val ANONYMOUS: String = "anonymous" + } + + fun isAnonymous(): Boolean = + name == null + + override fun toString(): String { + val connectorName = connector?.let { " using $it" } ?: "" + return "<${name ?: ANONYMOUS}> (${from ?: "?"} --> $to$connectorName)" + } +} + +infix fun Pair, Sink>.using(transformer: (Out) -> In?): Connection = + using(NotNullConnector(transformer)) + +infix fun Pair, Sink>.using(connector: Connector): Connection = + Connection( + from = first, + to = second, + connector = connector + ) + +infix fun Pair, Sink>.named(name: String): Connection = + Connection( + from = first, + to = second, + name = name + ) + +infix fun Connection.named(name: String) = + copy( + name = name + ) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt new file mode 100644 index 00000000..0c18ee75 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -0,0 +1,19 @@ +package com.badoo.mvicore.common + +interface Cancellable { + fun cancel() +} + +fun cancellableOf(onCancel: () -> Unit): Cancellable = CancellableImpl(onCancel) + +private class CancellableImpl(private val onCancel: () -> Unit): Cancellable { + private var isCancelledRef = AtomicRef(false) + + val isCancelled: Boolean = isCancelledRef.get() + override fun cancel() { + if (!isCancelled) { + isCancelledRef.update { true } + onCancel() + } + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt new file mode 100644 index 00000000..d640ff56 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt @@ -0,0 +1,37 @@ +//package com.badoo.mvicore.common.element +// +//import com.badoo.mvicore.common.Cancellable +//import com.badoo.mvicore.common.Sink +//import com.badoo.mvicore.common.Source +//import com.badoo.mvicore.common.source +//import kotlin.contracts.Effect +// +//class ActorReducerFeature( +// initialState: State, +// bootstrapper: Bootstrapper? = null, +// private val actor: Actor, +// private val reducer: Reducer, +// private val newsPublisher: SimpleNewsPublisher? = null +//) : Feature { +// private val newsSource = source() +// private val stateSource = source(initialState) +// +// init { +// bootstrapper?.invoke()?.connect(this) +// } +// +// override fun invoke(wish: Wish) { +// process(wish) +// } +// +// override fun connect(sink: Sink): Cancellable = stateSource.connect(sink) +// +// override val news: Source = newsSource +// +// private fun process(wish: Wish) { +// val oldState = stateSource.value ?: return +// val newState = reducer(oldState, wish) +// stateSource.invoke(newState) +// newsPublisher?.invoke(oldState, wish, newState)?.let(newsSource) +// } +//} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt new file mode 100644 index 00000000..782ac6e2 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt @@ -0,0 +1,46 @@ +//package com.badoo.mvicore.common.element +// +//import com.badoo.mvicore.common.Cancellable +//import com.badoo.mvicore.common.Sink +//import com.badoo.mvicore.common.Source +//import com.badoo.mvicore.common.source +// +//abstract class BaseFeature ( +// initialState: State, +// private val bootstrapper: Bootstrapper? = null, +// private val wishToAction: (Wish) -> Action, +// private val actor: Actor, +// private val reducer: Reducer, +// private val newsPublisher: NewsPublisher? = null +//) : Feature { +// private val stateSource = source(initialState) +// private val newsSource = source() +// +// init { +// bootstrapper?.invoke()?.connect() +// } +// +// override fun invoke(wish: Wish) { +// val oldState = state +// actor.invoke(oldState, wishToAction(wish)) +// .connect { effect -> +// val newState = reducer(this.state, effect) +// stateSource(newState) +// newsPublisher(wish, effect, newState)?.let { +// newsSource(it) +// } +// } +// } +// +// override fun connect(sink: Sink) = stateSource.connect(sink) +// +// override fun cancel() { +// +// } +// +// val state: State +// get() = stateSource.value!! +// +// override val news: Source +// get() = newsSource +//} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt new file mode 100644 index 00000000..7543b8f5 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt @@ -0,0 +1,38 @@ +//package com.badoo.mvicore.common.element +// +//import com.badoo.mvicore.common.Cancellable +//import com.badoo.mvicore.common.Sink +//import com.badoo.mvicore.common.Source +//import com.badoo.mvicore.common.source +//import kotlin.contracts.Effect +// +//class ReducerFeature( +// initialState: State, +// bootstrapper: Bootstrapper? = null, +// private val reducer: Reducer, +// private val newsPublisher: SimpleNewsPublisher? = null +//) : Feature { +// private val newsSource = source() +// private val stateSource = source(initialState) +// +// init { +// bootstrapper?.invoke()?.connect(this) +// } +// +// override fun invoke(wish: Wish) { +// process(wish) +// } +// +// override fun connect(sink: Sink): Cancellable = stateSource.connect(sink) +// +// override val news: Source = newsSource +// +// private fun process(wish: Wish) { +// val oldState = stateSource.value ?: return +// val newState = reducer(oldState, wish) +// stateSource.invoke(newState) +// newsPublisher?.invoke(oldState, wish, newState)?.let(newsSource) +// } +//} +// +//typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt new file mode 100644 index 00000000..bb20b13c --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt @@ -0,0 +1,17 @@ +package com.badoo.mvicore.common.element + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source + +typealias Bootstrapper = () -> Source + +typealias Actor = (state: State, wish: Wish) -> Source + +typealias Reducer = (state: State, effect: Effect) -> State + +typealias NewsPublisher = (old: State, wish: Wish, effect: Effect, new: State) -> News? + +interface Feature: Sink, Source, Cancellable { + val news: Source +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt new file mode 100644 index 00000000..544034f2 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt @@ -0,0 +1,41 @@ +package com.badoo.mvicore.common.middleware + +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.binder.Connection + +abstract class Middleware( + protected val wrapped: Sink +): Sink { + + open fun onBind(connection: Connection) { + wrapped.applyIfMiddleware { onBind(connection) } + } + + override fun invoke(value: In) { + wrapped(value) + } + + open fun onElement(connection: Connection, element: In) { + wrapped.applyIfMiddleware { onElement(connection, element) } + } + + open fun onComplete(connection: Connection) { + wrapped.applyIfMiddleware { onComplete(connection) } + } + + private inline fun Sink.applyIfMiddleware( + block: Middleware.() -> Unit + ) { + if (this is Middleware<*, *>) { + (this as Middleware).block() + } + } + + protected val innerMost by lazy { + var sink = wrapped + while (sink is Middleware<*, *>) { + sink = (sink as Middleware).wrapped + } + sink + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt new file mode 100644 index 00000000..3b18eb68 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt @@ -0,0 +1,44 @@ +package com.badoo.mvicore.common.middleware + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.binder.Connection + +internal class StandaloneMiddleware( + private val wrappedMiddleware: Middleware, + name: String? = null, + postfix: String? = null +): Middleware(wrappedMiddleware), Cancellable { + private val connection = Connection( + to = innerMost, + name = "${name ?: innerMost::class.qualifiedName}.${postfix ?: "input"}" // FIXME wtf + ) + + init { + onBind(connection) + } + + override fun onBind(connection: Connection) { + assertSame(connection) + wrappedMiddleware.onBind(connection) + } + + override fun invoke(value: In) { + wrappedMiddleware.onElement(connection, value) + wrappedMiddleware.invoke(value) + } + + override fun onComplete(connection: Connection) { + wrappedMiddleware.onComplete(connection) + } + + override fun cancel() { + onComplete(this.connection) + } + + private fun assertSame(connection: Connection) { + if (connection !== this.connection) { + throw IllegalStateException("Middleware was initialised in standalone mode, can't accept other connections") + } + } + +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt new file mode 100644 index 00000000..b1283313 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt @@ -0,0 +1,3 @@ +package com.badoo.mvicore.common + +typealias Sink = (value: T) -> Unit diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt new file mode 100644 index 00000000..607ec304 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -0,0 +1,40 @@ +package com.badoo.mvicore.common + +interface Source : Cancellable { + fun connect(sink: Sink): Cancellable +} + +fun source(initialValue: T): SimpleSource = + SimpleSource(initialValue, true) + +fun source(): SimpleSource = + SimpleSource(null, false) + +class SimpleSource(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { + private val sinks = AtomicRef(listOf>()) + var value: T? = initialValue + private set + + override fun invoke(value: T) { + this.value = value + val sinks = sinks.get() + sinks.forEach { it.invoke(value) } + } + + override fun connect(sink: Sink): Cancellable { + sinks.update { it + sink } + + val value = value + if (emitOnConnect && value != null) { + sink.invoke(value) + } + + return cancellableOf { + sinks.update { it - sink } + } + } + + override fun cancel() { + sinks.update { emptyList() } + } +} diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt new file mode 100644 index 00000000..0938465a --- /dev/null +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt @@ -0,0 +1,48 @@ +package com.badoo.mvicore.common + +import kotlin.test.Test +import kotlin.test.assertEquals + +class SourceTest { + + @Test + fun source_connected_sink_receives_no_values() { + val source = source() + val sink = TestSink() + + source.connect(sink) + assertEquals(emptyList(), sink.values) + } + + @Test + fun source_with_initial_value_connected_sink_receives_a_value() { + val source = source(0) + val sink = TestSink() + + source.connect(sink) + assertEquals(listOf(0), sink.values) + } + + @Test + fun source_sends_value_after_connect_connected_sink_receives_a_value() { + val source = source() + val sink = TestSink() + + source.connect(sink) + source.invoke(0) + + assertEquals(listOf(0), sink.values) + } + + @Test + fun source_sends_value_after_disconnect_connected_sink_receives_no_values() { + val source = source() + val sink = TestSink() + val cancellable = source.connect(sink) + cancellable.cancel() + + source.invoke(0) + + assertEquals(emptyList(), sink.values) + } +} diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt new file mode 100644 index 00000000..90d7cb0c --- /dev/null +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -0,0 +1,11 @@ +package com.badoo.mvicore.common + +class TestSink: Sink { + private val _values: MutableList = mutableListOf() + val values: List + get() = _values + + override fun invoke(value: T) { + _values += value + } +} diff --git a/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt b/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt new file mode 100644 index 00000000..1494b472 --- /dev/null +++ b/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt @@ -0,0 +1,15 @@ +package com.badoo.mvicore.common + +actual class AtomicRef actual constructor(initialValue: V) { + private var value: V = initialValue + + actual fun compareAndSet(expect: V, update: V): Boolean { + if (value == expect) { + value = update + return true + } + return false + } + + actual fun get(): V = value +} diff --git a/mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt b/mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt new file mode 100644 index 00000000..0c4a75a4 --- /dev/null +++ b/mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt @@ -0,0 +1,5 @@ +package com.badoo.mvicore.common + +import java.util.concurrent.atomic.AtomicReference + +actual typealias AtomicRef = AtomicReference diff --git a/settings.gradle b/settings.gradle index 9bbeb729..e626f822 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,6 @@ +include ':mvicore-base' + + include ':mvicore' include ':mvicore-diff' include ':mvicore-android' From 9237883b2d79f93b8e28ac6568994c35e3a36f50 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 1 Oct 2019 23:12:18 +0100 Subject: [PATCH 02/31] Mpp implementation of lifecycle --- .gitignore | 1 + .../mvicore/common/lifecycle/lifecycle.kt | 25 +++++++ .../kotlin/com/badoo/mvicore/common/binder.kt | 67 +++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt create mode 100644 mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt diff --git a/.gitignore b/.gitignore index ae3f416a..c7255485 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /build /captures .externalNativeBuild +/out diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt new file mode 100644 index 00000000..d5536d35 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt @@ -0,0 +1,25 @@ +package com.badoo.mvicore.common.lifecycle + +import com.badoo.mvicore.common.SimpleSource +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN +import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END +import com.badoo.mvicore.common.source + +interface Lifecycle : Source { + enum class Event { + BEGIN, END + } + + fun manual() = ManualLifecycle() +} + +class ManualLifecycle(private val source: SimpleSource = source()) : Lifecycle, Source by source { + fun begin() { + source(BEGIN) + } + + fun end() { + source(END) + } +} diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt new file mode 100644 index 00000000..915705a2 --- /dev/null +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -0,0 +1,67 @@ +package com.badoo.mvicore.common + +import com.badoo.mvicore.common.binder.Binder +import com.badoo.mvicore.common.binder.NotNullConnector +import com.badoo.mvicore.common.binder.using +import kotlin.test.Test +import kotlin.test.assertEquals + +class BinderTest { + private val source = source() + private val sink = TestSink() + + @Test + fun binder_without_lifecycle_connects_source_and_sink() { + Binder().apply { + bind(source to sink) + } + + source.invoke(0) + assertEquals(listOf(0), sink.values) + } + + @Test + fun binder_without_lifecycle_does_not_connect_source_and_sink_after_cancel() { + val binder = Binder().apply { + bind(source to sink) + } + + binder.cancel() + + source.invoke(0) + assertEquals(emptyList(), sink.values) + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_using_mapper() { + Binder().apply { + bind(source to sink using { it + 1 }) + } + + source.invoke(0) + assertEquals(listOf(1), sink.values) + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_skips_nulls_from_mapper() { + Binder().apply { + bind(source to sink using { if (it % 2 == 0) null else it }) + } + + source.invoke(0) + source.invoke(1) + source.invoke(2) + assertEquals(listOf(1), sink.values) + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_using_connector() { + val connector = NotNullConnector { it } + Binder().apply { + bind(source to sink using connector) + } + + source.invoke(0) + assertEquals(listOf(0), sink.values) + } +} From f453cc1e42fc3a7abb06e2e3ae9469355ee97804 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Wed, 9 Oct 2019 01:52:24 +0100 Subject: [PATCH 03/31] Binder implementation --- .../com/badoo/mvicore/common/binder/Binder.kt | 65 ++++++++- .../common/element/ActorReducerFeature.kt | 37 ----- .../mvicore/common/element/BaseFeature.kt | 46 ------- .../mvicore/common/element/ReducerFeature.kt | 38 ----- .../badoo/mvicore/common/element/elements.kt | 7 +- .../common/feature/ActorReducerFeature.kt | 21 +++ .../mvicore/common/feature/BaseFeature.kt | 59 ++++++++ .../mvicore/common/feature/ReducerFeature.kt | 12 ++ .../mvicore/common/lifecycle/lifecycle.kt | 9 +- .../kotlin/com/badoo/mvicore/common/binder.kt | 130 +++++++++++++++++- .../kotlin/com/badoo/mvicore/common/util.kt | 8 ++ 11 files changed, 296 insertions(+), 136 deletions(-) delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index d5c85a3d..602ae584 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -4,16 +4,26 @@ import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.lifecycle.Lifecycle +import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN +import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END -class Binder(private val lifecycle: Lifecycle? = null): Cancellable { +interface Binder : Cancellable { + fun bind(connection: Pair, Sink>) + fun bind(connection: Connection) +} + +fun Binder(): Binder = SimpleBinder() +fun Binder(lifecycle: Lifecycle): Binder = LifecycleBinder(lifecycle) + +internal class SimpleBinder : Binder { private val cancellables = mutableListOf() - fun bind(connection: Pair, Sink>) { + override fun bind(connection: Pair, Sink>) { val (from, to) = connection bind(Connection(from = from, to = to)) } - fun bind(connection: Connection) { + override fun bind(connection: Connection) { val out = connection.connector?.let { it(connection.from!!) } ?: (connection.from as Source) cancellables += out.connect(connection.to) } @@ -23,3 +33,52 @@ class Binder(private val lifecycle: Lifecycle? = null): Cancellable { cancellables.clear() } } + +internal class LifecycleBinder(private val lifecycle: Lifecycle) : Binder { + private var lifecycleActive = false + private val cancellables = mutableListOf() + private val innerBinder = SimpleBinder() + private val connections = mutableListOf>() + + init { + cancellables += lifecycle.connect { + when (it) { + BEGIN -> connect() + END -> disconnect() + } + } + cancellables += innerBinder + } + + override fun bind(connection: Pair, Sink>) { + val (from, to) = connection + bind(Connection(from = from, to = to)) + } + + override fun bind(connection: Connection) { + connections += connection + if (lifecycleActive) { + innerBinder.bind(connection) + } + } + + private fun connect() { + if (lifecycleActive) return + + lifecycleActive = true + connections.forEach { innerBinder.bind(it) } + } + + private fun disconnect() { + if (!lifecycleActive) return + + lifecycleActive = false + innerBinder.cancel() + } + + override fun cancel() { + cancellables.forEach { it.cancel() } + connections.clear() + } + +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt deleted file mode 100644 index d640ff56..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ActorReducerFeature.kt +++ /dev/null @@ -1,37 +0,0 @@ -//package com.badoo.mvicore.common.element -// -//import com.badoo.mvicore.common.Cancellable -//import com.badoo.mvicore.common.Sink -//import com.badoo.mvicore.common.Source -//import com.badoo.mvicore.common.source -//import kotlin.contracts.Effect -// -//class ActorReducerFeature( -// initialState: State, -// bootstrapper: Bootstrapper? = null, -// private val actor: Actor, -// private val reducer: Reducer, -// private val newsPublisher: SimpleNewsPublisher? = null -//) : Feature { -// private val newsSource = source() -// private val stateSource = source(initialState) -// -// init { -// bootstrapper?.invoke()?.connect(this) -// } -// -// override fun invoke(wish: Wish) { -// process(wish) -// } -// -// override fun connect(sink: Sink): Cancellable = stateSource.connect(sink) -// -// override val news: Source = newsSource -// -// private fun process(wish: Wish) { -// val oldState = stateSource.value ?: return -// val newState = reducer(oldState, wish) -// stateSource.invoke(newState) -// newsPublisher?.invoke(oldState, wish, newState)?.let(newsSource) -// } -//} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt deleted file mode 100644 index 782ac6e2..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/BaseFeature.kt +++ /dev/null @@ -1,46 +0,0 @@ -//package com.badoo.mvicore.common.element -// -//import com.badoo.mvicore.common.Cancellable -//import com.badoo.mvicore.common.Sink -//import com.badoo.mvicore.common.Source -//import com.badoo.mvicore.common.source -// -//abstract class BaseFeature ( -// initialState: State, -// private val bootstrapper: Bootstrapper? = null, -// private val wishToAction: (Wish) -> Action, -// private val actor: Actor, -// private val reducer: Reducer, -// private val newsPublisher: NewsPublisher? = null -//) : Feature { -// private val stateSource = source(initialState) -// private val newsSource = source() -// -// init { -// bootstrapper?.invoke()?.connect() -// } -// -// override fun invoke(wish: Wish) { -// val oldState = state -// actor.invoke(oldState, wishToAction(wish)) -// .connect { effect -> -// val newState = reducer(this.state, effect) -// stateSource(newState) -// newsPublisher(wish, effect, newState)?.let { -// newsSource(it) -// } -// } -// } -// -// override fun connect(sink: Sink) = stateSource.connect(sink) -// -// override fun cancel() { -// -// } -// -// val state: State -// get() = stateSource.value!! -// -// override val news: Source -// get() = newsSource -//} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt deleted file mode 100644 index 7543b8f5..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/ReducerFeature.kt +++ /dev/null @@ -1,38 +0,0 @@ -//package com.badoo.mvicore.common.element -// -//import com.badoo.mvicore.common.Cancellable -//import com.badoo.mvicore.common.Sink -//import com.badoo.mvicore.common.Source -//import com.badoo.mvicore.common.source -//import kotlin.contracts.Effect -// -//class ReducerFeature( -// initialState: State, -// bootstrapper: Bootstrapper? = null, -// private val reducer: Reducer, -// private val newsPublisher: SimpleNewsPublisher? = null -//) : Feature { -// private val newsSource = source() -// private val stateSource = source(initialState) -// -// init { -// bootstrapper?.invoke()?.connect(this) -// } -// -// override fun invoke(wish: Wish) { -// process(wish) -// } -// -// override fun connect(sink: Sink): Cancellable = stateSource.connect(sink) -// -// override val news: Source = newsSource -// -// private fun process(wish: Wish) { -// val oldState = stateSource.value ?: return -// val newState = reducer(oldState, wish) -// stateSource.invoke(newState) -// newsPublisher?.invoke(oldState, wish, newState)?.let(newsSource) -// } -//} -// -//typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt index bb20b13c..90196267 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt @@ -1,7 +1,6 @@ package com.badoo.mvicore.common.element import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source typealias Bootstrapper = () -> Source @@ -10,8 +9,10 @@ typealias Actor = (state: State, wish: Wish) -> Source = (state: State, effect: Effect) -> State -typealias NewsPublisher = (old: State, wish: Wish, effect: Effect, new: State) -> News? +typealias NewsPublisher = (old: State, action: Action, effect: Effect, new: State) -> News? -interface Feature: Sink, Source, Cancellable { +typealias PostProcessor = (old: State, action: Action, effect: Effect, new: State) -> Action? + +interface Feature: (Wish) -> Unit, Source, Cancellable { val news: Source } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt new file mode 100644 index 00000000..71442948 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt @@ -0,0 +1,21 @@ +package com.badoo.mvicore.common.feature + +import com.badoo.mvicore.common.element.Actor +import com.badoo.mvicore.common.element.Bootstrapper +import com.badoo.mvicore.common.element.NewsPublisher +import com.badoo.mvicore.common.element.Reducer + +open class ActorReducerFeature( + initialState: State, + bootstrapper: Bootstrapper? = null, + actor: Actor, + reducer: Reducer, + newsPublisher: NewsPublisher? = null +) : BaseFeature( + initialState = initialState, + bootstrapper = bootstrapper, + wishToAction = { it }, + actor = actor, + reducer = reducer, + newsPublisher = newsPublisher +) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt new file mode 100644 index 00000000..305c8801 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -0,0 +1,59 @@ +package com.badoo.mvicore.common.feature + +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.element.Actor +import com.badoo.mvicore.common.element.Bootstrapper +import com.badoo.mvicore.common.element.Feature +import com.badoo.mvicore.common.element.NewsPublisher +import com.badoo.mvicore.common.element.PostProcessor +import com.badoo.mvicore.common.element.Reducer +import com.badoo.mvicore.common.source + +open class BaseFeature ( + initialState: State, + private val bootstrapper: Bootstrapper? = null, + private val wishToAction: (Wish) -> Action, + private val actor: Actor, + private val reducer: Reducer, + private val newsPublisher: NewsPublisher? = null, + private val postProcessor: PostProcessor? = null +): Feature { + private val actionSource = source() + internal val effectSource = source() + private val stateSource = source(initialState) + private val newsSource = source() + + init { + bootstrapper?.invoke()?.connect(actionSource) + actionSource.connect { action -> + val oldState = state + actor.invoke(oldState, action) + .connect { effect -> + val newState = reducer(this.state, effect) + stateSource(newState) + newsPublisher?.invoke(oldState, action, effect, newState)?.let { + newsSource(it) + } + postProcessor?.invoke(oldState, action, effect, newState)?.let(actionSource::invoke) + } + } + } + + override fun invoke(wish: Wish) { + val action = wishToAction(wish) + actionSource.invoke(action) + } + + override fun connect(sink: Sink) = stateSource.connect(sink) + + override fun cancel() { + // TODO + } + + val state: State + get() = stateSource.value!! + + override val news: Source + get() = newsSource +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt new file mode 100644 index 00000000..37538279 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt @@ -0,0 +1,12 @@ +package com.badoo.mvicore.common.feature + +//open class ReducerFeature( +// initialState: State, +// bootstrapper: Bootstrapper? = null, +// private val reducer: Reducer, +// private val newsPublisher: SimpleNewsPublisher? = null +//) : ActorReducerFeature( +// actor = { } +//) + +typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt index d5536d35..e178d27b 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt @@ -4,17 +4,20 @@ import com.badoo.mvicore.common.SimpleSource import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END -import com.badoo.mvicore.common.source interface Lifecycle : Source { enum class Event { BEGIN, END } - fun manual() = ManualLifecycle() + companion object { + fun manual() = ManualLifecycle() + } } -class ManualLifecycle(private val source: SimpleSource = source()) : Lifecycle, Source by source { +class ManualLifecycle( + private val source: SimpleSource = SimpleSource(initialValue = null, emitOnConnect = true) +) : Lifecycle, Source by source { fun begin() { source(BEGIN) } diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 915705a2..40dd8a0c 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -3,8 +3,8 @@ package com.badoo.mvicore.common import com.badoo.mvicore.common.binder.Binder import com.badoo.mvicore.common.binder.NotNullConnector import com.badoo.mvicore.common.binder.using +import com.badoo.mvicore.common.lifecycle.Lifecycle import kotlin.test.Test -import kotlin.test.assertEquals class BinderTest { private val source = source() @@ -17,7 +17,7 @@ class BinderTest { } source.invoke(0) - assertEquals(listOf(0), sink.values) + sink.assertValues(0) } @Test @@ -29,7 +29,7 @@ class BinderTest { binder.cancel() source.invoke(0) - assertEquals(emptyList(), sink.values) + sink.assertNoValues() } @Test @@ -39,7 +39,7 @@ class BinderTest { } source.invoke(0) - assertEquals(listOf(1), sink.values) + sink.assertValues(1) } @Test @@ -51,7 +51,7 @@ class BinderTest { source.invoke(0) source.invoke(1) source.invoke(2) - assertEquals(listOf(1), sink.values) + sink.assertValues(1) } @Test @@ -62,6 +62,124 @@ class BinderTest { } source.invoke(0) - assertEquals(listOf(0), sink.values) + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_connects_source_and_sink_when_active() { + val lifecycle = Lifecycle.manual() + Binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.invoke(0) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_does_not_connect_source_and_sink_before_active() { + val lifecycle = Lifecycle.manual() + Binder(lifecycle).apply { + bind(source to sink) + } + + source.invoke(0) + lifecycle.begin() + + sink.assertNoValues() + } + + @Test + fun binder_with_lifecycle_disconnect_source_and_sink_after_end() { + val lifecycle = Lifecycle.manual() + Binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.invoke(0) + lifecycle.end() + source.invoke(1) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_reconnect_source_and_sink_after_begin() { + val lifecycle = Lifecycle.manual() + Binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.invoke(0) + lifecycle.end() + source.invoke(1) + lifecycle.begin() + source.invoke(2) + + sink.assertValues(0, 2) + } + + @Test + fun binder_with_lifecycle_does_not_reconnect_source_and_sink_after_cancel() { + val lifecycle = Lifecycle.manual() + Binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.invoke(0) + lifecycle.end() + lifecycle.cancel() + lifecycle.begin() + source.invoke(2) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_connects_source_and_sink_if_lifecycle_started() { + val lifecycle = Lifecycle.manual() + lifecycle.begin() + + Binder(lifecycle).apply { + bind(source to sink) + } + + source.invoke(0) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_does_not_reconnect_on_duplicated_lifecycle_events() { + val lifecycle = Lifecycle.manual() + + Binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + lifecycle.begin() + + source.invoke(0) + + sink.assertValues(0) + } + + @Test + fun binder_covariant_endpoints_compile_for_pair() { + val sink = { it: Any -> /* no-op */ } + Binder().bind(source to sink) + } + + @Test + fun binder_covariant_endpoints_compile_for_connection() { + val sink = { it: Any -> /* no-op */ } + val intToString: (Int) -> String = { it.toString() } + Binder().bind(source to sink using intToString) } } diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index 90d7cb0c..e401e439 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -1,5 +1,7 @@ package com.badoo.mvicore.common +import kotlin.test.assertEquals + class TestSink: Sink { private val _values: MutableList = mutableListOf() val values: List @@ -9,3 +11,9 @@ class TestSink: Sink { _values += value } } + +fun TestSink.assertValues(vararg values: T) = + assertEquals(values.toList(), this.values) + +fun TestSink.assertNoValues() = + assertEquals(emptyList(), this.values) From 24209ed20e92f8c48e46ff776387e56aef714fd4 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Wed, 9 Oct 2019 01:53:11 +0100 Subject: [PATCH 04/31] Suppress for function names --- .../commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 602ae584..ac53232c 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -12,7 +12,9 @@ interface Binder : Cancellable { fun bind(connection: Connection) } +@Suppress("FunctionName") fun Binder(): Binder = SimpleBinder() +@Suppress("FunctionName") fun Binder(lifecycle: Lifecycle): Binder = LifecycleBinder(lifecycle) internal class SimpleBinder : Binder { From 1687223e194bed29529eaa556128eabc2b03fda3 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 23 Dec 2019 14:41:46 +0000 Subject: [PATCH 05/31] Allow last binder message to be propagated --- mvicore-base/.gitignore | 1 + .../com/badoo/mvicore/common/binder/Binder.kt | 60 +++++++++++-------- .../binder/{connection.kt => Connection.kt} | 0 .../common/middleware/StandaloneMiddleware.kt | 2 +- .../kotlin/com/badoo/mvicore/common/binder.kt | 45 +++++++++----- 5 files changed, 67 insertions(+), 41 deletions(-) create mode 100644 mvicore-base/.gitignore rename mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/{connection.kt => Connection.kt} (100%) diff --git a/mvicore-base/.gitignore b/mvicore-base/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/mvicore-base/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index ac53232c..434e086a 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -1,42 +1,57 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.SimpleSource import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.lifecycle.Lifecycle import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END +import com.badoo.mvicore.common.source -interface Binder : Cancellable { - fun bind(connection: Pair, Sink>) - fun bind(connection: Connection) +abstract class Binder : Cancellable { + internal abstract fun connect(connection: Connection) } -@Suppress("FunctionName") -fun Binder(): Binder = SimpleBinder() -@Suppress("FunctionName") -fun Binder(lifecycle: Lifecycle): Binder = LifecycleBinder(lifecycle) +fun binder(): Binder = SimpleBinder() +fun binder(lifecycle: Lifecycle): Binder = LifecycleBinder(lifecycle) -internal class SimpleBinder : Binder { - private val cancellables = mutableListOf() +fun Binder.bind(connection: Pair, Sink>) { + val (from, to) = connection + connect(Connection(from = from, to = to)) +} - override fun bind(connection: Pair, Sink>) { - val (from, to) = connection - bind(Connection(from = from, to = to)) - } +fun Binder.bind(connection: Connection) { + connect(connection) +} - override fun bind(connection: Connection) { - val out = connection.connector?.let { it(connection.from!!) } ?: (connection.from as Source) - cancellables += out.connect(connection.to) +internal class SimpleBinder : Binder() { + private val cancellables = mutableListOf() + private val fromToInternalSource = mutableMapOf, SimpleSource<*>>() + + override fun connect(connection: Connection) { + val internalSource = getInternalSourceFor(connection.from!!) + val outSource = if (connection.connector != null) { + connection.connector.invoke(internalSource) + } else { + internalSource as Source + } + outSource.connect(connection.to) } + private fun getInternalSourceFor(from: Source): SimpleSource = + fromToInternalSource.getOrPut(from) { + source().also { cancellables += from.connect(it) } + } as SimpleSource + override fun cancel() { cancellables.forEach { it.cancel() } + fromToInternalSource.clear() cancellables.clear() } } -internal class LifecycleBinder(private val lifecycle: Lifecycle) : Binder { +internal class LifecycleBinder(lifecycle: Lifecycle) : Binder() { private var lifecycleActive = false private val cancellables = mutableListOf() private val innerBinder = SimpleBinder() @@ -52,15 +67,10 @@ internal class LifecycleBinder(private val lifecycle: Lifecycle) : Binder { cancellables += innerBinder } - override fun bind(connection: Pair, Sink>) { - val (from, to) = connection - bind(Connection(from = from, to = to)) - } - - override fun bind(connection: Connection) { + override fun connect(connection: Connection) { connections += connection if (lifecycleActive) { - innerBinder.bind(connection) + innerBinder.connect(connection) } } @@ -68,7 +78,7 @@ internal class LifecycleBinder(private val lifecycle: Lifecycle) : Binder { if (lifecycleActive) return lifecycleActive = true - connections.forEach { innerBinder.bind(it) } + connections.forEach { innerBinder.connect(it) } } private fun disconnect() { diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/connection.kt rename to mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt index 3b18eb68..888a1ef5 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt @@ -10,7 +10,7 @@ internal class StandaloneMiddleware( ): Middleware(wrappedMiddleware), Cancellable { private val connection = Connection( to = innerMost, - name = "${name ?: innerMost::class.qualifiedName}.${postfix ?: "input"}" // FIXME wtf + name = "${name ?: ""}.${postfix ?: "input"}" // FIXME wtf ) init { diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 40dd8a0c..653e3566 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -1,7 +1,8 @@ package com.badoo.mvicore.common -import com.badoo.mvicore.common.binder.Binder import com.badoo.mvicore.common.binder.NotNullConnector +import com.badoo.mvicore.common.binder.bind +import com.badoo.mvicore.common.binder.binder import com.badoo.mvicore.common.binder.using import com.badoo.mvicore.common.lifecycle.Lifecycle import kotlin.test.Test @@ -12,7 +13,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink() { - Binder().apply { + binder().apply { bind(source to sink) } @@ -22,7 +23,7 @@ class BinderTest { @Test fun binder_without_lifecycle_does_not_connect_source_and_sink_after_cancel() { - val binder = Binder().apply { + val binder = binder().apply { bind(source to sink) } @@ -34,7 +35,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_using_mapper() { - Binder().apply { + binder().apply { bind(source to sink using { it + 1 }) } @@ -44,7 +45,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_skips_nulls_from_mapper() { - Binder().apply { + binder().apply { bind(source to sink using { if (it % 2 == 0) null else it }) } @@ -57,7 +58,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_using_connector() { val connector = NotNullConnector { it } - Binder().apply { + binder().apply { bind(source to sink using connector) } @@ -68,7 +69,7 @@ class BinderTest { @Test fun binder_with_lifecycle_connects_source_and_sink_when_active() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -81,7 +82,7 @@ class BinderTest { @Test fun binder_with_lifecycle_does_not_connect_source_and_sink_before_active() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -94,7 +95,7 @@ class BinderTest { @Test fun binder_with_lifecycle_disconnect_source_and_sink_after_end() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -109,7 +110,7 @@ class BinderTest { @Test fun binder_with_lifecycle_reconnect_source_and_sink_after_begin() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -126,7 +127,7 @@ class BinderTest { @Test fun binder_with_lifecycle_does_not_reconnect_source_and_sink_after_cancel() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -145,7 +146,7 @@ class BinderTest { val lifecycle = Lifecycle.manual() lifecycle.begin() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -158,7 +159,7 @@ class BinderTest { fun binder_with_lifecycle_does_not_reconnect_on_duplicated_lifecycle_events() { val lifecycle = Lifecycle.manual() - Binder(lifecycle).apply { + binder(lifecycle).apply { bind(source to sink) } @@ -173,13 +174,27 @@ class BinderTest { @Test fun binder_covariant_endpoints_compile_for_pair() { val sink = { it: Any -> /* no-op */ } - Binder().bind(source to sink) + binder().bind(source to sink) } @Test fun binder_covariant_endpoints_compile_for_connection() { val sink = { it: Any -> /* no-op */ } val intToString: (Int) -> String = { it.toString() } - Binder().bind(source to sink using intToString) + binder().bind(source to sink using intToString) + } + + @Test + fun binder_delivers_message_to_all_sinks_on_dispose() { + val binder = binder() + + val sink2 = { it: Int -> binder.cancel() } + + binder.bind(source to sink2) + binder.bind(source to sink) + + source.invoke(0) + + sink.assertValues(0) } } From 5da45f72e917e7d1e34f5712175efc4e455be3eb Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 23 Dec 2019 15:10:40 +0000 Subject: [PATCH 06/31] Add initializer block to binder --- .../com/badoo/mvicore/common/binder/Binder.kt | 41 ++++++++++++++--- .../mvicore/common/binder/NotNullConnector.kt | 14 +----- .../kotlin/com/badoo/mvicore/common/source.kt | 4 ++ .../common/sources/DelayUntilSource.kt | 44 +++++++++++++++++++ .../common/sources/MapNotNullSource.kt | 16 +++++++ .../kotlin/com/badoo/mvicore/common/binder.kt | 12 +++++ 6 files changed, 111 insertions(+), 20 deletions(-) create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 434e086a..222f00ef 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -8,13 +8,14 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END import com.badoo.mvicore.common.source +import com.badoo.mvicore.common.sources.DelayUntilSource abstract class Binder : Cancellable { internal abstract fun connect(connection: Connection) } -fun binder(): Binder = SimpleBinder() -fun binder(lifecycle: Lifecycle): Binder = LifecycleBinder(lifecycle) +fun binder(init: Binder.() -> Unit = { }): Binder = SimpleBinder(init) +fun binder(lifecycle: Lifecycle, init: Binder.() -> Unit = { }): Binder = LifecycleBinder(lifecycle, init) fun Binder.bind(connection: Pair, Sink>) { val (from, to) = connection @@ -25,18 +26,44 @@ fun Binder.bind(connection: Connection) { connect(connection) } -internal class SimpleBinder : Binder() { +internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { private val cancellables = mutableListOf() + + /** + * Stores internal end of every `emitter` connected through binder + * Allows for propagation of events in case emission disposes the binder + * E.g.: + * from -> internalSource -> to // Events from `from` are propagated to `to` + * #dispose() + * from xx internalSource -> to // Remaining events from `internalSource` are propagated to `to` + */ private val fromToInternalSource = mutableMapOf, SimpleSource<*>>() + /** + * Delay events emitted on subscribe until `init` lambda is executed + */ + private val initialized = source(initialValue = false) + + init { + init() + initialized(true) + } + override fun connect(connection: Connection) { val internalSource = getInternalSourceFor(connection.from!!) - val outSource = if (connection.connector != null) { + val transformedSource = if (connection.connector != null) { connection.connector.invoke(internalSource) } else { internalSource as Source } - outSource.connect(connection.to) + + val delayInitialize = if (initialized.value!!) { + transformedSource + } else { + DelayUntilSource(initialized, transformedSource) + } + + delayInitialize.connect(connection.to) } private fun getInternalSourceFor(from: Source): SimpleSource = @@ -51,10 +78,10 @@ internal class SimpleBinder : Binder() { } } -internal class LifecycleBinder(lifecycle: Lifecycle) : Binder() { +internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : Binder() { private var lifecycleActive = false private val cancellables = mutableListOf() - private val innerBinder = SimpleBinder() + private val innerBinder = SimpleBinder(init) private val connections = mutableListOf>() init { diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt index 2d936976..f5b2ace1 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt @@ -1,8 +1,7 @@ package com.badoo.mvicore.common.binder -import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.sources.MapNotNullSource internal class NotNullConnector(private val mapper: (Out) -> In?): Connector { override fun invoke(element: Source): Source { @@ -12,14 +11,3 @@ internal class NotNullConnector(private val mapper: (Out) -> In?): Conn override fun toString(): String = mapper.toString() } - -private class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { - override fun connect(sink: Sink): Cancellable = - delegate.connect { - mapper(it)?.let { sink(it) } - } - - override fun cancel() { - delegate.cancel() - } -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 607ec304..ba844b73 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -1,5 +1,9 @@ package com.badoo.mvicore.common +/** + * NOTE: in conversions from other frameworks you need to override equals and hashCode + * to support binder "emit after dispose" functionality + */ interface Source : Cancellable { fun connect(sink: Sink): Cancellable } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt new file mode 100644 index 00000000..d62dd968 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -0,0 +1,44 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.cancellableOf + +internal class DelayUntilSource( + private val signal: Source, + private val delegate: Source +): Source { + override fun connect(sink: Sink): Cancellable { + val delayedSink = DelayedSink(sink) + val cancelDelay = signal.connect { if (it) { delayedSink.send() } } + val cancelSubscription = delegate.connect(delayedSink) + return cancellableOf { + cancelDelay.cancel() + cancelSubscription.cancel() + } + } + + override fun cancel() { + delegate.cancel() + } + + private class DelayedSink(private val delegate: Sink) : Sink { + private var passThrough = false + private val events = mutableListOf() + + override fun invoke(value: T) { + if (passThrough) { + delegate(value) + } else { + events += value + } + } + + fun send() { + passThrough = true + events.forEach { delegate(it) } + events.clear() + } + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt new file mode 100644 index 00000000..722e338e --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -0,0 +1,16 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source + +internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { + override fun connect(sink: Sink): Cancellable = + delegate.connect { + mapper(it)?.let { sink(it) } + } + + override fun cancel() { + delegate.cancel() + } +} diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 653e3566..2f213c57 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -197,4 +197,16 @@ class BinderTest { sink.assertValues(0) } + + @Test + fun binder_messages_sent_on_initialize_are_not_lost() { + val passThroughSource = source() + val binder = binder { + bind(source to passThroughSource) + source.invoke(0) + bind(passThroughSource to sink) + } + + sink.assertValues(0) + } } From 3dadd578d795d6b22b123f442197e806f4f0c47e Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 23 Dec 2019 15:13:51 +0000 Subject: [PATCH 07/31] Minor rearrangements --- .../com/badoo/mvicore/common/binder/Binder.kt | 16 ++++------------ .../badoo/mvicore/common/binder/BinderExt.kt | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 222f00ef..773443a9 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -10,22 +10,14 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END import com.badoo.mvicore.common.source import com.badoo.mvicore.common.sources.DelayUntilSource +/** + * Establishes connections between [Source] and [Sink] endpoints + * NOTE: binder is not thread safe. All the emissions should happen on single thread (preferably main). + */ abstract class Binder : Cancellable { internal abstract fun connect(connection: Connection) } -fun binder(init: Binder.() -> Unit = { }): Binder = SimpleBinder(init) -fun binder(lifecycle: Lifecycle, init: Binder.() -> Unit = { }): Binder = LifecycleBinder(lifecycle, init) - -fun Binder.bind(connection: Pair, Sink>) { - val (from, to) = connection - connect(Connection(from = from, to = to)) -} - -fun Binder.bind(connection: Connection) { - connect(connection) -} - internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { private val cancellables = mutableListOf() diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt new file mode 100644 index 00000000..cadbc453 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt @@ -0,0 +1,17 @@ +package com.badoo.mvicore.common.binder + +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.lifecycle.Lifecycle + +fun binder(init: Binder.() -> Unit = { }): Binder = SimpleBinder(init) +fun binder(lifecycle: Lifecycle, init: Binder.() -> Unit = { }): Binder = LifecycleBinder(lifecycle, init) + +fun Binder.bind(connection: Pair, Sink>) { + val (from, to) = connection + connect(Connection(from = from, to = to)) +} + +fun Binder.bind(connection: Connection) { + connect(connection) +} From be42e1254cb0624239eb253ffcfc39238ff89032 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 27 Dec 2019 22:41:43 +0000 Subject: [PATCH 08/31] Add js and macos targets --- build.gradle | 5 +- mvicore-base/build.gradle | 38 +++++++++++++-- .../com/badoo/mvicore/common/binder/Binder.kt | 16 +++++-- .../com/badoo/mvicore/common/cancellable.kt | 39 ++++++++++++++-- .../mvicore/common/feature/BaseFeature.kt | 18 +++++--- .../mvicore/common/feature/ReducerFeature.kt | 46 +++++++++++++++---- .../common/middleware/StandaloneMiddleware.kt | 2 + .../kotlin/com/badoo/mvicore/common/source.kt | 11 ++++- .../common/sources/DelayUntilSource.kt | 3 ++ .../common/sources/MapNotNullSource.kt | 3 ++ .../badoo/mvicore/common/reducerFeature.kt | 5 ++ .../com/badoo/mvicore/common/baseNative.kt | 23 ++++++++++ 12 files changed, 176 insertions(+), 33 deletions(-) create mode 100644 mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt create mode 100644 mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt diff --git a/build.gradle b/build.gradle index 22166e94..957f6ba3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlinVersion = '1.3.50' + ext.kotlinVersion = '1.3.61' repositories { google() @@ -119,6 +119,3 @@ allprojects { } } -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/mvicore-base/build.gradle b/mvicore-base/build.gradle index 02197675..4f3a164f 100644 --- a/mvicore-base/build.gradle +++ b/mvicore-base/build.gradle @@ -4,8 +4,25 @@ plugins { kotlin { jvm() - js() - //TODO native + js("js") { + browser { + testTask { + useKarma { + useChromeHeadless() + } + } + } + nodejs { + testTask { + useCommonJs() + } + } + } + macosX64 { + binaries { + framework() + } + } sourceSets { commonMain { @@ -24,11 +41,26 @@ kotlin { implementation kotlin('stdlib-js') } } + jsTest { + dependencies { + implementation kotlin('test-js') + } + } jvmMain { dependencies { implementation kotlin('stdlib') + } + } + jvmTest { + dependencies { implementation kotlin('test-junit') } } - } + nativeMain { dependsOn commonMain } + // Empty source set is required in order to have native tests task + nativeTest { } + + macosX64Main { dependsOn nativeMain } + macosX64Test { dependsOn nativeTest } + } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 773443a9..8cbb24ef 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -1,6 +1,7 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.SimpleSource import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source @@ -19,7 +20,7 @@ abstract class Binder : Cancellable { } internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { - private val cancellables = mutableListOf() + private val cancellables = CompositeCancellable() /** * Stores internal end of every `emitter` connected through binder @@ -64,15 +65,17 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { } as SimpleSource override fun cancel() { - cancellables.forEach { it.cancel() } + cancellables.cancel() fromToInternalSource.clear() - cancellables.clear() } + + override val isCancelled: Boolean + get() = cancellables.isCancelled } internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : Binder() { private var lifecycleActive = false - private val cancellables = mutableListOf() + private val cancellables = CompositeCancellable() private val innerBinder = SimpleBinder(init) private val connections = mutableListOf>() @@ -108,8 +111,11 @@ internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : } override fun cancel() { - cancellables.forEach { it.cancel() } + cancellables.cancel() connections.clear() } + override val isCancelled: Boolean + get() = cancellables.isCancelled + } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt index 0c18ee75..884b3fd4 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -2,14 +2,17 @@ package com.badoo.mvicore.common interface Cancellable { fun cancel() + val isCancelled: Boolean } -fun cancellableOf(onCancel: () -> Unit): Cancellable = CancellableImpl(onCancel) +fun cancellableOf(onCancel: Cancellable.() -> Unit): Cancellable = CancellableImpl(onCancel) -private class CancellableImpl(private val onCancel: () -> Unit): Cancellable { +private class CancellableImpl(private val onCancel: Cancellable.() -> Unit): Cancellable { private var isCancelledRef = AtomicRef(false) - val isCancelled: Boolean = isCancelledRef.get() + override val isCancelled: Boolean + get() = isCancelledRef.get() + override fun cancel() { if (!isCancelled) { isCancelledRef.update { true } @@ -17,3 +20,33 @@ private class CancellableImpl(private val onCancel: () -> Unit): Cancellable { } } } + +class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { + private val cancellableList = mutableListOf(*cancellables) + private val internalCancellable = CancellableImpl { + cancellableList.forEach { it.cancel() } + cancellableList.clear() + } + + override val isCancelled: Boolean + get() = internalCancellable.isCancelled + + operator fun plusAssign(cancellable: Cancellable?) { + if (!isCancelled) { + cancellableList.removeAll { it.isCancelled } + cancellable?.let { + cancellableList += cancellable + } + } + } + + operator fun minusAssign(cancellable: Cancellable?) { + if (!isCancelled) { + cancellableList.remove(cancellable) + } + } + + override fun cancel() { + internalCancellable.cancel() + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index 305c8801..2c31935c 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -1,5 +1,6 @@ package com.badoo.mvicore.common.feature +import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.element.Actor @@ -12,25 +13,25 @@ import com.badoo.mvicore.common.source open class BaseFeature ( initialState: State, - private val bootstrapper: Bootstrapper? = null, private val wishToAction: (Wish) -> Action, private val actor: Actor, private val reducer: Reducer, + private val bootstrapper: Bootstrapper? = null, private val newsPublisher: NewsPublisher? = null, private val postProcessor: PostProcessor? = null ): Feature { private val actionSource = source() - internal val effectSource = source() private val stateSource = source(initialState) private val newsSource = source() + private val cancellables = CompositeCancellable() init { - bootstrapper?.invoke()?.connect(actionSource) - actionSource.connect { action -> + cancellables += bootstrapper?.invoke()?.connect(actionSource) + cancellables += actionSource.connect { action -> val oldState = state - actor.invoke(oldState, action) + cancellables += actor.invoke(oldState, action) .connect { effect -> - val newState = reducer(this.state, effect) + val newState = reducer(state, effect) stateSource(newState) newsPublisher?.invoke(oldState, action, effect, newState)?.let { newsSource(it) @@ -47,8 +48,11 @@ open class BaseFeature) = stateSource.connect(sink) + override val isCancelled: Boolean + get() = cancellables.isCancelled + override fun cancel() { - // TODO + cancellables.cancel() } val state: State diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt index 37538279..988ff270 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt @@ -1,12 +1,38 @@ package com.badoo.mvicore.common.feature -//open class ReducerFeature( -// initialState: State, -// bootstrapper: Bootstrapper? = null, -// private val reducer: Reducer, -// private val newsPublisher: SimpleNewsPublisher? = null -//) : ActorReducerFeature( -// actor = { } -//) - -typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.element.Actor +import com.badoo.mvicore.common.element.Bootstrapper +import com.badoo.mvicore.common.element.NewsPublisher +import com.badoo.mvicore.common.element.Reducer +import com.badoo.mvicore.common.source + +open class ReducerFeature( + initialState: State, + bootstrapper: Bootstrapper? = null, + reducer: Reducer, + newsPublisher: SimpleNewsPublisher? = null +) : ActorReducerFeature( + initialState = initialState, + actor = PassthroughActor(), + bootstrapper = bootstrapper, + reducer = reducer, + newsPublisher = newsPublisher?.let { NoEffectNewsPublisher(it) } +) { + private class PassthroughActor : Actor { + override fun invoke(state: State, wish: Wish): Source = + source().apply { + invoke(wish) + cancel() + } + } + + private class NoEffectNewsPublisher( + private val simpleNewsPublisher: SimpleNewsPublisher + ) : NewsPublisher { + override fun invoke(old: State, action: Wish, effect: Wish, new: State): News? = + simpleNewsPublisher(old, action, new) + } +} + +typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt index 888a1ef5..d69b4def 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt @@ -35,6 +35,8 @@ internal class StandaloneMiddleware( onComplete(this.connection) } + override val isCancelled: Boolean = false + private fun assertSame(connection: Connection) { if (connection !== this.connection) { throw IllegalStateException("Middleware was initialised in standalone mode, can't accept other connections") diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index ba844b73..709bc122 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -16,6 +16,9 @@ fun source(): SimpleSource = class SimpleSource(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { private val sinks = AtomicRef(listOf>()) + private val internalCancellable = CompositeCancellable( + cancellableOf { sinks.update { emptyList() } } + ) var value: T? = initialValue private set @@ -35,10 +38,16 @@ class SimpleSource(initialValue: T?, private val emitOnConnect: Boolean): Sou return cancellableOf { sinks.update { it - sink } + internalCancellable -= this + }.also { + internalCancellable += it } } + override val isCancelled: Boolean + get() = internalCancellable.isCancelled + override fun cancel() { - sinks.update { emptyList() } + internalCancellable.cancel() } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt index d62dd968..1e8e7adc 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -23,6 +23,9 @@ internal class DelayUntilSource( delegate.cancel() } + override val isCancelled: Boolean + get() = delegate.isCancelled + private class DelayedSink(private val delegate: Sink) : Sink { private var passThrough = false private val events = mutableListOf() diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt index 722e338e..82fee633 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -13,4 +13,7 @@ internal class MapNotNullSource(private val delegate: Source, priv override fun cancel() { delegate.cancel() } + + override val isCancelled: Boolean + get() = delegate.isCancelled } diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt new file mode 100644 index 00000000..edf4a5d9 --- /dev/null +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt @@ -0,0 +1,5 @@ +package com.badoo.mvicore.common + +class ReducerFeatureTest { + +} diff --git a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt new file mode 100644 index 00000000..0ca642cb --- /dev/null +++ b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt @@ -0,0 +1,23 @@ +package com.badoo.mvicore.common + +import kotlin.native.concurrent.FreezableAtomicReference +import kotlin.native.concurrent.freeze +import kotlin.native.concurrent.isFrozen + +actual class AtomicRef actual constructor(initialValue: V) { + + private val delegate = FreezableAtomicReference(initialValue) + + actual fun get(): V = delegate.value + + actual fun compareAndSet(expect: V, update: V): Boolean = + delegate.compareAndSet(expect, update.freezeIfNeeded()) + + private fun V.freezeIfNeeded(): V { + if (delegate.isFrozen) { + freeze() + } + + return this + } +} From 6933bb572d6851c04274ce8c700cd3cecbb18e6e Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 27 Dec 2019 23:14:53 +0000 Subject: [PATCH 09/31] Add tests for reducer feature --- .../mvicore/common/feature/BaseFeature.kt | 2 +- .../mvicore/common/feature/ReducerFeature.kt | 5 +- .../kotlin/com/badoo/mvicore/common/binder.kt | 8 +- .../badoo/mvicore/common/reducerFeature.kt | 88 +++++++++++++++++++ 4 files changed, 94 insertions(+), 9 deletions(-) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index 2c31935c..1284b6b8 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -26,7 +26,6 @@ open class BaseFeature val oldState = state cancellables += actor.invoke(oldState, action) @@ -39,6 +38,7 @@ open class BaseFeature( ) { private class PassthroughActor : Actor { override fun invoke(state: State, wish: Wish): Source = - source().apply { - invoke(wish) - cancel() - } + source(wish) } private class NoEffectNewsPublisher( diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 2f213c57..41575805 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -173,13 +173,13 @@ class BinderTest { @Test fun binder_covariant_endpoints_compile_for_pair() { - val sink = { it: Any -> /* no-op */ } + val sink = { _: Any -> /* no-op */ } binder().bind(source to sink) } @Test fun binder_covariant_endpoints_compile_for_connection() { - val sink = { it: Any -> /* no-op */ } + val sink = { _: Any -> /* no-op */ } val intToString: (Int) -> String = { it.toString() } binder().bind(source to sink using intToString) } @@ -188,7 +188,7 @@ class BinderTest { fun binder_delivers_message_to_all_sinks_on_dispose() { val binder = binder() - val sink2 = { it: Int -> binder.cancel() } + val sink2 = { _: Int -> binder.cancel() } binder.bind(source to sink2) binder.bind(source to sink) @@ -201,7 +201,7 @@ class BinderTest { @Test fun binder_messages_sent_on_initialize_are_not_lost() { val passThroughSource = source() - val binder = binder { + binder { bind(source to passThroughSource) source.invoke(0) bind(passThroughSource to sink) diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt index edf4a5d9..555cdf6a 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt @@ -1,5 +1,93 @@ package com.badoo.mvicore.common +import com.badoo.mvicore.common.feature.ReducerFeature +import kotlin.test.Test + class ReducerFeatureTest { + @Test + fun reducer_feature_emits_initial_state_on_connect() { + val feature = TestReducerFeature() + val sink = TestSink() + + feature.connect(sink) + + sink.assertValues("") + } + + @Test + fun reducer_feature_emits_new_state_on_new_wish() { + val feature = TestReducerFeature() + val sink = TestSink() + + feature.connect(sink) + + feature.invoke(0) + sink.assertValues("", "0") + } + + @Test + fun reducer_feature_emits_new_state_on_new_wish_2() { + val feature = TestReducerFeature() + val sink = TestSink() + + feature.connect(sink) + + feature.invoke(0) + feature.invoke(1) + sink.assertValues("", "0", "01") + } + + @Test + fun reducer_feature_emits_new_state_on_bootstrapper_action() { + val feature = TestReducerFeatureWBootstrapper() + val sink = TestSink() + + feature.connect(sink) + + feature.invoke(1) + sink.assertValues("0", "01") + } + + @Test + fun reducer_feature_emits_news_on_wish() { + val feature = TestReducerFeatureWNews() + val sink = TestSink() + + feature.news.connect(sink) + + feature.invoke(1) + feature.invoke(1) + sink.assertValues(1, 2) + } + + class TestReducerFeature(initialState: String = ""): ReducerFeature( + initialState = initialState, + reducer = { state, wish -> + state + wish.toString() + } + ) + + class TestReducerFeatureWBootstrapper(initialState: String = ""): ReducerFeature( + initialState = initialState, + bootstrapper = { + source(initialValue = 0) + }, + reducer = { state, wish -> + state + wish.toString() + } + ) + class TestReducerFeatureWNews(initialState: String = ""): ReducerFeature( + initialState = initialState, + reducer = { state, wish -> + state + wish.toString() + }, + newsPublisher = { _: String, _: Int, state: String -> + if (state.isNotEmpty()) { + state.length + } else { + null + } + } + ) } From fff16bdafb667c389df71d0cee898074ec916e6f Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 28 Dec 2019 11:34:12 +0000 Subject: [PATCH 10/31] Migrate to interfaces instead of function supertypes to make js compile --- mvicore-base/build.gradle | 2 +- .../com/badoo/mvicore/common/binder/Binder.kt | 11 ++++---- .../badoo/mvicore/common/binder/BinderExt.kt | 2 +- .../badoo/mvicore/common/binder/Connector.kt | 4 ++- .../mvicore/common/binder/NotNullConnector.kt | 4 +-- .../com/badoo/mvicore/common/cancellable.kt | 28 +++++++++++++------ .../badoo/mvicore/common/element/Feature.kt | 9 ++++++ .../badoo/mvicore/common/element/elements.kt | 18 ------------ .../mvicore/common/element/featureElements.kt | 23 +++++++++++++++ .../mvicore/common/feature/BaseFeature.kt | 1 + .../mvicore/common/feature/ReducerFeature.kt | 4 +-- .../mvicore/common/lifecycle/lifecycle.kt | 4 +-- .../kotlin/com/badoo/mvicore/common/sink.kt | 10 ++++++- .../kotlin/com/badoo/mvicore/common/source.kt | 13 +++++---- .../common/sources/DelayUntilSource.kt | 1 + .../common/sources/MapNotNullSource.kt | 1 + .../mvicore/common/sources/ValueSource.kt | 27 ++++++++++++++++++ .../kotlin/com/badoo/mvicore/common/binder.kt | 6 ++-- .../badoo/mvicore/common/reducerFeature.kt | 19 +++++++++---- .../com/badoo/mvicore/common/baseNative.kt | 4 +-- 20 files changed, 134 insertions(+), 57 deletions(-) create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt create mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt diff --git a/mvicore-base/build.gradle b/mvicore-base/build.gradle index 4f3a164f..e8bb2d2d 100644 --- a/mvicore-base/build.gradle +++ b/mvicore-base/build.gradle @@ -4,7 +4,7 @@ plugins { kotlin { jvm() - js("js") { + js { browser { testTask { useKarma { diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 8cbb24ef..b86d8c0f 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -2,9 +2,10 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.CompositeCancellable -import com.badoo.mvicore.common.SimpleSource import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.SourceImpl +import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.lifecycle.Lifecycle import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END @@ -30,7 +31,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { * #dispose() * from xx internalSource -> to // Remaining events from `internalSource` are propagated to `to` */ - private val fromToInternalSource = mutableMapOf, SimpleSource<*>>() + private val fromToInternalSource = mutableMapOf, SourceImpl<*>>() /** * Delay events emitted on subscribe until `init` lambda is executed @@ -56,13 +57,13 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { DelayUntilSource(initialized, transformedSource) } - delayInitialize.connect(connection.to) + delayInitialize.connect(connection.to as Sink) } - private fun getInternalSourceFor(from: Source): SimpleSource = + private fun getInternalSourceFor(from: Source): SourceImpl = fromToInternalSource.getOrPut(from) { source().also { cancellables += from.connect(it) } - } as SimpleSource + } as SourceImpl override fun cancel() { cancellables.cancel() diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt index cadbc453..30ed0dbf 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt @@ -7,7 +7,7 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle fun binder(init: Binder.() -> Unit = { }): Binder = SimpleBinder(init) fun binder(lifecycle: Lifecycle, init: Binder.() -> Unit = { }): Binder = LifecycleBinder(lifecycle, init) -fun Binder.bind(connection: Pair, Sink>) { +fun Binder.bind(connection: Pair, Sink>) { val (from, to) = connection connect(Connection(from = from, to = to)) } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt index 70906dd9..02982459 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt @@ -2,4 +2,6 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Source -interface Connector: (Source) -> Source +interface Connector { + operator fun invoke(source: Source): Source +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt index f5b2ace1..d3e079e4 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt @@ -4,8 +4,8 @@ import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.sources.MapNotNullSource internal class NotNullConnector(private val mapper: (Out) -> In?): Connector { - override fun invoke(element: Source): Source { - return MapNotNullSource(element, mapper) + override fun invoke(source: Source): Source { + return MapNotNullSource(source, mapper) } override fun toString(): String = diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt index 884b3fd4..40ad6b2c 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -6,6 +6,7 @@ interface Cancellable { } fun cancellableOf(onCancel: Cancellable.() -> Unit): Cancellable = CancellableImpl(onCancel) +fun cancelled(): Cancellable = CancelledCancellable() private class CancellableImpl(private val onCancel: Cancellable.() -> Unit): Cancellable { private var isCancelledRef = AtomicRef(false) @@ -21,28 +22,37 @@ private class CancellableImpl(private val onCancel: Cancellable.() -> Unit): Can } } +private class CancelledCancellable : Cancellable { + override val isCancelled: Boolean = false + override fun cancel() { + // no-op + } +} + class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { - private val cancellableList = mutableListOf(*cancellables) + private val cancellableListRef = AtomicRef(setOf(*cancellables)) private val internalCancellable = CancellableImpl { - cancellableList.forEach { it.cancel() } - cancellableList.clear() + val list = cancellableListRef.get() + list.forEach { it.cancel() } + cancellableListRef.update { emptySet() } } override val isCancelled: Boolean get() = internalCancellable.isCancelled operator fun plusAssign(cancellable: Cancellable?) { - if (!isCancelled) { - cancellableList.removeAll { it.isCancelled } - cancellable?.let { - cancellableList += cancellable + if (!isCancelled && cancellable != null) { + cancellableListRef.update { + (it + cancellable).filterNotTo(hashSetOf()) { it.isCancelled } } } } operator fun minusAssign(cancellable: Cancellable?) { - if (!isCancelled) { - cancellableList.remove(cancellable) + if (!isCancelled && cancellable != null) { + cancellableListRef.update { + it - cancellable + } } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt new file mode 100644 index 00000000..b20ea897 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt @@ -0,0 +1,9 @@ +package com.badoo.mvicore.common.element + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source + +interface Feature: Sink, Source, Cancellable { + val news: Source +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt deleted file mode 100644 index 90196267..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/elements.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.badoo.mvicore.common.element - -import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Source - -typealias Bootstrapper = () -> Source - -typealias Actor = (state: State, wish: Wish) -> Source - -typealias Reducer = (state: State, effect: Effect) -> State - -typealias NewsPublisher = (old: State, action: Action, effect: Effect, new: State) -> News? - -typealias PostProcessor = (old: State, action: Action, effect: Effect, new: State) -> Action? - -interface Feature: (Wish) -> Unit, Source, Cancellable { - val news: Source -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt new file mode 100644 index 00000000..57cd104c --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt @@ -0,0 +1,23 @@ +package com.badoo.mvicore.common.element + +import com.badoo.mvicore.common.Source + +interface Bootstrapper { + operator fun invoke(): Source +} + +interface Actor { + operator fun invoke(state: State, wish: Wish): Source +} + +interface Reducer { + operator fun invoke(state: State, effect: Effect): State +} + +interface NewsPublisher { + operator fun invoke(old: State, action: Action, effect: Effect, new: State): News? +} + +interface PostProcessor { + operator fun invoke(old: State, action: Action, effect: Effect, new: State): Action? +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index 1284b6b8..6da0cd34 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -3,6 +3,7 @@ package com.badoo.mvicore.common.feature import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.element.Actor import com.badoo.mvicore.common.element.Bootstrapper import com.badoo.mvicore.common.element.Feature diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt index 28324468..057620fe 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt @@ -5,7 +5,7 @@ import com.badoo.mvicore.common.element.Actor import com.badoo.mvicore.common.element.Bootstrapper import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.Reducer -import com.badoo.mvicore.common.source +import com.badoo.mvicore.common.sources.ValueSource open class ReducerFeature( initialState: State, @@ -21,7 +21,7 @@ open class ReducerFeature( ) { private class PassthroughActor : Actor { override fun invoke(state: State, wish: Wish): Source = - source(wish) + ValueSource(wish) } private class NoEffectNewsPublisher( diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt index e178d27b..f35ecf86 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt @@ -1,7 +1,7 @@ package com.badoo.mvicore.common.lifecycle -import com.badoo.mvicore.common.SimpleSource import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.SourceImpl import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END @@ -16,7 +16,7 @@ interface Lifecycle : Source { } class ManualLifecycle( - private val source: SimpleSource = SimpleSource(initialValue = null, emitOnConnect = true) + private val source: SourceImpl = SourceImpl(initialValue = null, emitOnConnect = true) ) : Lifecycle, Source by source { fun begin() { source(BEGIN) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt index b1283313..613e5d8a 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt @@ -1,3 +1,11 @@ package com.badoo.mvicore.common -typealias Sink = (value: T) -> Unit +interface Sink { + operator fun invoke(value: T) +} + +fun sinkOf(action: (T) -> Unit) = object : Sink { + override fun invoke(value: T) { + action(value) + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 709bc122..78a53b26 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -8,13 +8,16 @@ interface Source : Cancellable { fun connect(sink: Sink): Cancellable } -fun source(initialValue: T): SimpleSource = - SimpleSource(initialValue, true) +fun source(initialValue: T): SourceImpl = + SourceImpl(initialValue, true) -fun source(): SimpleSource = - SimpleSource(null, false) +fun source(): SourceImpl = + SourceImpl(null, false) -class SimpleSource(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { +fun Source.connect(action: (T) -> Unit) = + connect(sinkOf(action)) + +class SourceImpl(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { private val sinks = AtomicRef(listOf>()) private val internalCancellable = CompositeCancellable( cancellableOf { sinks.update { emptyList() } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt index 1e8e7adc..48fba6e3 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -4,6 +4,7 @@ import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.cancellableOf +import com.badoo.mvicore.common.connect internal class DelayUntilSource( private val signal: Source, diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt index 82fee633..42f3d276 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -3,6 +3,7 @@ package com.badoo.mvicore.common.sources import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.connect internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { override fun connect(sink: Sink): Cancellable = diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt new file mode 100644 index 00000000..07ccad52 --- /dev/null +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt @@ -0,0 +1,27 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.cancellableOf +import com.badoo.mvicore.common.cancelled + +internal class ValueSource(vararg values: T) : Source { + private val valuesToEmit = values + private val cancellable = cancellableOf { } + + override fun connect(sink: Sink): Cancellable { + if (!cancellable.isCancelled) { + valuesToEmit.forEach { sink(it) } + } + return cancelled() + } + + override fun cancel() { + cancellable.cancel() + } + + override val isCancelled: Boolean + get() = cancellable.isCancelled +} + diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 41575805..be4b3ba6 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -173,13 +173,13 @@ class BinderTest { @Test fun binder_covariant_endpoints_compile_for_pair() { - val sink = { _: Any -> /* no-op */ } + val sink = sinkOf { /* no-op */ } binder().bind(source to sink) } @Test fun binder_covariant_endpoints_compile_for_connection() { - val sink = { _: Any -> /* no-op */ } + val sink = sinkOf { _: Any -> /* no-op */ } val intToString: (Int) -> String = { it.toString() } binder().bind(source to sink using intToString) } @@ -188,7 +188,7 @@ class BinderTest { fun binder_delivers_message_to_all_sinks_on_dispose() { val binder = binder() - val sink2 = { _: Int -> binder.cancel() } + val sink2 = sinkOf { _: Int -> binder.cancel() } binder.bind(source to sink2) binder.bind(source to sink) diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt index 555cdf6a..78396bac 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt @@ -1,6 +1,9 @@ package com.badoo.mvicore.common +import com.badoo.mvicore.common.element.Bootstrapper +import com.badoo.mvicore.common.element.Reducer import com.badoo.mvicore.common.feature.ReducerFeature +import com.badoo.mvicore.common.sources.ValueSource import kotlin.test.Test class ReducerFeatureTest { @@ -62,24 +65,25 @@ class ReducerFeatureTest { class TestReducerFeature(initialState: String = ""): ReducerFeature( initialState = initialState, - reducer = { state, wish -> + reducer = reducer { state, wish -> state + wish.toString() } ) class TestReducerFeatureWBootstrapper(initialState: String = ""): ReducerFeature( initialState = initialState, - bootstrapper = { - source(initialValue = 0) + bootstrapper = object : Bootstrapper { + override fun invoke(): Source = + ValueSource(0) }, - reducer = { state, wish -> + reducer = reducer { state, wish -> state + wish.toString() } ) class TestReducerFeatureWNews(initialState: String = ""): ReducerFeature( initialState = initialState, - reducer = { state, wish -> + reducer = reducer { state, wish -> state + wish.toString() }, newsPublisher = { _: String, _: Int, state: String -> @@ -91,3 +95,8 @@ class ReducerFeatureTest { } ) } + +fun reducer(block: (state: State, effect: Effect) -> State) = + object : Reducer { + override fun invoke(state: State, effect: Effect): State = block(state, effect) + } diff --git a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt index 0ca642cb..de1c6fae 100644 --- a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt +++ b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt @@ -11,9 +11,9 @@ actual class AtomicRef actual constructor(initialValue: V) { actual fun get(): V = delegate.value actual fun compareAndSet(expect: V, update: V): Boolean = - delegate.compareAndSet(expect, update.freezeIfNeeded()) + delegate.compareAndSet(expect, update.freezeIfFrozen()) - private fun V.freezeIfNeeded(): V { + private fun V.freezeIfFrozen(): V { if (delegate.isFrozen) { freeze() } From 876d93e29a9968f1437b00e5900654ba26feacfc Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 30 Dec 2019 02:49:26 +0000 Subject: [PATCH 11/31] Rx compat module --- mvicore-base/build.gradle | 4 + .../mvicore/common/element/featureElements.kt | 4 +- .../mvicore/common/feature/BaseFeature.kt | 3 +- .../common/{element => feature}/Feature.kt | 2 +- .../kotlin/com/badoo/mvicore/common/source.kt | 20 ++--- .../common/sources/DelayUntilSource.kt | 14 +-- .../common/sources/MapNotNullSource.kt | 12 +-- .../mvicore/common/sources/ValueSource.kt | 12 +-- .../kotlin/com/badoo/mvicore/common/binder.kt | 4 +- mvicore-rx/.gitignore | 1 + mvicore-rx/build.gradle | 57 ++++++++++++ .../main/java/com/badoo/mvicore/rx/Sink.kt | 10 +++ .../main/java/com/badoo/mvicore/rx/Source.kt | 53 ++++++++++++ .../com/badoo/mvicore/rx/binder/Binder.kt | 20 +++++ .../com/badoo/mvicore/rx/binder/Connector.kt | 19 ++++ .../mvicore/rx/element/featureElements.kt | 23 +++++ .../mvicore/rx/feature/ActorReducerFeature.kt | 21 +++++ .../badoo/mvicore/rx/feature/BaseFeature.kt | 86 +++++++++++++++++++ .../com/badoo/mvicore/rx/feature/Feature.kt | 9 ++ .../mvicore/rx/feature/ReducerFeature.kt | 34 ++++++++ settings.gradle | 1 + 21 files changed, 370 insertions(+), 39 deletions(-) rename mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/{element => feature}/Feature.kt (84%) create mode 100644 mvicore-rx/.gitignore create mode 100644 mvicore-rx/build.gradle create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ActorReducerFeature.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt diff --git a/mvicore-base/build.gradle b/mvicore-base/build.gradle index e8bb2d2d..9ddf32da 100644 --- a/mvicore-base/build.gradle +++ b/mvicore-base/build.gradle @@ -24,6 +24,10 @@ kotlin { } } + targets { + fromPreset(presets.macosX64, 'native') + } + sourceSets { commonMain { dependencies { diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt index 57cd104c..bcd6c5e7 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt @@ -6,8 +6,8 @@ interface Bootstrapper { operator fun invoke(): Source } -interface Actor { - operator fun invoke(state: State, wish: Wish): Source +interface Actor { + operator fun invoke(state: State, action: Action): Source } interface Reducer { diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index 6da0cd34..1de17792 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -6,13 +6,12 @@ import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.element.Actor import com.badoo.mvicore.common.element.Bootstrapper -import com.badoo.mvicore.common.element.Feature import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.PostProcessor import com.badoo.mvicore.common.element.Reducer import com.badoo.mvicore.common.source -open class BaseFeature ( +abstract class BaseFeature ( initialState: State, private val wishToAction: (Wish) -> Action, private val actor: Actor, diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt similarity index 84% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt rename to mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt index b20ea897..7cc9b46c 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/Feature.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt @@ -1,4 +1,4 @@ -package com.badoo.mvicore.common.element +package com.badoo.mvicore.common.feature import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 78a53b26..b1e526c1 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -4,7 +4,7 @@ package com.badoo.mvicore.common * NOTE: in conversions from other frameworks you need to override equals and hashCode * to support binder "emit after dispose" functionality */ -interface Source : Cancellable { +interface Source { fun connect(sink: Sink): Cancellable } @@ -19,9 +19,6 @@ fun Source.connect(action: (T) -> Unit) = class SourceImpl(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { private val sinks = AtomicRef(listOf>()) - private val internalCancellable = CompositeCancellable( - cancellableOf { sinks.update { emptyList() } } - ) var value: T? = initialValue private set @@ -41,16 +38,13 @@ class SourceImpl(initialValue: T?, private val emitOnConnect: Boolean): Sourc return cancellableOf { sinks.update { it - sink } - internalCancellable -= this - }.also { - internalCancellable += it } } - override val isCancelled: Boolean - get() = internalCancellable.isCancelled - - override fun cancel() { - internalCancellable.cancel() - } +// override val isCancelled: Boolean +// get() = internalCancellable.isCancelled +// +// override fun cancel() { +// internalCancellable.cancel() +// } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt index 48fba6e3..0c7d2860 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -19,13 +19,13 @@ internal class DelayUntilSource( cancelSubscription.cancel() } } - - override fun cancel() { - delegate.cancel() - } - - override val isCancelled: Boolean - get() = delegate.isCancelled +// +// override fun cancel() { +// delegate.cancel() +// } +// +// override val isCancelled: Boolean +// get() = delegate.isCancelled private class DelayedSink(private val delegate: Sink) : Sink { private var passThrough = false diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt index 42f3d276..cf076a1a 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -11,10 +11,10 @@ internal class MapNotNullSource(private val delegate: Source, priv mapper(it)?.let { sink(it) } } - override fun cancel() { - delegate.cancel() - } - - override val isCancelled: Boolean - get() = delegate.isCancelled +// override fun cancel() { +// delegate.cancel() +// } +// +// override val isCancelled: Boolean +// get() = delegate.isCancelled } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt index 07ccad52..7a044156 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt @@ -17,11 +17,11 @@ internal class ValueSource(vararg values: T) : Source { return cancelled() } - override fun cancel() { - cancellable.cancel() - } - - override val isCancelled: Boolean - get() = cancellable.isCancelled +// override fun cancel() { +// cancellable.cancel() +// } +// +// override val isCancelled: Boolean +// get() = cancellable.isCancelled } diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index be4b3ba6..45d8bab6 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -127,14 +127,14 @@ class BinderTest { @Test fun binder_with_lifecycle_does_not_reconnect_source_and_sink_after_cancel() { val lifecycle = Lifecycle.manual() - binder(lifecycle).apply { + val binder = binder(lifecycle).apply { bind(source to sink) } lifecycle.begin() source.invoke(0) lifecycle.end() - lifecycle.cancel() + binder.cancel() lifecycle.begin() source.invoke(2) diff --git a/mvicore-rx/.gitignore b/mvicore-rx/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/mvicore-rx/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mvicore-rx/build.gradle b/mvicore-rx/build.gradle new file mode 100644 index 00000000..9386af27 --- /dev/null +++ b/mvicore-rx/build.gradle @@ -0,0 +1,57 @@ +apply plugin: 'java' +apply plugin: 'maven' +apply plugin: 'kotlin' +apply plugin: 'org.jetbrains.dokka' + +group = 'com.github.badoo.mvicore' + +dependencies { + def deps = rootProject.ext.deps + + implementation deps('io.reactivex.rxjava2:rxjava') + implementation project(':mvicore-base') + implementation deps('io.reactivex.rxjava2:rxkotlin') + implementation deps("org.jetbrains.kotlin:kotlin-stdlib-jdk7") + + testImplementation deps('junit:junit') + testImplementation deps('org.jetbrains.kotlin:kotlin-test-junit') + testImplementation deps('org.amshove.kluent:kluent') + testImplementation deps('com.nhaarman:mockito-kotlin') +} + +sourceCompatibility = "1.7" +targetCompatibility = "1.7" + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion" + } +} + +repositories { + jcenter() +} + +sourceSets { + main { + java {} + } +} + +task packageSources(type: Jar, dependsOn: 'classes') { + classifier = 'sources' + from sourceSets.main.allSource +} + +task packageJavadoc(type: Jar, dependsOn: javadoc) { + from javadoc.outputDirectory + classifier = 'javadoc' +} + +artifacts { + archives packageSources + archives packageJavadoc +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt new file mode 100644 index 00000000..6f55f2fd --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt @@ -0,0 +1,10 @@ +package com.badoo.mvicore.rx + +import com.badoo.mvicore.common.Sink +import io.reactivex.functions.Consumer + +fun Consumer.toSink() = object : Sink { + override fun invoke(value: T) { + accept(value) + } +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt new file mode 100644 index 00000000..0ec21076 --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt @@ -0,0 +1,53 @@ +package com.badoo.mvicore.rx + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.cancellableOf +import com.badoo.mvicore.common.connect +import io.reactivex.Observable +import io.reactivex.ObservableSource +import io.reactivex.Observer +import io.reactivex.disposables.Disposables + +fun ObservableSource.toSource(): Source = SourceAdapter(Observable.wrap(this)) + +fun Source.toObservable(): ObservableSource = ObservableAdapter(this) + +internal class SourceAdapter(internal val delegate: Observable): Source { + private val disposable = Disposables.empty() + + override fun connect(sink: Sink): Cancellable { + val disposable = delegate.subscribe { sink(it) } + return cancellableOf { + disposable.dispose() + } + } + + override fun cancel() { + disposable.dispose() + } + + override val isCancelled: Boolean + get() = disposable.isDisposed + + override fun equals(other: Any?): Boolean = + other is SourceAdapter<*> && super.equals(other.delegate) + + override fun hashCode(): Int = + delegate.hashCode() + + override fun toString(): String = + delegate.toString() +} + +internal class ObservableAdapter(private val delegate: Source): ObservableSource { + override fun subscribe(observer: Observer) { + try { + val cancellable = delegate.connect { observer.onNext(it) } + observer.onSubscribe(Disposables.fromAction { cancellable.cancel() }) + } catch (e: Exception) { + observer.onError(e) + } + } +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt new file mode 100644 index 00000000..3f537cac --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt @@ -0,0 +1,20 @@ +package com.badoo.mvicore.rx.binder + +import com.badoo.mvicore.common.binder.Binder +import com.badoo.mvicore.common.binder.Connection +import com.badoo.mvicore.common.binder.bind +import com.badoo.mvicore.common.binder.using +import com.badoo.mvicore.rx.toSink +import com.badoo.mvicore.rx.toSource +import io.reactivex.ObservableSource +import io.reactivex.functions.Consumer + +fun Binder.bind(connection: Pair, Consumer>) { + bind(connection.first.toSource() to connection.second.toSink()) +} + +infix fun Pair, Consumer>.using(mapper: (Out) -> In?): Connection = + first.toSource() to second.toSink() using mapper + +infix fun Pair, Consumer>.using(connector: Connector): Connection = + first.toSource() to second.toSink() using connector.toCommonConnector() diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt new file mode 100644 index 00000000..e4569e3e --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt @@ -0,0 +1,19 @@ +package com.badoo.mvicore.rx.binder + +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.rx.toObservable +import com.badoo.mvicore.rx.toSource +import io.reactivex.ObservableSource +import com.badoo.mvicore.common.binder.Connector as CommonConnector + +interface Connector { + operator fun invoke(source: ObservableSource): ObservableSource +} + +internal fun Connector.toCommonConnector(): CommonConnector = + CommonConnectorAdapter(this) + +internal class CommonConnectorAdapter(private val delegate: Connector) : CommonConnector { + override fun invoke(source: Source): Source = + delegate.invoke(source.toObservable()).toSource() +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt new file mode 100644 index 00000000..df5f0091 --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt @@ -0,0 +1,23 @@ +package com.badoo.mvicore.rx.element + +import io.reactivex.ObservableSource + +interface Bootstrapper { + operator fun invoke(): ObservableSource +} + +interface Actor { + operator fun invoke(state: State, action: Action): ObservableSource +} + +interface Reducer { + operator fun invoke(state: State, effect: Effect): State +} + +interface NewsPublisher { + operator fun invoke(old: State, action: Action, effect: Effect, new: State): News? +} + +interface PostProcessor { + operator fun invoke(old: State, action: Action, effect: Effect, new: State): Action? +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ActorReducerFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ActorReducerFeature.kt new file mode 100644 index 00000000..99b671e2 --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ActorReducerFeature.kt @@ -0,0 +1,21 @@ +package com.badoo.mvicore.rx.feature + +import com.badoo.mvicore.rx.element.Actor +import com.badoo.mvicore.rx.element.Bootstrapper +import com.badoo.mvicore.rx.element.NewsPublisher +import com.badoo.mvicore.rx.element.Reducer + +open class ActorReducerFeature( + initialState: State, + bootstrapper: Bootstrapper? = null, + actor: Actor, + reducer: Reducer, + newsPublisher: NewsPublisher? = null +) : BaseFeature( + initialState = initialState, + bootstrapper = bootstrapper, + wishToAction = { it }, + actor = actor, + reducer = reducer, + newsPublisher = newsPublisher +) diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt new file mode 100644 index 00000000..671ce9fa --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt @@ -0,0 +1,86 @@ +package com.badoo.mvicore.rx.feature + +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.connect +import com.badoo.mvicore.common.feature.BaseFeature +import com.badoo.mvicore.rx.element.Actor +import com.badoo.mvicore.rx.element.Bootstrapper +import com.badoo.mvicore.rx.element.NewsPublisher +import com.badoo.mvicore.rx.element.PostProcessor +import com.badoo.mvicore.rx.element.Reducer +import com.badoo.mvicore.rx.toObservable +import com.badoo.mvicore.rx.toSource +import io.reactivex.ObservableSource +import io.reactivex.Observer +import io.reactivex.disposables.Disposables + +abstract class BaseFeature ( + initialState: State, + private val wishToAction: (Wish) -> Action, + private val actor: Actor, + private val reducer: Reducer, + private val bootstrapper: Bootstrapper? = null, + private val newsPublisher: NewsPublisher? = null, + private val postProcessor: PostProcessor? = null +): Feature { + private val delegate = object : BaseFeature( + initialState = initialState, + wishToAction = wishToAction, + actor = ActorAdapter(actor), + reducer = ReducerAdapter(reducer), + bootstrapper = bootstrapper?.let { BootstrapperAdapter(it) }, + newsPublisher = newsPublisher?.let { NewsPublisherAdapter(it) }, + postProcessor = postProcessor?.let { PostProcessorAdapter(it) } + ) { } + + override fun accept(wish: Wish) { + delegate.invoke(wish) + } + + override val news: ObservableSource + get() = delegate.news.toObservable() + + override fun subscribe(observer: Observer) { + observer.onSubscribe( + Disposables.fromAction { + delegate.connect { observer.onNext(it) } + } + ) + } + + private class ActorAdapter( + private val delegate: Actor + ) : com.badoo.mvicore.common.element.Actor { + override fun invoke(state: State, action: Action): Source = + delegate.invoke(state, action).toSource() + + } + + private class ReducerAdapter( + private val delegate: Reducer + ): com.badoo.mvicore.common.element.Reducer { + override fun invoke(state: State, effect: Wish): State = + delegate.invoke(state, effect) + } + + private class BootstrapperAdapter( + private val delegate: Bootstrapper + ): com.badoo.mvicore.common.element.Bootstrapper { + override fun invoke(): Source = + delegate.invoke().toSource() + } + + private class NewsPublisherAdapter( + private val delegate: NewsPublisher + ): com.badoo.mvicore.common.element.NewsPublisher { + override fun invoke(old: State, action: Action, effect: Effect, new: State): News? = + delegate.invoke(old, action, effect, new) + } + + private class PostProcessorAdapter( + private val delegate: PostProcessor + ): com.badoo.mvicore.common.element.PostProcessor{ + override fun invoke(old: State, action: Action, effect: Effect, new: State): Action? = + delegate.invoke(old, action, effect, new) + } +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt new file mode 100644 index 00000000..d2576e62 --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt @@ -0,0 +1,9 @@ +package com.badoo.mvicore.rx.feature + +import io.reactivex.ObservableSource +import io.reactivex.functions.Consumer + +interface Feature : Consumer, ObservableSource { + val news: ObservableSource +} + diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt new file mode 100644 index 00000000..5a0e7e78 --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt @@ -0,0 +1,34 @@ +package com.badoo.mvicore.rx.feature + +import com.badoo.mvicore.rx.element.Actor +import com.badoo.mvicore.rx.element.Bootstrapper +import com.badoo.mvicore.rx.element.NewsPublisher +import com.badoo.mvicore.rx.element.Reducer +import io.reactivex.Observable + +open class ReducerFeature( + initialState: State, + bootstrapper: Bootstrapper? = null, + reducer: Reducer, + newsPublisher: SimpleNewsPublisher? = null +) : ActorReducerFeature( + initialState = initialState, + actor = PassthroughActor(), + bootstrapper = bootstrapper, + reducer = reducer, + newsPublisher = newsPublisher?.let { NoEffectNewsPublisher(it) } +) { + private class PassthroughActor : Actor { + override fun invoke(state: State, wish: Wish): Observable = + Observable.just(wish) + } + + private class NoEffectNewsPublisher( + private val simpleNewsPublisher: SimpleNewsPublisher + ) : NewsPublisher { + override fun invoke(old: State, action: Wish, effect: Wish, new: State): News? = + simpleNewsPublisher(old, action, new) + } +} + +typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/settings.gradle b/settings.gradle index e626f822..3c709c8c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ include ':mvicore-base' +include ':mvicore-rx' include ':mvicore' From 39a25da8b9d0d42d3d7ff9e398ca0ed8806b1975 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 30 Dec 2019 02:58:11 +0000 Subject: [PATCH 12/31] Update gradle version --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b1b4c702..7d025e29 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip From 17a71ee3b0f61362bec10c017b11512f906385c7 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 30 Dec 2019 13:23:44 +0000 Subject: [PATCH 13/31] Update travis run to include new modules --- .../src/main/java/com/badoo/mvicore/rx/Source.kt | 7 ------- scripts/run-tests-travis.sh | 10 +++++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt index 0ec21076..40f8ce9d 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt @@ -24,13 +24,6 @@ internal class SourceAdapter(internal val delegate: Observable): Source } } - override fun cancel() { - disposable.dispose() - } - - override val isCancelled: Boolean - get() = disposable.isDisposed - override fun equals(other: Any?): Boolean = other is SourceAdapter<*> && super.equals(other.delegate) diff --git a/scripts/run-tests-travis.sh b/scripts/run-tests-travis.sh index b13f00c7..f51271d9 100755 --- a/scripts/run-tests-travis.sh +++ b/scripts/run-tests-travis.sh @@ -1,3 +1,11 @@ #!/usr/bin/env bash -./gradlew :mvicore:test :mvicore-android:assembleDebug :mvicore-debugdrawer:assembleDebug :mvicore-demo:mvicore-demo-app:assembleDebug --info --stacktrace +./gradlew \ + clean \ + :mvicore-base:jvmTest \ + :mvicore-rx:test \ + :mvicore-android:assembleDebug \ + :mvicore-debugdrawer:assembleDebug \ + :mvicore-demo:mvicore-demo-app:assembleDebug \ + --console=plain \ + --stacktrace From 4c04075fbd42a09b9726a5f86c58385723d2d158 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 30 Dec 2019 22:26:53 +0000 Subject: [PATCH 14/31] Check feature correctness when frozen --- .../kotlin/com/badoo/mvicore/common/base.kt | 4 +-- .../kotlin/com/badoo/mvicore/common/source.kt | 7 ++-- .../kotlin/com/badoo/mvicore/common/util.kt | 6 ++-- .../kotlin/com/badoo/mvicore/common/baseJs.kt | 2 +- .../com/badoo/mvicore/common/baseNative.kt | 2 +- .../src/nativeTest/kotlin/reducerFeature.kt | 32 +++++++++++++++++++ 6 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 mvicore-base/src/nativeTest/kotlin/reducerFeature.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt index 4cd6e82a..5018d1f5 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt @@ -1,11 +1,11 @@ package com.badoo.mvicore.common -expect class AtomicRef(initialValue: V) { +expect class AtomicRef(initialValue: V) { fun compareAndSet(expect: V, update: V): Boolean fun get(): V } -internal fun AtomicRef.update(update: (T) -> T) { +internal fun AtomicRef.update(update: (T) -> T) { do { val oldValue = get() val result = compareAndSet(oldValue, update(oldValue)) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index b1e526c1..a6f34d4d 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -19,11 +19,12 @@ fun Source.connect(action: (T) -> Unit) = class SourceImpl(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { private val sinks = AtomicRef(listOf>()) - var value: T? = initialValue - private set + private val _value = AtomicRef(initialValue) + val value: T? + get() = _value.get() override fun invoke(value: T) { - this.value = value + _value.update { value } val sinks = sinks.get() sinks.forEach { it.invoke(value) } } diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index e401e439..adb8945a 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -3,12 +3,12 @@ package com.badoo.mvicore.common import kotlin.test.assertEquals class TestSink: Sink { - private val _values: MutableList = mutableListOf() + private val _values = AtomicRef>(emptyList()) val values: List - get() = _values + get() = _values.get() override fun invoke(value: T) { - _values += value + _values.update { it + value } } } diff --git a/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt b/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt index 1494b472..561ed6e1 100644 --- a/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt +++ b/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt @@ -1,6 +1,6 @@ package com.badoo.mvicore.common -actual class AtomicRef actual constructor(initialValue: V) { +actual class AtomicRef actual constructor(initialValue: V) { private var value: V = initialValue actual fun compareAndSet(expect: V, update: V): Boolean { diff --git a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt index de1c6fae..e29f0b7e 100644 --- a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt +++ b/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt @@ -4,7 +4,7 @@ import kotlin.native.concurrent.FreezableAtomicReference import kotlin.native.concurrent.freeze import kotlin.native.concurrent.isFrozen -actual class AtomicRef actual constructor(initialValue: V) { +actual class AtomicRef actual constructor(initialValue: V) { private val delegate = FreezableAtomicReference(initialValue) diff --git a/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt b/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt new file mode 100644 index 00000000..9e127694 --- /dev/null +++ b/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt @@ -0,0 +1,32 @@ +import com.badoo.mvicore.common.TestSink +import com.badoo.mvicore.common.assertValues +import com.badoo.mvicore.common.element.Reducer +import com.badoo.mvicore.common.feature.ReducerFeature +import kotlin.native.concurrent.freeze +import kotlin.test.Test + +class ReducerFeatureTest { + + @Test + fun reducer_feature_emits_new_state_on_new_wish_when_frozen() { + val feature = TestReducerFeature().freeze() + val sink = TestSink() + + feature.connect(sink) + + feature.invoke(0) + sink.assertValues("", "0") + } + + class TestReducerFeature(initialState: String = ""): ReducerFeature( + initialState = initialState, + reducer = reducer { state, wish -> + state + wish.toString() + } + ) +} + +fun reducer(block: (state: State, effect: Effect) -> State) = + object : Reducer { + override fun invoke(state: State, effect: Effect): State = block(state, effect) + } From 5a88ff46d8ccd7d89df47346a52870e9a29770a9 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 31 Dec 2019 16:42:55 +0000 Subject: [PATCH 15/31] Migrate binder to atomics --- build.gradle | 7 +++ .../com/badoo/mvicore/common/binder/Binder.kt | 45 +++++++++++-------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/build.gradle b/build.gradle index 957f6ba3..678b2d55 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,9 @@ buildscript { google() jcenter() mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' @@ -116,6 +119,10 @@ allprojects { repositories { google() jcenter() + mavenLocal() + maven { + url "https://plugins.gradle.org/m2/" + } } } diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index b86d8c0f..f730f2fc 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -1,5 +1,6 @@ package com.badoo.mvicore.common.binder +import com.badoo.mvicore.common.AtomicRef import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.Sink @@ -11,6 +12,7 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END import com.badoo.mvicore.common.source import com.badoo.mvicore.common.sources.DelayUntilSource +import com.badoo.mvicore.common.update /** * Establishes connections between [Source] and [Sink] endpoints @@ -25,13 +27,13 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { /** * Stores internal end of every `emitter` connected through binder - * Allows for propagation of events in case emission disposes the binder + * Allows for propagation of events in case emission cancels the binder * E.g.: * from -> internalSource -> to // Events from `from` are propagated to `to` - * #dispose() + * #cancel() * from xx internalSource -> to // Remaining events from `internalSource` are propagated to `to` */ - private val fromToInternalSource = mutableMapOf, SourceImpl<*>>() + private val internalSourceCache = AtomicRef(emptyMap, SourceImpl<*>>()) /** * Delay events emitted on subscribe until `init` lambda is executed @@ -60,14 +62,21 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { delayInitialize.connect(connection.to as Sink) } - private fun getInternalSourceFor(from: Source): SourceImpl = - fromToInternalSource.getOrPut(from) { - source().also { cancellables += from.connect(it) } - } as SourceImpl + private fun getInternalSourceFor(from: Source): SourceImpl { + val cachedSource = internalSourceCache.get()[from] + return if (cachedSource != null) { + cachedSource as SourceImpl + } else { + source().also { source -> + cancellables += from.connect(source) + internalSourceCache.update { it + (from to source) } + } + } + } override fun cancel() { cancellables.cancel() - fromToInternalSource.clear() + internalSourceCache.update { emptyMap() } } override val isCancelled: Boolean @@ -75,10 +84,10 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { } internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : Binder() { - private var lifecycleActive = false + private var lifecycleActive = AtomicRef(false) private val cancellables = CompositeCancellable() private val innerBinder = SimpleBinder(init) - private val connections = mutableListOf>() + private val connections = AtomicRef(listOf>()) init { cancellables += lifecycle.connect { @@ -91,29 +100,29 @@ internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : } override fun connect(connection: Connection) { - connections += connection - if (lifecycleActive) { + connections.update { it + connection } + if (lifecycleActive.get()) { innerBinder.connect(connection) } } private fun connect() { - if (lifecycleActive) return + if (lifecycleActive.get()) return - lifecycleActive = true - connections.forEach { innerBinder.connect(it) } + lifecycleActive.update { true } + connections.get().forEach { innerBinder.connect(it) } } private fun disconnect() { - if (!lifecycleActive) return + if (!lifecycleActive.get()) return - lifecycleActive = false + lifecycleActive.update { false } innerBinder.cancel() } override fun cancel() { cancellables.cancel() - connections.clear() + connections.update { emptyList() } } override val isCancelled: Boolean From a2fc84d1bac169abdb412bf541ae98839f6db3a2 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 2 Jan 2020 01:29:27 +0000 Subject: [PATCH 16/31] Add actor reducer tests --- .../common/feature/actorReducerFeature.kt | 125 ++++++++++++++++++ .../common/{ => feature}/reducerFeature.kt | 31 +++-- .../kotlin/com/badoo/mvicore/common/util.kt | 27 ++++ 3 files changed, 171 insertions(+), 12 deletions(-) create mode 100644 mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt rename mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/{ => feature}/reducerFeature.kt (81%) diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt new file mode 100644 index 00000000..adf609b0 --- /dev/null +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -0,0 +1,125 @@ +package com.badoo.mvicore.common.feature + +import com.badoo.mvicore.common.TestSink +import com.badoo.mvicore.common.actor +import com.badoo.mvicore.common.assertValues +import com.badoo.mvicore.common.bootstrapper +import com.badoo.mvicore.common.newsPublisher +import com.badoo.mvicore.common.reducer +import com.badoo.mvicore.common.sources.ValueSource +import kotlin.test.Test + +class ActorReducerFeatureTest { + + @Test + fun feature_emits_initial_state_on_connect() { + val feature = TestActorReducerFeature() + val sink = TestSink() + + feature.connect(sink) + + sink.assertValues("") + } + + @Test + fun feature_emits_states_on_each_wish() { + val feature = TestActorReducerFeature() + val sink = TestSink() + + feature.connect(sink) + feature.invoke(0) + + sink.assertValues("", "0") + } + + @Test + fun feature_emits_states_on_each_actor_emission() { + val feature = TestActorReducerFeature() + val sink = TestSink() + + feature.connect(sink) + feature.invoke(1) + + sink.assertValues("", "1", "12") + } + + @Test + fun feature_updates_states_on_init_with_bootstrapper() { + val feature = TestActorReducerFeatureWBootstrapper() + val sink = TestSink() + + feature.connect(sink) + + sink.assertValues("0") + } + + @Test + fun feature_emits_news_for_each_state_update() { + val feature = TestActorReducerFeatureWNews() + val stateSink = TestSink() + val newsSink = TestSink() + + feature.news.connect(newsSink) + feature.connect(stateSink) + feature.invoke(0) + feature.invoke(1) + + stateSink.assertValues("", "0", "01", "012") + newsSink.assertValues(0, 2) + } + + @Test + fun feature_stops_emitting_after_cancel() { + val feature = TestActorReducerFeatureWNews() + val stateSink = TestSink() + val newsSink = TestSink() + + feature.news.connect(newsSink) + feature.connect(stateSink) + + feature.invoke(0) + feature.cancel() + + feature.invoke(1) + + stateSink.assertValues("", "0") + newsSink.assertValues(0) + + } +} + +class TestActorReducerFeature(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + } +) + +class TestActorReducerFeatureWBootstrapper(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + }, + bootstrapper = bootstrapper { + ValueSource(0) + } +) + +class TestActorReducerFeatureWNews(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + }, + newsPublisher = newsPublisher { old, action, effect, new -> + if (effect % 2 == 0) effect else null + } +) diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt similarity index 81% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt rename to mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt index 78396bac..f4642b5d 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/reducerFeature.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt @@ -1,8 +1,9 @@ -package com.badoo.mvicore.common +package com.badoo.mvicore.common.feature -import com.badoo.mvicore.common.element.Bootstrapper -import com.badoo.mvicore.common.element.Reducer -import com.badoo.mvicore.common.feature.ReducerFeature +import com.badoo.mvicore.common.TestSink +import com.badoo.mvicore.common.assertValues +import com.badoo.mvicore.common.bootstrapper +import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource import kotlin.test.Test @@ -63,6 +64,18 @@ class ReducerFeatureTest { sink.assertValues(1, 2) } + @Test + fun reducer_feature_stop_processing_events_after_cancel() { + val feature = TestReducerFeature() + val sink = TestSink() + + feature.connect(sink) + feature.cancel() + + feature.invoke(1) + sink.assertValues("") + } + class TestReducerFeature(initialState: String = ""): ReducerFeature( initialState = initialState, reducer = reducer { state, wish -> @@ -72,10 +85,7 @@ class ReducerFeatureTest { class TestReducerFeatureWBootstrapper(initialState: String = ""): ReducerFeature( initialState = initialState, - bootstrapper = object : Bootstrapper { - override fun invoke(): Source = - ValueSource(0) - }, + bootstrapper = bootstrapper { ValueSource(0) }, reducer = reducer { state, wish -> state + wish.toString() } @@ -96,7 +106,4 @@ class ReducerFeatureTest { ) } -fun reducer(block: (state: State, effect: Effect) -> State) = - object : Reducer { - override fun invoke(state: State, effect: Effect): State = block(state, effect) - } + diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index adb8945a..f6c779b9 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -1,5 +1,9 @@ package com.badoo.mvicore.common +import com.badoo.mvicore.common.element.Actor +import com.badoo.mvicore.common.element.Bootstrapper +import com.badoo.mvicore.common.element.NewsPublisher +import com.badoo.mvicore.common.element.Reducer import kotlin.test.assertEquals class TestSink: Sink { @@ -17,3 +21,26 @@ fun TestSink.assertValues(vararg values: T) = fun TestSink.assertNoValues() = assertEquals(emptyList(), this.values) + +fun reducer(block: (state: State, effect: Effect) -> State) = + object : Reducer { + override fun invoke(state: State, effect: Effect): State = block(state, effect) + } + +fun actor(block: (state: State, wish: Wish) -> Source) = + object : Actor { + override fun invoke(state: State, action: Wish): Source = + block(state, action) + } + +fun bootstrapper(block: () -> Source) = + object : Bootstrapper { + override fun invoke(): Source = + block() + } + +fun newsPublisher(block: (old: State, action: Action, effect: Effect, new: State) -> News?) = + object : NewsPublisher { + override fun invoke(old: State, action: Action, effect: Effect, new: State): News? = + block(old, action, effect, new) + } From 0bc48af21c28d44367f9852cf9e1a87f78e6e784 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 2 Jan 2020 01:30:53 +0000 Subject: [PATCH 17/31] Add cancel test for native --- .../src/nativeTest/kotlin/reducerFeature.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt b/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt index 9e127694..bc145f00 100644 --- a/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt +++ b/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt @@ -18,6 +18,19 @@ class ReducerFeatureTest { sink.assertValues("", "0") } + @Test + fun reducer_feature_stops_emitting_after_cancel_when_frozen() { + val feature = TestReducerFeature().freeze() + val sink = TestSink() + + feature.connect(sink) + + feature.cancel() + + feature.invoke(0) + sink.assertValues("") + } + class TestReducerFeature(initialState: String = ""): ReducerFeature( initialState = initialState, reducer = reducer { state, wish -> From 4e89f9aff78c9d5e60126d05aa904685fd73fa6c Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 3 Jan 2020 07:13:54 +0000 Subject: [PATCH 18/31] Add benchmarks and fix impl of sources --- {mvicore-base => benchmarks}/.gitignore | 0 benchmarks/build.gradle | 24 ++++ .../com/badoo/mvicore/CommonFeatureBench.kt | 51 +++++++ .../com/badoo/mvicore/NewFeatureBench.kt | 48 +++++++ .../com/badoo/mvicore/OldFeatureBench.kt | 49 +++++++ build.gradle | 3 - .../kotlin/com/badoo/mvicore/common/base.kt | 13 -- .../kotlin/com/badoo/mvicore/common/sink.kt | 11 -- .../kotlin/com/badoo/mvicore/common/source.kt | 51 ------- .../common/sources/DelayUntilSource.kt | 48 ------- .../common/sources/MapNotNullSource.kt | 20 --- .../mvicore/common/sources/ValueSource.kt | 27 ---- mvicore-common/.gitignore | 1 + {mvicore-base => mvicore-common}/build.gradle | 2 +- .../kotlin/com/badoo/mvicore/common/base.kt | 25 ++++ .../com/badoo/mvicore/common/binder/Binder.kt | 12 +- .../badoo/mvicore/common/binder/BinderExt.kt | 0 .../badoo/mvicore/common/binder/Connection.kt | 0 .../badoo/mvicore/common/binder/Connector.kt | 0 .../mvicore/common/binder/NotNullConnector.kt | 0 .../com/badoo/mvicore/common/cancellable.kt | 16 ++- .../mvicore/common/element/featureElements.kt | 0 .../common/feature/ActorReducerFeature.kt | 0 .../mvicore/common/feature/BaseFeature.kt | 4 +- .../badoo/mvicore/common/feature/Feature.kt | 0 .../mvicore/common/feature/ReducerFeature.kt | 0 .../mvicore/common/lifecycle/lifecycle.kt | 4 +- .../mvicore/common/middleware/Middleware.kt | 0 .../common/middleware/StandaloneMiddleware.kt | 0 .../kotlin/com/badoo/mvicore/common/sink.kt | 43 ++++++ .../kotlin/com/badoo/mvicore/common/source.kt | 73 ++++++++++ .../common/sources/DelayUntilSource.kt | 64 +++++++++ .../common/sources/MapNotNullSource.kt | 31 +++++ .../mvicore/common/sources/ValueSource.kt | 26 ++++ .../kotlin/com/badoo/mvicore/common/binder.kt | 0 .../common/feature/actorReducerFeature.kt | 3 +- .../mvicore/common/feature/baseFeature.kt | 126 ++++++++++++++++++ .../mvicore/common/feature/reducerFeature.kt | 1 + .../kotlin/com/badoo/mvicore/common/source.kt | 0 .../kotlin/com/badoo/mvicore/common/util.kt | 0 .../kotlin/com/badoo/mvicore/common/baseJs.kt | 0 .../com/badoo/mvicore/common/baseJvm.kt | 0 .../com/badoo/mvicore/common/baseNative.kt | 0 .../src/nativeTest/kotlin/reducerFeature.kt | 0 mvicore-rx/build.gradle | 2 +- .../main/java/com/badoo/mvicore/rx/Source.kt | 31 +++-- scripts/run-tests-travis.sh | 2 +- settings.gradle | 3 +- 48 files changed, 611 insertions(+), 203 deletions(-) rename {mvicore-base => benchmarks}/.gitignore (100%) create mode 100644 benchmarks/build.gradle create mode 100644 benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt create mode 100644 benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt create mode 100644 benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt delete mode 100644 mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt create mode 100644 mvicore-common/.gitignore rename {mvicore-base => mvicore-common}/build.gradle (94%) create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt (92%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt (80%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt (94%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt (78%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt (100%) rename {mvicore-base => mvicore-common}/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt (100%) create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt create mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt rename {mvicore-base => mvicore-common}/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt (100%) rename {mvicore-base => mvicore-common}/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt (97%) create mode 100644 mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt rename {mvicore-base => mvicore-common}/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt (98%) rename {mvicore-base => mvicore-common}/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt (100%) rename {mvicore-base => mvicore-common}/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt (100%) rename {mvicore-base => mvicore-common}/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt (100%) rename {mvicore-base => mvicore-common}/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt (100%) rename {mvicore-base => mvicore-common}/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt (100%) rename {mvicore-base => mvicore-common}/src/nativeTest/kotlin/reducerFeature.kt (100%) diff --git a/mvicore-base/.gitignore b/benchmarks/.gitignore similarity index 100% rename from mvicore-base/.gitignore rename to benchmarks/.gitignore diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle new file mode 100644 index 00000000..24bbbbca --- /dev/null +++ b/benchmarks/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'org.jetbrains.kotlin.jvm' + id 'me.champeau.gradle.jmh' version "0.5.0" +} + +dependencies { + implementation project(':mvicore-rx') + implementation project(':mvicore-common') + implementation project(':mvicore') + implementation deps('io.reactivex.rxjava2:rxjava') + implementation 'org.openjdk.jmh:jmh-core:1.22' + implementation deps("org.jetbrains.kotlin:kotlin-stdlib-jdk7") +} + +jmh { + benchmarkMode = ['thrpt'] + timeUnit = 'ms' + fork = 1 + warmup = '2s' + warmupIterations = 8 + timeOnIteration = '2s' + iterations = 10 +} diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt new file mode 100644 index 00000000..bd916262 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt @@ -0,0 +1,51 @@ +package com.badoo.mvicore + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.connect +import com.badoo.mvicore.common.element.Actor +import com.badoo.mvicore.common.element.Reducer +import com.badoo.mvicore.common.feature.ActorReducerFeature +import com.badoo.mvicore.common.sources.ValueSource +import org.openjdk.jmh.annotations.Benchmark +import org.openjdk.jmh.annotations.Level +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.TearDown +import org.openjdk.jmh.infra.Blackhole + + +@State(Scope.Benchmark) +open class CommonFeatureBench { + class TestActorReducerFeature(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = object : Actor { + override fun invoke(state: String, action: Int): Source = + ValueSource(action) + }, + reducer = object : Reducer { + override fun invoke(state: String, effect: Int): String = + state + } + ) + + private lateinit var feature: TestActorReducerFeature + private lateinit var disposable: Cancellable + + @Setup(Level.Iteration) + open fun setup(blackhole: Blackhole) { + feature = TestActorReducerFeature() + disposable = feature.connect { blackhole.consume(it) } + } + + @Benchmark + fun feature() { + feature.invoke(1) + } + + @TearDown(Level.Iteration) + open fun tearDown() { + disposable.cancel() + } +} diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt new file mode 100644 index 00000000..ff6eff88 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt @@ -0,0 +1,48 @@ +package com.badoo.mvicore + +import com.badoo.mvicore.rx.element.Actor +import com.badoo.mvicore.rx.element.Reducer +import com.badoo.mvicore.rx.feature.ActorReducerFeature +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import org.openjdk.jmh.annotations.Level +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.TearDown +import org.openjdk.jmh.infra.Blackhole + + +@State(Scope.Benchmark) +open class NewFeatureBench { + class TestActorReducerFeature(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = object : Actor { + override fun invoke(state: String, action: Int): Observable = + Observable.just(action) + }, + reducer = object : Reducer { + override fun invoke(state: String, effect: Int): String = + state + } + ) + + private lateinit var feature: TestActorReducerFeature + private lateinit var disposable: Disposable + + @Setup(Level.Iteration) + open fun setup(blackhole: Blackhole) { + feature = TestActorReducerFeature() + disposable = Observable.wrap(feature).subscribe { blackhole.consume(it) } + } + +// @Benchmark + fun feature() { + feature.accept(1) + } + + @TearDown(Level.Iteration) + open fun tearDown() { + disposable.dispose() + } +} diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt new file mode 100644 index 00000000..8a99ba4d --- /dev/null +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt @@ -0,0 +1,49 @@ +package com.badoo.mvicore + +import com.badoo.mvicore.element.Actor +import com.badoo.mvicore.element.Reducer +import com.badoo.mvicore.feature.ActorReducerFeature +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import org.openjdk.jmh.annotations.Level +import org.openjdk.jmh.annotations.Scope +import org.openjdk.jmh.annotations.Setup +import org.openjdk.jmh.annotations.State +import org.openjdk.jmh.annotations.TearDown +import org.openjdk.jmh.infra.Blackhole + + +@State(Scope.Benchmark) +open class OldFeatureBench { + + class TestActorReducerFeature(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = object : Actor { + override fun invoke(state: String, action: Int): Observable = + Observable.just(action) + }, + reducer = object : Reducer { + override fun invoke(state: String, effect: Int): String = + state + } + ) + + private lateinit var feature: TestActorReducerFeature + private lateinit var disposable: Disposable + + @Setup(Level.Iteration) + open fun setup(blackhole: Blackhole) { + feature = TestActorReducerFeature() + disposable = Observable.wrap(feature).subscribe { blackhole.consume(it) } + } + +// @Benchmark + fun feature() { + feature.accept(1) + } + + @TearDown(Level.Iteration) + open fun tearDown() { + disposable.dispose() + } +} diff --git a/build.gradle b/build.gradle index 678b2d55..730f918f 100644 --- a/build.gradle +++ b/build.gradle @@ -6,9 +6,6 @@ buildscript { google() jcenter() mavenCentral() - maven { - url "https://plugins.gradle.org/m2/" - } } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt deleted file mode 100644 index 5018d1f5..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.badoo.mvicore.common - -expect class AtomicRef(initialValue: V) { - fun compareAndSet(expect: V, update: V): Boolean - fun get(): V -} - -internal fun AtomicRef.update(update: (T) -> T) { - do { - val oldValue = get() - val result = compareAndSet(oldValue, update(oldValue)) - } while (!result) -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt deleted file mode 100644 index 613e5d8a..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.badoo.mvicore.common - -interface Sink { - operator fun invoke(value: T) -} - -fun sinkOf(action: (T) -> Unit) = object : Sink { - override fun invoke(value: T) { - action(value) - } -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt deleted file mode 100644 index a6f34d4d..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.badoo.mvicore.common - -/** - * NOTE: in conversions from other frameworks you need to override equals and hashCode - * to support binder "emit after dispose" functionality - */ -interface Source { - fun connect(sink: Sink): Cancellable -} - -fun source(initialValue: T): SourceImpl = - SourceImpl(initialValue, true) - -fun source(): SourceImpl = - SourceImpl(null, false) - -fun Source.connect(action: (T) -> Unit) = - connect(sinkOf(action)) - -class SourceImpl(initialValue: T?, private val emitOnConnect: Boolean): Source, Sink { - private val sinks = AtomicRef(listOf>()) - private val _value = AtomicRef(initialValue) - val value: T? - get() = _value.get() - - override fun invoke(value: T) { - _value.update { value } - val sinks = sinks.get() - sinks.forEach { it.invoke(value) } - } - - override fun connect(sink: Sink): Cancellable { - sinks.update { it + sink } - - val value = value - if (emitOnConnect && value != null) { - sink.invoke(value) - } - - return cancellableOf { - sinks.update { it - sink } - } - } - -// override val isCancelled: Boolean -// get() = internalCancellable.isCancelled -// -// override fun cancel() { -// internalCancellable.cancel() -// } -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt deleted file mode 100644 index 0c7d2860..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ /dev/null @@ -1,48 +0,0 @@ -package com.badoo.mvicore.common.sources - -import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink -import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.cancellableOf -import com.badoo.mvicore.common.connect - -internal class DelayUntilSource( - private val signal: Source, - private val delegate: Source -): Source { - override fun connect(sink: Sink): Cancellable { - val delayedSink = DelayedSink(sink) - val cancelDelay = signal.connect { if (it) { delayedSink.send() } } - val cancelSubscription = delegate.connect(delayedSink) - return cancellableOf { - cancelDelay.cancel() - cancelSubscription.cancel() - } - } -// -// override fun cancel() { -// delegate.cancel() -// } -// -// override val isCancelled: Boolean -// get() = delegate.isCancelled - - private class DelayedSink(private val delegate: Sink) : Sink { - private var passThrough = false - private val events = mutableListOf() - - override fun invoke(value: T) { - if (passThrough) { - delegate(value) - } else { - events += value - } - } - - fun send() { - passThrough = true - events.forEach { delegate(it) } - events.clear() - } - } -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt deleted file mode 100644 index cf076a1a..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.badoo.mvicore.common.sources - -import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink -import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.connect - -internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { - override fun connect(sink: Sink): Cancellable = - delegate.connect { - mapper(it)?.let { sink(it) } - } - -// override fun cancel() { -// delegate.cancel() -// } -// -// override val isCancelled: Boolean -// get() = delegate.isCancelled -} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt b/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt deleted file mode 100644 index 7a044156..00000000 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.badoo.mvicore.common.sources - -import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink -import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.cancellableOf -import com.badoo.mvicore.common.cancelled - -internal class ValueSource(vararg values: T) : Source { - private val valuesToEmit = values - private val cancellable = cancellableOf { } - - override fun connect(sink: Sink): Cancellable { - if (!cancellable.isCancelled) { - valuesToEmit.forEach { sink(it) } - } - return cancelled() - } - -// override fun cancel() { -// cancellable.cancel() -// } -// -// override val isCancelled: Boolean -// get() = cancellable.isCancelled -} - diff --git a/mvicore-common/.gitignore b/mvicore-common/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/mvicore-common/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mvicore-base/build.gradle b/mvicore-common/build.gradle similarity index 94% rename from mvicore-base/build.gradle rename to mvicore-common/build.gradle index 9ddf32da..50036c0f 100644 --- a/mvicore-base/build.gradle +++ b/mvicore-common/build.gradle @@ -25,7 +25,7 @@ kotlin { } targets { - fromPreset(presets.macosX64, 'native') + fromPreset(presets.macosX64, 'native') // remove when multiple targets are setup } sourceSets { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt new file mode 100644 index 00000000..8f3b5a6a --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt @@ -0,0 +1,25 @@ +package com.badoo.mvicore.common + +expect class AtomicRef(initialValue: V) { + fun compareAndSet(expect: V, update: V): Boolean + fun get(): V +} + +internal inline fun AtomicRef.update(update: (T) -> T) { + do { + val oldValue = get() + val result = compareAndSet(oldValue, update(oldValue)) + } while (!result) +} + +internal inline operator fun Array.minus(value: T): Array { + val index = indexOf(value) + return if (index == -1) { + this + } else { + val newArray = arrayOfNulls(size - 1) + copyInto(newArray, destinationOffset = 0, startIndex = 0, endIndex = index) + copyInto(newArray, destinationOffset = index, startIndex = index + 1, endIndex = size) + newArray as Array + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt similarity index 92% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index f730f2fc..99569ed6 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -3,9 +3,9 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.AtomicRef import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.CompositeCancellable +import com.badoo.mvicore.common.PublishSource import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.SourceImpl import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.lifecycle.Lifecycle import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN @@ -33,7 +33,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { * #cancel() * from xx internalSource -> to // Remaining events from `internalSource` are propagated to `to` */ - private val internalSourceCache = AtomicRef(emptyMap, SourceImpl<*>>()) + private val internalSourceCache = AtomicRef(emptyMap, PublishSource<*>>()) /** * Delay events emitted on subscribe until `init` lambda is executed @@ -62,10 +62,10 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { delayInitialize.connect(connection.to as Sink) } - private fun getInternalSourceFor(from: Source): SourceImpl { + private fun getInternalSourceFor(from: Source): PublishSource { val cachedSource = internalSourceCache.get()[from] return if (cachedSource != null) { - cachedSource as SourceImpl + cachedSource as PublishSource } else { source().also { source -> cancellables += from.connect(source) @@ -87,7 +87,7 @@ internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : private var lifecycleActive = AtomicRef(false) private val cancellables = CompositeCancellable() private val innerBinder = SimpleBinder(init) - private val connections = AtomicRef(listOf>()) + private val connections = AtomicRef(emptyArray>()) init { cancellables += lifecycle.connect { @@ -122,7 +122,7 @@ internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : override fun cancel() { cancellables.cancel() - connections.update { emptyList() } + connections.update { emptyArray() } } override val isCancelled: Boolean diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt similarity index 80% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt index 40ad6b2c..6f2ac3cf 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -23,14 +23,14 @@ private class CancellableImpl(private val onCancel: Cancellable.() -> Unit): Can } private class CancelledCancellable : Cancellable { - override val isCancelled: Boolean = false + override val isCancelled: Boolean = true override fun cancel() { // no-op } } class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { - private val cancellableListRef = AtomicRef(setOf(*cancellables)) + private val cancellableListRef: AtomicRef> = AtomicRef(hashSetOf(*cancellables)) private val internalCancellable = CancellableImpl { val list = cancellableListRef.get() list.forEach { it.cancel() } @@ -50,9 +50,7 @@ class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { operator fun minusAssign(cancellable: Cancellable?) { if (!isCancelled && cancellable != null) { - cancellableListRef.update { - it - cancellable - } + cancellableListRef.update { it - cancellable } } } @@ -60,3 +58,11 @@ class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { internalCancellable.cancel() } } + +internal fun AtomicRef.cancel() { + val value = get() + if (value != null) { + value.cancel() + compareAndSet(value, null) + } +} diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt similarity index 94% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index 1de17792..ac5f514e 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -1,7 +1,7 @@ package com.badoo.mvicore.common.feature import com.badoo.mvicore.common.CompositeCancellable -import com.badoo.mvicore.common.Sink +import com.badoo.mvicore.common.Observer import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.element.Actor @@ -46,7 +46,7 @@ abstract class BaseFeature) = stateSource.connect(sink) + override fun connect(observer: Observer) = stateSource.connect(observer) override val isCancelled: Boolean get() = cancellables.isCancelled diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt similarity index 78% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt index f35ecf86..9e7f650b 100644 --- a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt @@ -1,7 +1,7 @@ package com.badoo.mvicore.common.lifecycle +import com.badoo.mvicore.common.BehaviourSource import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.SourceImpl import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END @@ -16,7 +16,7 @@ interface Lifecycle : Source { } class ManualLifecycle( - private val source: SourceImpl = SourceImpl(initialValue = null, emitOnConnect = true) + private val source: BehaviourSource = BehaviourSource() ) : Lifecycle, Source by source { fun begin() { source(BEGIN) diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt diff --git a/mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt similarity index 100% rename from mvicore-base/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt rename to mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt new file mode 100644 index 00000000..3d3a02da --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt @@ -0,0 +1,43 @@ +package com.badoo.mvicore.common + +interface Sink { + operator fun invoke(value: T) +} + +interface Observer : Sink { + fun onSubscribe(cancellable: Cancellable) + fun onComplete() + fun onError(throwable: Throwable) +} + +fun sinkOf(action: (T) -> Unit): Sink = SinkFromAction(action) + +private class SinkFromAction(private val action: (T) -> Unit): Sink { + override fun invoke(value: T) { + action(value) + } +} + +fun ((T) -> Unit).toObserver(): Observer = + ObserverFromAction(this) + +private class ObserverFromAction(val action: (T) -> Unit): Observer { + private val cancellable: AtomicRef = AtomicRef(null) + + override fun invoke(value: T) { + action(value) + } + + override fun onSubscribe(cancellable: Cancellable) { + this.cancellable.compareAndSet(null, cancellable) + } + + override fun onComplete() { + cancellable.cancel() + } + + override fun onError(throwable: Throwable) { + cancellable.cancel() + throw throwable + } +} diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt new file mode 100644 index 00000000..40b357d0 --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -0,0 +1,73 @@ +package com.badoo.mvicore.common + +/** + * NOTE: in conversions from other frameworks you need to override equals and hashCode + * to support binder "emit after dispose" functionality + */ +interface Source { + fun connect(observer: Observer): Cancellable +} + +fun source(initialValue: T): BehaviourSource = BehaviourSource(initialValue) + +fun source(): PublishSource = PublishSource() + +fun Source.connect(action: (T) -> Unit) = + connect(action.toObserver()) + +fun Source.connect(sink: Sink) = + connect(sink::invoke) + + +class PublishSource internal constructor(): Source, Sink { + private val observers: AtomicRef>> = AtomicRef(emptyArray()) + + override fun connect(observer: Observer): Cancellable { + observers.update { it + observer } + val cancellable = cancellableOf { + observers.update { it - observer } + observer.onComplete() + } + + observer.onSubscribe(cancellable) + return cancellable + } + + override fun invoke(value: T) { + val observers = observers.get() + observers.forEach { it(value) } + } +} + +class BehaviourSource internal constructor(initialValue: Any? = NoValue) : Source, Sink { + private val observers: AtomicRef>> = AtomicRef(emptyArray()) + private val _value = AtomicRef(initialValue) + + override fun connect(observer: Observer): Cancellable { + observers.update { it + observer } + + val cancellable = cancellableOf { + observers.update { it - observer } + observer.onComplete() + } + observer.onSubscribe(cancellable) + + val value = _value.get() + if (value !== NoValue) { + observer.invoke(value as T) + } + + return cancellable + } + + override fun invoke(value: T) { + val observers = observers.get() + _value.update { value } + observers.forEach { it(value) } + } + + val value: T? get() = + _value.get().takeIf { it !== NoValue } as T? + + object NoValue +} diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt new file mode 100644 index 00000000..c96d80c5 --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -0,0 +1,64 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.AtomicRef +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.CompositeCancellable +import com.badoo.mvicore.common.Observer +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.connect +import com.badoo.mvicore.common.update + +internal class DelayUntilSource( + private val signal: Source, + private val delegate: Source +): Source { + override fun connect(observer: Observer): Cancellable = + delegate.connect(DelayedObserver(observer, signal)) + + private class DelayedObserver( + private val delegate: Observer, + signal: Source + ) : Observer { + private val passThrough = AtomicRef(false) + private val isCompleted = AtomicRef(false) + private val events: AtomicRef> = AtomicRef(emptyList()) + private val cancellable = signal.connect { if (it) send() } + + override fun invoke(value: T) { + if (passThrough.get()) { + delegate(value) + } else { + events.update { it + value } + } + } + + private fun send() { + passThrough.compareAndSet(false, true) + + val events = this.events.get() + events.forEach { delegate(it) } + this.events.update { emptyList() } + + completeDownstream() + } + + override fun onSubscribe(cancellable: Cancellable) { + delegate.onSubscribe(CompositeCancellable(cancellable, this.cancellable)) + } + + override fun onComplete() { + isCompleted.compareAndSet(false, true) + completeDownstream() + } + + private fun completeDownstream() { + if (isCompleted.get() && (passThrough.get() || cancellable.isCancelled)) { + delegate.onComplete() + } + } + + override fun onError(throwable: Throwable) { + delegate.onError(throwable) + } + } +} diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt new file mode 100644 index 00000000..be661c61 --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -0,0 +1,31 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Observer +import com.badoo.mvicore.common.Source + +internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { + override fun connect(observer: Observer): Cancellable = + delegate.connect(MapNotNullObserver(observer, mapper)) + + private class MapNotNullObserver( + val delegate: Observer, + private val mapper: (Out) -> In? + ): Observer { + override fun invoke(value: Out) { + mapper(value)?.let { delegate(it) } + } + + override fun onSubscribe(cancellable: Cancellable) { + delegate.onSubscribe(cancellable) + } + + override fun onComplete() { + delegate.onComplete() + } + + override fun onError(throwable: Throwable) { + delegate.onError(throwable) + } + } +} diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt new file mode 100644 index 00000000..c7884f17 --- /dev/null +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt @@ -0,0 +1,26 @@ +package com.badoo.mvicore.common.sources + +import com.badoo.mvicore.common.Cancellable +import com.badoo.mvicore.common.Observer +import com.badoo.mvicore.common.Source +import com.badoo.mvicore.common.cancellableOf + +class ValueSource(vararg values: T) : Source { + private val valuesToEmit = values + + override fun connect(observer: Observer): Cancellable { + val cancellable = cancellableOf { } + observer.onSubscribe(cancellable) + + valuesToEmit.forEach { + observer.invoke(it) + } + + if (!cancellable.isCancelled) { + observer.onComplete() + } + + return cancellable + } +} + diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt similarity index 100% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt rename to mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt similarity index 97% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt rename to mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt index adf609b0..1d607629 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -4,6 +4,7 @@ import com.badoo.mvicore.common.TestSink import com.badoo.mvicore.common.actor import com.badoo.mvicore.common.assertValues import com.badoo.mvicore.common.bootstrapper +import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.newsPublisher import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource @@ -45,7 +46,7 @@ class ActorReducerFeatureTest { @Test fun feature_updates_states_on_init_with_bootstrapper() { - val feature = TestActorReducerFeatureWBootstrapper() + val feature = TestBaseFeatureWBootstrapper() val sink = TestSink() feature.connect(sink) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt new file mode 100644 index 00000000..9d06a23f --- /dev/null +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -0,0 +1,126 @@ +package com.badoo.mvicore.common.feature + +import com.badoo.mvicore.common.TestSink +import com.badoo.mvicore.common.actor +import com.badoo.mvicore.common.assertValues +import com.badoo.mvicore.common.bootstrapper +import com.badoo.mvicore.common.connect +import com.badoo.mvicore.common.newsPublisher +import com.badoo.mvicore.common.reducer +import com.badoo.mvicore.common.sources.ValueSource +import kotlin.test.Test + +class BaseFeatureTest { + + @Test + fun feature_emits_initial_state_on_connect() { + val feature = TestBaseFeature() + val sink = TestSink() + + feature.connect(sink) + + sink.assertValues("") + } + + @Test + fun feature_emits_states_on_each_wish() { + val feature = TestBaseFeature() + val sink = TestSink() + + feature.connect(sink) + feature.invoke(0) + + sink.assertValues("", "0") + } + + @Test + fun feature_emits_states_on_each_actor_emission() { + val feature = TestBaseFeature() + val sink = TestSink() + + feature.connect(sink) + feature.invoke(1) + + sink.assertValues("", "1", "12") + } + + @Test + fun feature_updates_states_on_init_with_bootstrapper() { + val feature = TestBaseFeatureWBootstrapper() + val sink = TestSink() + + feature.connect(sink) + + sink.assertValues("0") + } + + @Test + fun feature_emits_news_for_each_state_update() { + val feature = TestActorReducerFeatureWNews() + val stateSink = TestSink() + val newsSink = TestSink() + + feature.news.connect(newsSink) + feature.connect(stateSink) + feature.invoke(0) + feature.invoke(1) + + stateSink.assertValues("", "0", "01", "012") + newsSink.assertValues(0, 2) + } + + @Test + fun feature_stops_emitting_after_cancel() { + val feature = TestActorReducerFeatureWNews() + val stateSink = TestSink() + val newsSink = TestSink() + + feature.news.connect(newsSink) + feature.connect(stateSink) + + feature.invoke(0) + feature.cancel() + + feature.invoke(1) + + stateSink.assertValues("", "0") + newsSink.assertValues(0) + + } +} + +class TestBaseFeature(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + } +) + +class TestBaseFeatureWBootstrapper(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + }, + bootstrapper = bootstrapper { + ValueSource(0) + } +) + +class TestBaseFeatureWNews(initialState: String = ""): ActorReducerFeature( + initialState = initialState, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) + }, + reducer = reducer { state, effect -> + state + effect.toString() + }, + newsPublisher = newsPublisher { old, action, effect, new -> + if (effect % 2 == 0) effect else null + } +) diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt similarity index 98% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt rename to mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt index f4642b5d..f1aaab89 100644 --- a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt @@ -3,6 +3,7 @@ package com.badoo.mvicore.common.feature import com.badoo.mvicore.common.TestSink import com.badoo.mvicore.common.assertValues import com.badoo.mvicore.common.bootstrapper +import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource import kotlin.test.Test diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt similarity index 100% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt rename to mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt diff --git a/mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt similarity index 100% rename from mvicore-base/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt rename to mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt diff --git a/mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt b/mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt similarity index 100% rename from mvicore-base/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt rename to mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt diff --git a/mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt b/mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt similarity index 100% rename from mvicore-base/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt rename to mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt diff --git a/mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt b/mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt similarity index 100% rename from mvicore-base/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt rename to mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt diff --git a/mvicore-base/src/nativeTest/kotlin/reducerFeature.kt b/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt similarity index 100% rename from mvicore-base/src/nativeTest/kotlin/reducerFeature.kt rename to mvicore-common/src/nativeTest/kotlin/reducerFeature.kt diff --git a/mvicore-rx/build.gradle b/mvicore-rx/build.gradle index 9386af27..e487c9b2 100644 --- a/mvicore-rx/build.gradle +++ b/mvicore-rx/build.gradle @@ -9,7 +9,7 @@ dependencies { def deps = rootProject.ext.deps implementation deps('io.reactivex.rxjava2:rxjava') - implementation project(':mvicore-base') + implementation project(':mvicore-common') implementation deps('io.reactivex.rxjava2:rxkotlin') implementation deps("org.jetbrains.kotlin:kotlin-stdlib-jdk7") diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt index 40f8ce9d..a0637b6d 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt @@ -1,28 +1,29 @@ package com.badoo.mvicore.rx import com.badoo.mvicore.common.Cancellable -import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.cancellableOf import com.badoo.mvicore.common.connect import io.reactivex.Observable import io.reactivex.ObservableSource import io.reactivex.Observer +import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposables +import com.badoo.mvicore.common.Observer as MVICoreObserver fun ObservableSource.toSource(): Source = SourceAdapter(Observable.wrap(this)) fun Source.toObservable(): ObservableSource = ObservableAdapter(this) internal class SourceAdapter(internal val delegate: Observable): Source { - private val disposable = Disposables.empty() - - override fun connect(sink: Sink): Cancellable { - val disposable = delegate.subscribe { sink(it) } - return cancellableOf { - disposable.dispose() - } - } + override fun connect(observer: MVICoreObserver): Cancellable = + DisposableAdapter( + delegate.subscribe( + observer::invoke, + observer::onError, + observer::onComplete, + { observer.onSubscribe(DisposableAdapter(it)) } + ) + ) override fun equals(other: Any?): Boolean = other is SourceAdapter<*> && super.equals(other.delegate) @@ -34,6 +35,16 @@ internal class SourceAdapter(internal val delegate: Observable): Source delegate.toString() } +internal class DisposableAdapter(private val delegate: Disposable): Cancellable { + override fun cancel() { + delegate.dispose() + } + + override val isCancelled: Boolean + get() = delegate.isDisposed +} + + internal class ObservableAdapter(private val delegate: Source): ObservableSource { override fun subscribe(observer: Observer) { try { diff --git a/scripts/run-tests-travis.sh b/scripts/run-tests-travis.sh index f51271d9..28444f8c 100755 --- a/scripts/run-tests-travis.sh +++ b/scripts/run-tests-travis.sh @@ -2,7 +2,7 @@ ./gradlew \ clean \ - :mvicore-base:jvmTest \ + :mvicore-common:jvmTest \ :mvicore-rx:test \ :mvicore-android:assembleDebug \ :mvicore-debugdrawer:assembleDebug \ diff --git a/settings.gradle b/settings.gradle index 3c709c8c..3eedde11 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,6 +1,7 @@ -include ':mvicore-base' +include ':mvicore-common' include ':mvicore-rx' +include ':benchmarks' include ':mvicore' include ':mvicore-diff' From 02125f696f76e879f921f5c58e916f0836a63a12 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 3 Jan 2020 07:29:00 +0000 Subject: [PATCH 19/31] More benches --- benchmarks/build.gradle | 2 +- .../src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt | 2 +- .../src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt | 5 +++-- .../src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 24bbbbca..c6e54899 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -14,7 +14,7 @@ dependencies { } jmh { - benchmarkMode = ['thrpt'] + benchmarkMode = ['avgt'] timeUnit = 'ms' fork = 1 warmup = '2s' diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt index bd916262..1f7be096 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt @@ -26,7 +26,7 @@ open class CommonFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + state + effect } ) diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt index ff6eff88..23201eec 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt @@ -5,6 +5,7 @@ import com.badoo.mvicore.rx.element.Reducer import com.badoo.mvicore.rx.feature.ActorReducerFeature import io.reactivex.Observable import io.reactivex.disposables.Disposable +import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Level import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -23,7 +24,7 @@ open class NewFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + state + effect } ) @@ -36,7 +37,7 @@ open class NewFeatureBench { disposable = Observable.wrap(feature).subscribe { blackhole.consume(it) } } -// @Benchmark + @Benchmark fun feature() { feature.accept(1) } diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt index 8a99ba4d..cccbff8d 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt @@ -5,6 +5,7 @@ import com.badoo.mvicore.element.Reducer import com.badoo.mvicore.feature.ActorReducerFeature import io.reactivex.Observable import io.reactivex.disposables.Disposable +import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Level import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -24,7 +25,7 @@ open class OldFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + state + effect } ) @@ -37,7 +38,7 @@ open class OldFeatureBench { disposable = Observable.wrap(feature).subscribe { blackhole.consume(it) } } -// @Benchmark + @Benchmark fun feature() { feature.accept(1) } From 743657dd22df4d8b8b1695184aff67881c075b5e Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 3 Jan 2020 07:29:46 +0000 Subject: [PATCH 20/31] Remove string operations from benches for reference numbers --- .../src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt | 2 +- benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt | 2 +- benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt index 1f7be096..bd916262 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/CommonFeatureBench.kt @@ -26,7 +26,7 @@ open class CommonFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + effect + state } ) diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt index 23201eec..a652a3d6 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/NewFeatureBench.kt @@ -24,7 +24,7 @@ open class NewFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + effect + state } ) diff --git a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt index cccbff8d..9f6d8935 100644 --- a/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt +++ b/benchmarks/src/jmh/kotlin/com/badoo/mvicore/OldFeatureBench.kt @@ -25,7 +25,7 @@ open class OldFeatureBench { }, reducer = object : Reducer { override fun invoke(state: String, effect: Int): String = - state + effect + state } ) From 2cc2c905cb32bef31cba629beeb80386c5e85ba4 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 3 Jan 2020 13:37:28 +0000 Subject: [PATCH 21/31] Use typealiases for common elements --- .../mvicore/rx/element/featureElements.kt | 15 ++++------ .../badoo/mvicore/rx/feature/BaseFeature.kt | 28 ++----------------- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt index df5f0091..606fc247 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt @@ -1,5 +1,8 @@ package com.badoo.mvicore.rx.element +import com.badoo.mvicore.common.element.NewsPublisher +import com.badoo.mvicore.common.element.PostProcessor +import com.badoo.mvicore.common.element.Reducer import io.reactivex.ObservableSource interface Bootstrapper { @@ -10,14 +13,8 @@ interface Actor { operator fun invoke(state: State, action: Action): ObservableSource } -interface Reducer { - operator fun invoke(state: State, effect: Effect): State -} +typealias Reducer = Reducer -interface NewsPublisher { - operator fun invoke(old: State, action: Action, effect: Effect, new: State): News? -} +typealias NewsPublisher = NewsPublisher -interface PostProcessor { - operator fun invoke(old: State, action: Action, effect: Effect, new: State): Action? -} +typealias PostProcessor = PostProcessor diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt index 671ce9fa..965c9520 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt @@ -27,10 +27,10 @@ abstract class BaseFeature { override fun invoke(state: State, action: Action): Source = delegate.invoke(state, action).toSource() - - } - - private class ReducerAdapter( - private val delegate: Reducer - ): com.badoo.mvicore.common.element.Reducer { - override fun invoke(state: State, effect: Wish): State = - delegate.invoke(state, effect) } private class BootstrapperAdapter( @@ -69,18 +61,4 @@ abstract class BaseFeature = delegate.invoke().toSource() } - - private class NewsPublisherAdapter( - private val delegate: NewsPublisher - ): com.badoo.mvicore.common.element.NewsPublisher { - override fun invoke(old: State, action: Action, effect: Effect, new: State): News? = - delegate.invoke(old, action, effect, new) - } - - private class PostProcessorAdapter( - private val delegate: PostProcessor - ): com.badoo.mvicore.common.element.PostProcessor{ - override fun invoke(old: State, action: Action, effect: Effect, new: State): Action? = - delegate.invoke(old, action, effect, new) - } } From 970f22eae37632707412b572769400917f9fdddf Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Mon, 6 Jan 2020 15:45:43 +0000 Subject: [PATCH 22/31] Add base feature and cancellable tests --- .../kotlin/com/badoo/mvicore/common/base.kt | 11 --- .../kotlin/com/badoo/mvicore/common/source.kt | 4 +- .../com/badoo/mvicore/common/cancellable.kt | 75 +++++++++++++++++++ .../common/feature/actorReducerFeature.kt | 2 +- .../mvicore/common/feature/baseFeature.kt | 25 ++++--- .../kotlin/com/badoo/mvicore/common/source.kt | 34 +++++++++ .../kotlin/com/badoo/mvicore/common/util.kt | 21 +++++- 7 files changed, 146 insertions(+), 26 deletions(-) create mode 100644 mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt index 8f3b5a6a..b79c66b8 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt @@ -12,14 +12,3 @@ internal inline fun AtomicRef.update(update: (T) -> T) { } while (!result) } -internal inline operator fun Array.minus(value: T): Array { - val index = indexOf(value) - return if (index == -1) { - this - } else { - val newArray = arrayOfNulls(size - 1) - copyInto(newArray, destinationOffset = 0, startIndex = 0, endIndex = index) - copyInto(newArray, destinationOffset = index, startIndex = index + 1, endIndex = size) - newArray as Array - } -} diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 40b357d0..76c8fecc 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -20,7 +20,7 @@ fun Source.connect(sink: Sink) = class PublishSource internal constructor(): Source, Sink { - private val observers: AtomicRef>> = AtomicRef(emptyArray()) + private val observers: AtomicRef>> = AtomicRef(emptyList()) override fun connect(observer: Observer): Cancellable { observers.update { it + observer } @@ -40,7 +40,7 @@ class PublishSource internal constructor(): Source, Sink { } class BehaviourSource internal constructor(initialValue: Any? = NoValue) : Source, Sink { - private val observers: AtomicRef>> = AtomicRef(emptyArray()) + private val observers: AtomicRef>> = AtomicRef(emptyList()) private val _value = AtomicRef(initialValue) override fun connect(observer: Observer): Cancellable { diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt new file mode 100644 index 00000000..a9d7e053 --- /dev/null +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -0,0 +1,75 @@ +package com.badoo.mvicore.common + +import kotlin.test.Test +import kotlin.test.assertEquals + +class CancellableTest { + @Test + fun cancellable_is_not_cancelled_by_default() { + val cancellable = cancellableOf { } + + assertEquals(cancellable.isCancelled, false) + } + + @Test + fun cancellable_action_is_executed_on_cancel() { + val sink = TestSink() + val cancellable = cancellableOf { sink(Unit) } + + cancellable.cancel() + + sink.assertValues(Unit) + } + + @Test + fun cancellable_action_is_executed_only_once_on_cancel() { + val sink = TestSink() + val cancellable = cancellableOf { sink(Unit) } + + cancellable.cancel() + cancellable.cancel() + + sink.assertValues(Unit) + } + + @Test + fun cancellable_is_cancelled_on_cancel() { + val cancellable = cancellableOf { } + + cancellable.cancel() + + assertEquals(cancellable.isCancelled, true) + } + + @Test + fun composite_cancellable_cancels_children() { + val childCancellable = cancellableOf { } + val compositeCancellable = CompositeCancellable(childCancellable) + + compositeCancellable.cancel() + + assertEquals(childCancellable.isCancelled, true) + } + + @Test + fun composite_cancellable_does_not_cancel_removed_children() { + val childCancellable = cancellableOf { } + val compositeCancellable = CompositeCancellable(childCancellable) + + compositeCancellable -= childCancellable + compositeCancellable.cancel() + + assertEquals(childCancellable.isCancelled, false) + } + + @Test + fun composite_cancellable_cancel_added_children() { + val childCancellable = cancellableOf { } + val compositeCancellable = CompositeCancellable() + + compositeCancellable += childCancellable + compositeCancellable.cancel() + + assertEquals(childCancellable.isCancelled, true) + } +} diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt index 1d607629..9a552c5f 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -46,7 +46,7 @@ class ActorReducerFeatureTest { @Test fun feature_updates_states_on_init_with_bootstrapper() { - val feature = TestBaseFeatureWBootstrapper() + val feature = TestActorReducerFeatureWBootstrapper() val sink = TestSink() feature.connect(sink) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt index 9d06a23f..494c5145 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -28,7 +28,7 @@ class BaseFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke(0) + feature.invoke("0") sink.assertValues("", "0") } @@ -39,7 +39,7 @@ class BaseFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke(1) + feature.invoke("1") sink.assertValues("", "1", "12") } @@ -56,14 +56,14 @@ class BaseFeatureTest { @Test fun feature_emits_news_for_each_state_update() { - val feature = TestActorReducerFeatureWNews() + val feature = TestBaseFeatureWNews() val stateSink = TestSink() val newsSink = TestSink() feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke(0) - feature.invoke(1) + feature.invoke("0") + feature.invoke("1") stateSink.assertValues("", "0", "01", "012") newsSink.assertValues(0, 2) @@ -71,17 +71,17 @@ class BaseFeatureTest { @Test fun feature_stops_emitting_after_cancel() { - val feature = TestActorReducerFeatureWNews() + val feature = TestBaseFeatureWNews() val stateSink = TestSink() val newsSink = TestSink() feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke(0) + feature.invoke("0") feature.cancel() - feature.invoke(1) + feature.invoke("1") stateSink.assertValues("", "0") newsSink.assertValues(0) @@ -89,8 +89,9 @@ class BaseFeatureTest { } } -class TestBaseFeature(initialState: String = ""): ActorReducerFeature( +class TestBaseFeature(initialState: String = ""): BaseFeature( initialState = initialState, + wishToAction = { it.toInt() }, actor = actor { _, wish -> if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) }, @@ -99,8 +100,9 @@ class TestBaseFeature(initialState: String = ""): ActorReducerFeature( +class TestBaseFeatureWBootstrapper(initialState: String = ""): BaseFeature( initialState = initialState, + wishToAction = { it.toInt() }, actor = actor { _, wish -> if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) }, @@ -112,8 +114,9 @@ class TestBaseFeatureWBootstrapper(initialState: String = ""): ActorReducerFeatu } ) -class TestBaseFeatureWNews(initialState: String = ""): ActorReducerFeature( +class TestBaseFeatureWNews(initialState: String = ""): BaseFeature( initialState = initialState, + wishToAction = { it.toInt() }, actor = actor { _, wish -> if (wish % 2 == 0) ValueSource(wish) else ValueSource(wish, wish + 1) }, diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt index 0938465a..c051493a 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt @@ -45,4 +45,38 @@ class SourceTest { assertEquals(emptyList(), sink.values) } + + @Test + fun source_calls_on_subscribe_with_cancellable() { + val source = source() + val observer = TestObserver() + + source.connect(observer) + + assertEquals(observer.onSubscribeEvents.size, 1) + } + + @Test + fun source_sends_correct_cancellable_downstream() { + val source = source() + val observer = TestObserver() + + source.connect(observer) + + val cancellable = observer.onSubscribeEvents.first() + cancellable.cancel() + assertEquals(observer.onCompleteEvents.size, 1) + } + + + @Test + fun source_completes_observer_on_cancel() { + val source = source() + val observer = TestObserver() + + val cancellable = source.connect(observer) + cancellable.cancel() + + assertEquals(observer.onCompleteEvents.size, 1) + } } diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index f6c779b9..900dd96a 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -6,7 +6,7 @@ import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.Reducer import kotlin.test.assertEquals -class TestSink: Sink { +open class TestSink: Sink { private val _values = AtomicRef>(emptyList()) val values: List get() = _values.get() @@ -22,6 +22,25 @@ fun TestSink.assertValues(vararg values: T) = fun TestSink.assertNoValues() = assertEquals(emptyList(), this.values) +class TestObserver: TestSink(), Observer { + val onSubscribeEvents = mutableListOf() + val onCompleteEvents = mutableListOf() + val onErrorEvents = mutableListOf() + + override fun onSubscribe(cancellable: Cancellable) { + onSubscribeEvents += cancellable + } + + override fun onComplete() { + onCompleteEvents += Unit + } + + override fun onError(throwable: Throwable) { + onErrorEvents += throwable + } + +} + fun reducer(block: (state: State, effect: Effect) -> State) = object : Reducer { override fun invoke(state: State, effect: Effect): State = block(state, effect) From 9e0b1e29867ea5b3718ff958d579896f6ada6999 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 7 Jan 2020 03:12:20 +0000 Subject: [PATCH 23/31] Add test to check states after crash --- .../mvicore/common/feature/baseFeature.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt index 494c5145..0dbf634f 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -9,6 +9,7 @@ import com.badoo.mvicore.common.newsPublisher import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource import kotlin.test.Test +import kotlin.test.assertFailsWith class BaseFeatureTest { @@ -33,6 +34,21 @@ class BaseFeatureTest { sink.assertValues("", "0") } + @Test + fun feature_emits_states_after_crash() { + val feature = TestBaseFeatureCrashingActor() + val sink = TestSink() + + feature.connect(sink) + + assertFailsWith(IllegalArgumentException::class) { + feature.invoke("1") + } + feature.invoke("0") + + sink.assertValues("", "0") + } + @Test fun feature_emits_states_on_each_actor_emission() { val feature = TestBaseFeature() @@ -127,3 +143,14 @@ class TestBaseFeatureWNews(initialState: String = ""): BaseFeature( + initialState = initialState, + wishToAction = { it.toInt() }, + actor = actor { _, wish -> + if (wish % 2 == 0) ValueSource(wish) else throw IllegalArgumentException("Wish is odd") + }, + reducer = reducer { state, effect -> + state + effect.toString() + } +) From fe54594bd0fe544571fdd98aacee2f04a0cf40a4 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 7 Jan 2020 03:21:40 +0000 Subject: [PATCH 24/31] Fix native frozen tests --- .../com/badoo/mvicore/common/feature/actorReducerFeature.kt | 2 +- .../kotlin/com/badoo/mvicore/common/feature/baseFeature.kt | 2 +- mvicore-common/src/nativeTest/kotlin/reducerFeature.kt | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt index 9a552c5f..04ed5655 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -120,7 +120,7 @@ class TestActorReducerFeatureWNews(initialState: String = ""): ActorReducerFeatu reducer = reducer { state, effect -> state + effect.toString() }, - newsPublisher = newsPublisher { old, action, effect, new -> + newsPublisher = newsPublisher { _, _, effect, _ -> if (effect % 2 == 0) effect else null } ) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt index 0dbf634f..0a61e11b 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -139,7 +139,7 @@ class TestBaseFeatureWNews(initialState: String = ""): BaseFeature state + effect.toString() }, - newsPublisher = newsPublisher { old, action, effect, new -> + newsPublisher = newsPublisher { _, _, effect, _ -> if (effect % 2 == 0) effect else null } ) diff --git a/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt b/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt index bc145f00..ccd88ae3 100644 --- a/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt +++ b/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt @@ -1,5 +1,6 @@ import com.badoo.mvicore.common.TestSink import com.badoo.mvicore.common.assertValues +import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.element.Reducer import com.badoo.mvicore.common.feature.ReducerFeature import kotlin.native.concurrent.freeze From 2c6624b4bad8e5d34fe34567a952091b0bbf9ec9 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Tue, 7 Jan 2020 03:46:47 +0000 Subject: [PATCH 25/31] Sneak out linux tests into travis build --- mvicore-common/build.gradle | 4 ++++ scripts/run-tests-travis.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/mvicore-common/build.gradle b/mvicore-common/build.gradle index 50036c0f..bd45ae28 100644 --- a/mvicore-common/build.gradle +++ b/mvicore-common/build.gradle @@ -23,6 +23,7 @@ kotlin { framework() } } + linuxX64 { } targets { fromPreset(presets.macosX64, 'native') // remove when multiple targets are setup @@ -66,5 +67,8 @@ kotlin { macosX64Main { dependsOn nativeMain } macosX64Test { dependsOn nativeTest } + + linuxX64Main { dependsOn nativeMain } + linuxX64Test { dependsOn nativeTest } } } diff --git a/scripts/run-tests-travis.sh b/scripts/run-tests-travis.sh index 28444f8c..71d68ef6 100755 --- a/scripts/run-tests-travis.sh +++ b/scripts/run-tests-travis.sh @@ -3,6 +3,7 @@ ./gradlew \ clean \ :mvicore-common:jvmTest \ + :mvicore-common:linuxX64Test \ :mvicore-rx:test \ :mvicore-android:assembleDebug \ :mvicore-debugdrawer:assembleDebug \ From aaf683a792e4da3dd38e9bd0a2aa24911b8d837e Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Fri, 8 May 2020 22:52:16 +0100 Subject: [PATCH 26/31] Use reaktive utils package --- build.gradle | 12 ++++- mvicore-common/build.gradle | 4 +- .../kotlin/com/badoo/mvicore/common/base.kt | 14 ------ .../com/badoo/mvicore/common/binder/Binder.kt | 33 ++++++------- .../com/badoo/mvicore/common/cancellable.kt | 18 +++++--- .../kotlin/com/badoo/mvicore/common/sink.kt | 4 +- .../kotlin/com/badoo/mvicore/common/source.kt | 17 ++++--- .../common/sources/DelayUntilSource.kt | 17 +++---- .../kotlin/com/badoo/mvicore/common/binder.kt | 45 +++++++++--------- .../com/badoo/mvicore/common/cancellable.kt | 13 +++--- .../common/feature/actorReducerFeature.kt | 13 +++--- .../mvicore/common/feature/baseFeature.kt | 15 +++--- .../mvicore/common/feature/reducerFeature.kt | 13 +++--- .../kotlin/com/badoo/mvicore/common/source.kt | 15 +++--- .../kotlin/com/badoo/mvicore/common/util.kt | 6 ++- .../kotlin/com/badoo/mvicore/common/baseJs.kt | 15 ------ .../com/badoo/mvicore/common/baseJvm.kt | 5 -- .../com/badoo/mvicore/common/baseNative.kt | 23 ---------- .../src/nativeTest/kotlin/reducerFeature.kt | 46 ------------------- scripts/run-tests-travis.sh | 3 +- 20 files changed, 128 insertions(+), 203 deletions(-) delete mode 100644 mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt delete mode 100644 mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt delete mode 100644 mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt delete mode 100644 mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt delete mode 100644 mvicore-common/src/nativeTest/kotlin/reducerFeature.kt diff --git a/build.gradle b/build.gradle index 730f918f..edb941ff 100644 --- a/build.gradle +++ b/build.gradle @@ -1,11 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlinVersion = '1.3.61' + ext.kotlinVersion = '1.3.72' repositories { google() jcenter() mavenCentral() + maven { + url "https://dl.bintray.com/badoo/maven" + } } dependencies { classpath 'com.android.tools.build:gradle:3.5.0' @@ -27,6 +30,7 @@ ext { rxJavaVersion = '2.2.10' rxKotlinVersion = '2.2.0' rxAndroidVersion = '2.1.1' + reaktiveVersion = '1.1.13' // DI daggerVersion = '2.14.1' @@ -63,6 +67,9 @@ ext { "io.reactivex.rxjava2:rxkotlin" : rxKotlinVersion, "io.reactivex.rxjava2:rxandroid" : rxAndroidVersion, + // Reaktive + 'com.badoo.reaktive:utils' : reaktiveVersion, + // DI "com.google.dagger:dagger" : daggerVersion, "com.google.dagger:dagger-android" : daggerVersion, @@ -120,6 +127,9 @@ allprojects { maven { url "https://plugins.gradle.org/m2/" } + maven { + url "https://dl.bintray.com/badoo/maven" + } } } diff --git a/mvicore-common/build.gradle b/mvicore-common/build.gradle index bd45ae28..ba0ec706 100644 --- a/mvicore-common/build.gradle +++ b/mvicore-common/build.gradle @@ -33,6 +33,7 @@ kotlin { commonMain { dependencies { implementation kotlin('stdlib-common') + implementation deps('com.badoo.reaktive:utils') } } commonTest { @@ -62,8 +63,7 @@ kotlin { } } nativeMain { dependsOn commonMain } - // Empty source set is required in order to have native tests task - nativeTest { } + nativeTest { dependsOn commonTest } macosX64Main { dependsOn nativeMain } macosX64Test { dependsOn nativeTest } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt deleted file mode 100644 index b79c66b8..00000000 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/base.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.badoo.mvicore.common - -expect class AtomicRef(initialValue: V) { - fun compareAndSet(expect: V, update: V): Boolean - fun get(): V -} - -internal inline fun AtomicRef.update(update: (T) -> T) { - do { - val oldValue = get() - val result = compareAndSet(oldValue, update(oldValue)) - } while (!result) -} - diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 99569ed6..5221ecab 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -1,6 +1,5 @@ package com.badoo.mvicore.common.binder -import com.badoo.mvicore.common.AtomicRef import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.PublishSource @@ -12,17 +11,19 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.BEGIN import com.badoo.mvicore.common.lifecycle.Lifecycle.Event.END import com.badoo.mvicore.common.source import com.badoo.mvicore.common.sources.DelayUntilSource -import com.badoo.mvicore.common.update +import com.badoo.reaktive.utils.atomic.AtomicBoolean +import com.badoo.reaktive.utils.atomic.AtomicReference +import com.badoo.reaktive.utils.atomic.update /** * Establishes connections between [Source] and [Sink] endpoints * NOTE: binder is not thread safe. All the emissions should happen on single thread (preferably main). */ -abstract class Binder : Cancellable { - internal abstract fun connect(connection: Connection) +interface Binder : Cancellable { + fun connect(connection: Connection) } -internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { +internal class SimpleBinder(init: Binder.() -> Unit) : Binder { private val cancellables = CompositeCancellable() /** @@ -33,7 +34,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { * #cancel() * from xx internalSource -> to // Remaining events from `internalSource` are propagated to `to` */ - private val internalSourceCache = AtomicRef(emptyMap, PublishSource<*>>()) + private val internalSourceCache = AtomicReference(emptyMap, PublishSource<*>>()) /** * Delay events emitted on subscribe until `init` lambda is executed @@ -63,7 +64,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { } private fun getInternalSourceFor(from: Source): PublishSource { - val cachedSource = internalSourceCache.get()[from] + val cachedSource = internalSourceCache.value[from] return if (cachedSource != null) { cachedSource as PublishSource } else { @@ -83,11 +84,11 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder() { get() = cancellables.isCancelled } -internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : Binder() { - private var lifecycleActive = AtomicRef(false) +internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : Binder { + private var lifecycleActive = AtomicBoolean(false) private val cancellables = CompositeCancellable() private val innerBinder = SimpleBinder(init) - private val connections = AtomicRef(emptyArray>()) + private val connections = AtomicReference(emptyArray>()) init { cancellables += lifecycle.connect { @@ -101,22 +102,22 @@ internal class LifecycleBinder(lifecycle: Lifecycle, init: Binder.() -> Unit) : override fun connect(connection: Connection) { connections.update { it + connection } - if (lifecycleActive.get()) { + if (lifecycleActive.value) { innerBinder.connect(connection) } } private fun connect() { - if (lifecycleActive.get()) return + if (lifecycleActive.value) return - lifecycleActive.update { true } - connections.get().forEach { innerBinder.connect(it) } + lifecycleActive.value = true + connections.value.forEach { innerBinder.connect(it) } } private fun disconnect() { - if (!lifecycleActive.get()) return + if (!lifecycleActive.value) return - lifecycleActive.update { false } + lifecycleActive.value = false innerBinder.cancel() } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt index 6f2ac3cf..7de3097d 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -1,5 +1,9 @@ package com.badoo.mvicore.common +import com.badoo.reaktive.utils.atomic.AtomicBoolean +import com.badoo.reaktive.utils.atomic.AtomicReference +import com.badoo.reaktive.utils.atomic.update + interface Cancellable { fun cancel() val isCancelled: Boolean @@ -9,14 +13,14 @@ fun cancellableOf(onCancel: Cancellable.() -> Unit): Cancellable = CancellableIm fun cancelled(): Cancellable = CancelledCancellable() private class CancellableImpl(private val onCancel: Cancellable.() -> Unit): Cancellable { - private var isCancelledRef = AtomicRef(false) + private var isCancelledRef = AtomicBoolean(false) override val isCancelled: Boolean - get() = isCancelledRef.get() + get() = isCancelledRef.value override fun cancel() { if (!isCancelled) { - isCancelledRef.update { true } + isCancelledRef.value = true onCancel() } } @@ -30,9 +34,9 @@ private class CancelledCancellable : Cancellable { } class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { - private val cancellableListRef: AtomicRef> = AtomicRef(hashSetOf(*cancellables)) + private val cancellableListRef: AtomicReference> = AtomicReference(hashSetOf(*cancellables)) private val internalCancellable = CancellableImpl { - val list = cancellableListRef.get() + val list = cancellableListRef.value list.forEach { it.cancel() } cancellableListRef.update { emptySet() } } @@ -59,8 +63,8 @@ class CompositeCancellable(vararg cancellables: Cancellable): Cancellable { } } -internal fun AtomicRef.cancel() { - val value = get() +internal fun AtomicReference.cancel() { + val value = value if (value != null) { value.cancel() compareAndSet(value, null) diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt index 3d3a02da..90650547 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt @@ -1,5 +1,7 @@ package com.badoo.mvicore.common +import com.badoo.reaktive.utils.atomic.AtomicReference + interface Sink { operator fun invoke(value: T) } @@ -22,7 +24,7 @@ fun ((T) -> Unit).toObserver(): Observer = ObserverFromAction(this) private class ObserverFromAction(val action: (T) -> Unit): Observer { - private val cancellable: AtomicRef = AtomicRef(null) + private val cancellable: AtomicReference = AtomicReference(null) override fun invoke(value: T) { action(value) diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 76c8fecc..26e50d28 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -1,5 +1,8 @@ package com.badoo.mvicore.common +import com.badoo.reaktive.utils.atomic.AtomicReference +import com.badoo.reaktive.utils.atomic.update + /** * NOTE: in conversions from other frameworks you need to override equals and hashCode * to support binder "emit after dispose" functionality @@ -20,7 +23,7 @@ fun Source.connect(sink: Sink) = class PublishSource internal constructor(): Source, Sink { - private val observers: AtomicRef>> = AtomicRef(emptyList()) + private val observers: AtomicReference>> = AtomicReference(emptyList()) override fun connect(observer: Observer): Cancellable { observers.update { it + observer } @@ -34,14 +37,14 @@ class PublishSource internal constructor(): Source, Sink { } override fun invoke(value: T) { - val observers = observers.get() + val observers = observers.value observers.forEach { it(value) } } } class BehaviourSource internal constructor(initialValue: Any? = NoValue) : Source, Sink { - private val observers: AtomicRef>> = AtomicRef(emptyList()) - private val _value = AtomicRef(initialValue) + private val observers: AtomicReference>> = AtomicReference(emptyList()) + private val _value = AtomicReference(initialValue) override fun connect(observer: Observer): Cancellable { observers.update { it + observer } @@ -52,7 +55,7 @@ class BehaviourSource internal constructor(initialValue: Any? = NoValue) : So } observer.onSubscribe(cancellable) - val value = _value.get() + val value = _value.value if (value !== NoValue) { observer.invoke(value as T) } @@ -61,13 +64,13 @@ class BehaviourSource internal constructor(initialValue: Any? = NoValue) : So } override fun invoke(value: T) { - val observers = observers.get() + val observers = observers.value _value.update { value } observers.forEach { it(value) } } val value: T? get() = - _value.get().takeIf { it !== NoValue } as T? + _value.value.takeIf { it !== NoValue } as T? object NoValue } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt index c96d80c5..50e528b9 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -1,12 +1,13 @@ package com.badoo.mvicore.common.sources -import com.badoo.mvicore.common.AtomicRef import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.CompositeCancellable import com.badoo.mvicore.common.Observer import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.connect -import com.badoo.mvicore.common.update +import com.badoo.reaktive.utils.atomic.AtomicBoolean +import com.badoo.reaktive.utils.atomic.AtomicReference +import com.badoo.reaktive.utils.atomic.update internal class DelayUntilSource( private val signal: Source, @@ -19,13 +20,13 @@ internal class DelayUntilSource( private val delegate: Observer, signal: Source ) : Observer { - private val passThrough = AtomicRef(false) - private val isCompleted = AtomicRef(false) - private val events: AtomicRef> = AtomicRef(emptyList()) + private val passThrough = AtomicBoolean(false) + private val isCompleted = AtomicBoolean(false) + private val events: AtomicReference> = AtomicReference(emptyList()) private val cancellable = signal.connect { if (it) send() } override fun invoke(value: T) { - if (passThrough.get()) { + if (passThrough.value) { delegate(value) } else { events.update { it + value } @@ -35,7 +36,7 @@ internal class DelayUntilSource( private fun send() { passThrough.compareAndSet(false, true) - val events = this.events.get() + val events = this.events.value events.forEach { delegate(it) } this.events.update { emptyList() } @@ -52,7 +53,7 @@ internal class DelayUntilSource( } private fun completeDownstream() { - if (isCompleted.get() && (passThrough.get() || cancellable.isCancelled)) { + if (isCompleted.value && (passThrough.value || cancellable.isCancelled)) { delegate.onComplete() } } diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 45d8bab6..7d42b80b 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -5,6 +5,7 @@ import com.badoo.mvicore.common.binder.bind import com.badoo.mvicore.common.binder.binder import com.badoo.mvicore.common.binder.using import com.badoo.mvicore.common.lifecycle.Lifecycle +import com.badoo.reaktive.utils.freeze import kotlin.test.Test class BinderTest { @@ -13,7 +14,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink() { - binder().apply { + binder().freeze().apply { bind(source to sink) } @@ -23,7 +24,7 @@ class BinderTest { @Test fun binder_without_lifecycle_does_not_connect_source_and_sink_after_cancel() { - val binder = binder().apply { + val binder = binder().freeze().apply { bind(source to sink) } @@ -35,7 +36,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_using_mapper() { - binder().apply { + binder().freeze().apply { bind(source to sink using { it + 1 }) } @@ -45,7 +46,7 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_skips_nulls_from_mapper() { - binder().apply { + binder().freeze().apply { bind(source to sink using { if (it % 2 == 0) null else it }) } @@ -57,8 +58,8 @@ class BinderTest { @Test fun binder_without_lifecycle_connects_source_and_sink_using_connector() { - val connector = NotNullConnector { it } - binder().apply { + val connector = NotNullConnector { it }.freeze() + binder().freeze().apply { bind(source to sink using connector) } @@ -68,8 +69,8 @@ class BinderTest { @Test fun binder_with_lifecycle_connects_source_and_sink_when_active() { - val lifecycle = Lifecycle.manual() - binder(lifecycle).apply { + val lifecycle = Lifecycle.manual().freeze() + binder(lifecycle).freeze().apply { bind(source to sink) } @@ -81,8 +82,8 @@ class BinderTest { @Test fun binder_with_lifecycle_does_not_connect_source_and_sink_before_active() { - val lifecycle = Lifecycle.manual() - binder(lifecycle).apply { + val lifecycle = Lifecycle.manual().freeze() + binder(lifecycle).freeze().apply { bind(source to sink) } @@ -94,8 +95,8 @@ class BinderTest { @Test fun binder_with_lifecycle_disconnect_source_and_sink_after_end() { - val lifecycle = Lifecycle.manual() - binder(lifecycle).apply { + val lifecycle = Lifecycle.manual().freeze() + binder(lifecycle).freeze().apply { bind(source to sink) } @@ -109,8 +110,8 @@ class BinderTest { @Test fun binder_with_lifecycle_reconnect_source_and_sink_after_begin() { - val lifecycle = Lifecycle.manual() - binder(lifecycle).apply { + val lifecycle = Lifecycle.manual().freeze() + binder(lifecycle).freeze().apply { bind(source to sink) } @@ -126,8 +127,8 @@ class BinderTest { @Test fun binder_with_lifecycle_does_not_reconnect_source_and_sink_after_cancel() { - val lifecycle = Lifecycle.manual() - val binder = binder(lifecycle).apply { + val lifecycle = Lifecycle.manual().freeze() + val binder = binder(lifecycle).freeze().apply { bind(source to sink) } @@ -143,10 +144,10 @@ class BinderTest { @Test fun binder_with_lifecycle_connects_source_and_sink_if_lifecycle_started() { - val lifecycle = Lifecycle.manual() + val lifecycle = Lifecycle.manual().freeze() lifecycle.begin() - binder(lifecycle).apply { + binder(lifecycle).freeze().apply { bind(source to sink) } @@ -174,19 +175,19 @@ class BinderTest { @Test fun binder_covariant_endpoints_compile_for_pair() { val sink = sinkOf { /* no-op */ } - binder().bind(source to sink) + binder().freeze().bind(source to sink) } @Test fun binder_covariant_endpoints_compile_for_connection() { val sink = sinkOf { _: Any -> /* no-op */ } val intToString: (Int) -> String = { it.toString() } - binder().bind(source to sink using intToString) + binder().freeze().bind(source to sink using intToString) } @Test fun binder_delivers_message_to_all_sinks_on_dispose() { - val binder = binder() + val binder = binder().freeze() val sink2 = sinkOf { _: Int -> binder.cancel() } @@ -205,7 +206,7 @@ class BinderTest { bind(source to passThroughSource) source.invoke(0) bind(passThroughSource to sink) - } + }.freeze() sink.assertValues(0) } diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt index a9d7e053..3e467261 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -1,5 +1,6 @@ package com.badoo.mvicore.common +import com.badoo.reaktive.utils.freeze import kotlin.test.Test import kotlin.test.assertEquals @@ -14,7 +15,7 @@ class CancellableTest { @Test fun cancellable_action_is_executed_on_cancel() { val sink = TestSink() - val cancellable = cancellableOf { sink(Unit) } + val cancellable = cancellableOf { sink(Unit) }.freeze() cancellable.cancel() @@ -24,7 +25,7 @@ class CancellableTest { @Test fun cancellable_action_is_executed_only_once_on_cancel() { val sink = TestSink() - val cancellable = cancellableOf { sink(Unit) } + val cancellable = cancellableOf { sink(Unit) }.freeze() cancellable.cancel() cancellable.cancel() @@ -34,7 +35,7 @@ class CancellableTest { @Test fun cancellable_is_cancelled_on_cancel() { - val cancellable = cancellableOf { } + val cancellable = cancellableOf { }.freeze() cancellable.cancel() @@ -44,7 +45,7 @@ class CancellableTest { @Test fun composite_cancellable_cancels_children() { val childCancellable = cancellableOf { } - val compositeCancellable = CompositeCancellable(childCancellable) + val compositeCancellable = CompositeCancellable(childCancellable).freeze() compositeCancellable.cancel() @@ -54,7 +55,7 @@ class CancellableTest { @Test fun composite_cancellable_does_not_cancel_removed_children() { val childCancellable = cancellableOf { } - val compositeCancellable = CompositeCancellable(childCancellable) + val compositeCancellable = CompositeCancellable(childCancellable).freeze() compositeCancellable -= childCancellable compositeCancellable.cancel() @@ -65,7 +66,7 @@ class CancellableTest { @Test fun composite_cancellable_cancel_added_children() { val childCancellable = cancellableOf { } - val compositeCancellable = CompositeCancellable() + val compositeCancellable = CompositeCancellable().freeze() compositeCancellable += childCancellable compositeCancellable.cancel() diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt index 04ed5655..0b9fa484 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -8,13 +8,14 @@ import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.newsPublisher import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource +import com.badoo.reaktive.utils.freeze import kotlin.test.Test class ActorReducerFeatureTest { @Test fun feature_emits_initial_state_on_connect() { - val feature = TestActorReducerFeature() + val feature = TestActorReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -24,7 +25,7 @@ class ActorReducerFeatureTest { @Test fun feature_emits_states_on_each_wish() { - val feature = TestActorReducerFeature() + val feature = TestActorReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -35,7 +36,7 @@ class ActorReducerFeatureTest { @Test fun feature_emits_states_on_each_actor_emission() { - val feature = TestActorReducerFeature() + val feature = TestActorReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -46,7 +47,7 @@ class ActorReducerFeatureTest { @Test fun feature_updates_states_on_init_with_bootstrapper() { - val feature = TestActorReducerFeatureWBootstrapper() + val feature = TestActorReducerFeatureWBootstrapper().freeze() val sink = TestSink() feature.connect(sink) @@ -56,7 +57,7 @@ class ActorReducerFeatureTest { @Test fun feature_emits_news_for_each_state_update() { - val feature = TestActorReducerFeatureWNews() + val feature = TestActorReducerFeatureWNews().freeze() val stateSink = TestSink() val newsSink = TestSink() @@ -71,7 +72,7 @@ class ActorReducerFeatureTest { @Test fun feature_stops_emitting_after_cancel() { - val feature = TestActorReducerFeatureWNews() + val feature = TestActorReducerFeatureWNews().freeze() val stateSink = TestSink() val newsSink = TestSink() diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt index 0a61e11b..2b7a3034 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -8,6 +8,7 @@ import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.newsPublisher import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource +import com.badoo.reaktive.utils.freeze import kotlin.test.Test import kotlin.test.assertFailsWith @@ -15,7 +16,7 @@ class BaseFeatureTest { @Test fun feature_emits_initial_state_on_connect() { - val feature = TestBaseFeature() + val feature = TestBaseFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -25,7 +26,7 @@ class BaseFeatureTest { @Test fun feature_emits_states_on_each_wish() { - val feature = TestBaseFeature() + val feature = TestBaseFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -36,7 +37,7 @@ class BaseFeatureTest { @Test fun feature_emits_states_after_crash() { - val feature = TestBaseFeatureCrashingActor() + val feature = TestBaseFeatureCrashingActor().freeze() val sink = TestSink() feature.connect(sink) @@ -51,7 +52,7 @@ class BaseFeatureTest { @Test fun feature_emits_states_on_each_actor_emission() { - val feature = TestBaseFeature() + val feature = TestBaseFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -62,7 +63,7 @@ class BaseFeatureTest { @Test fun feature_updates_states_on_init_with_bootstrapper() { - val feature = TestBaseFeatureWBootstrapper() + val feature = TestBaseFeatureWBootstrapper().freeze() val sink = TestSink() feature.connect(sink) @@ -72,7 +73,7 @@ class BaseFeatureTest { @Test fun feature_emits_news_for_each_state_update() { - val feature = TestBaseFeatureWNews() + val feature = TestBaseFeatureWNews().freeze() val stateSink = TestSink() val newsSink = TestSink() @@ -87,7 +88,7 @@ class BaseFeatureTest { @Test fun feature_stops_emitting_after_cancel() { - val feature = TestBaseFeatureWNews() + val feature = TestBaseFeatureWNews().freeze() val stateSink = TestSink() val newsSink = TestSink() diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt index f1aaab89..07730093 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt @@ -6,12 +6,13 @@ import com.badoo.mvicore.common.bootstrapper import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.reducer import com.badoo.mvicore.common.sources.ValueSource +import com.badoo.reaktive.utils.freeze import kotlin.test.Test class ReducerFeatureTest { @Test fun reducer_feature_emits_initial_state_on_connect() { - val feature = TestReducerFeature() + val feature = TestReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -21,7 +22,7 @@ class ReducerFeatureTest { @Test fun reducer_feature_emits_new_state_on_new_wish() { - val feature = TestReducerFeature() + val feature = TestReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -32,7 +33,7 @@ class ReducerFeatureTest { @Test fun reducer_feature_emits_new_state_on_new_wish_2() { - val feature = TestReducerFeature() + val feature = TestReducerFeature().freeze() val sink = TestSink() feature.connect(sink) @@ -44,7 +45,7 @@ class ReducerFeatureTest { @Test fun reducer_feature_emits_new_state_on_bootstrapper_action() { - val feature = TestReducerFeatureWBootstrapper() + val feature = TestReducerFeatureWBootstrapper().freeze() val sink = TestSink() feature.connect(sink) @@ -55,7 +56,7 @@ class ReducerFeatureTest { @Test fun reducer_feature_emits_news_on_wish() { - val feature = TestReducerFeatureWNews() + val feature = TestReducerFeatureWNews().freeze() val sink = TestSink() feature.news.connect(sink) @@ -67,7 +68,7 @@ class ReducerFeatureTest { @Test fun reducer_feature_stop_processing_events_after_cancel() { - val feature = TestReducerFeature() + val feature = TestReducerFeature().freeze() val sink = TestSink() feature.connect(sink) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt index c051493a..28d09f47 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt @@ -1,5 +1,6 @@ package com.badoo.mvicore.common +import com.badoo.reaktive.utils.freeze import kotlin.test.Test import kotlin.test.assertEquals @@ -7,7 +8,7 @@ class SourceTest { @Test fun source_connected_sink_receives_no_values() { - val source = source() + val source = source().freeze() val sink = TestSink() source.connect(sink) @@ -16,7 +17,7 @@ class SourceTest { @Test fun source_with_initial_value_connected_sink_receives_a_value() { - val source = source(0) + val source = source(0).freeze() val sink = TestSink() source.connect(sink) @@ -25,7 +26,7 @@ class SourceTest { @Test fun source_sends_value_after_connect_connected_sink_receives_a_value() { - val source = source() + val source = source().freeze() val sink = TestSink() source.connect(sink) @@ -36,7 +37,7 @@ class SourceTest { @Test fun source_sends_value_after_disconnect_connected_sink_receives_no_values() { - val source = source() + val source = source().freeze() val sink = TestSink() val cancellable = source.connect(sink) cancellable.cancel() @@ -48,7 +49,7 @@ class SourceTest { @Test fun source_calls_on_subscribe_with_cancellable() { - val source = source() + val source = source().freeze() val observer = TestObserver() source.connect(observer) @@ -58,7 +59,7 @@ class SourceTest { @Test fun source_sends_correct_cancellable_downstream() { - val source = source() + val source = source().freeze() val observer = TestObserver() source.connect(observer) @@ -71,7 +72,7 @@ class SourceTest { @Test fun source_completes_observer_on_cancel() { - val source = source() + val source = source().freeze() val observer = TestObserver() val cancellable = source.connect(observer) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index 900dd96a..109f8136 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -4,12 +4,14 @@ import com.badoo.mvicore.common.element.Actor import com.badoo.mvicore.common.element.Bootstrapper import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.Reducer +import com.badoo.reaktive.utils.atomic.AtomicReference +import com.badoo.reaktive.utils.atomic.update import kotlin.test.assertEquals open class TestSink: Sink { - private val _values = AtomicRef>(emptyList()) + private val _values = AtomicReference>(emptyList()) val values: List - get() = _values.get() + get() = _values.value override fun invoke(value: T) { _values.update { it + value } diff --git a/mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt b/mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt deleted file mode 100644 index 561ed6e1..00000000 --- a/mvicore-common/src/jsMain/kotlin/com/badoo/mvicore/common/baseJs.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.badoo.mvicore.common - -actual class AtomicRef actual constructor(initialValue: V) { - private var value: V = initialValue - - actual fun compareAndSet(expect: V, update: V): Boolean { - if (value == expect) { - value = update - return true - } - return false - } - - actual fun get(): V = value -} diff --git a/mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt b/mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt deleted file mode 100644 index 0c4a75a4..00000000 --- a/mvicore-common/src/jvmMain/kotlin/com/badoo/mvicore/common/baseJvm.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.badoo.mvicore.common - -import java.util.concurrent.atomic.AtomicReference - -actual typealias AtomicRef = AtomicReference diff --git a/mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt b/mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt deleted file mode 100644 index e29f0b7e..00000000 --- a/mvicore-common/src/nativeMain/kotlin/com/badoo/mvicore/common/baseNative.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.badoo.mvicore.common - -import kotlin.native.concurrent.FreezableAtomicReference -import kotlin.native.concurrent.freeze -import kotlin.native.concurrent.isFrozen - -actual class AtomicRef actual constructor(initialValue: V) { - - private val delegate = FreezableAtomicReference(initialValue) - - actual fun get(): V = delegate.value - - actual fun compareAndSet(expect: V, update: V): Boolean = - delegate.compareAndSet(expect, update.freezeIfFrozen()) - - private fun V.freezeIfFrozen(): V { - if (delegate.isFrozen) { - freeze() - } - - return this - } -} diff --git a/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt b/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt deleted file mode 100644 index ccd88ae3..00000000 --- a/mvicore-common/src/nativeTest/kotlin/reducerFeature.kt +++ /dev/null @@ -1,46 +0,0 @@ -import com.badoo.mvicore.common.TestSink -import com.badoo.mvicore.common.assertValues -import com.badoo.mvicore.common.connect -import com.badoo.mvicore.common.element.Reducer -import com.badoo.mvicore.common.feature.ReducerFeature -import kotlin.native.concurrent.freeze -import kotlin.test.Test - -class ReducerFeatureTest { - - @Test - fun reducer_feature_emits_new_state_on_new_wish_when_frozen() { - val feature = TestReducerFeature().freeze() - val sink = TestSink() - - feature.connect(sink) - - feature.invoke(0) - sink.assertValues("", "0") - } - - @Test - fun reducer_feature_stops_emitting_after_cancel_when_frozen() { - val feature = TestReducerFeature().freeze() - val sink = TestSink() - - feature.connect(sink) - - feature.cancel() - - feature.invoke(0) - sink.assertValues("") - } - - class TestReducerFeature(initialState: String = ""): ReducerFeature( - initialState = initialState, - reducer = reducer { state, wish -> - state + wish.toString() - } - ) -} - -fun reducer(block: (state: State, effect: Effect) -> State) = - object : Reducer { - override fun invoke(state: State, effect: Effect): State = block(state, effect) - } diff --git a/scripts/run-tests-travis.sh b/scripts/run-tests-travis.sh index 71d68ef6..9adcd763 100755 --- a/scripts/run-tests-travis.sh +++ b/scripts/run-tests-travis.sh @@ -2,8 +2,7 @@ ./gradlew \ clean \ - :mvicore-common:jvmTest \ - :mvicore-common:linuxX64Test \ + :mvicore-common:build \ :mvicore-rx:test \ :mvicore-android:assembleDebug \ :mvicore-debugdrawer:assembleDebug \ From ba45911a6e201791d1860dc1744969202f13e042 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 9 May 2020 01:02:53 +0100 Subject: [PATCH 27/31] Make observer use atomic references --- .../kotlin/com/badoo/mvicore/common/util.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index 109f8136..7d140d6a 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -25,20 +25,24 @@ fun TestSink.assertNoValues() = assertEquals(emptyList(), this.values) class TestObserver: TestSink(), Observer { - val onSubscribeEvents = mutableListOf() - val onCompleteEvents = mutableListOf() - val onErrorEvents = mutableListOf() + private val onSubscribeEventsRef = AtomicReference>(emptyList()) + private val onCompleteEventsRef = AtomicReference>(emptyList()) + private val onErrorEventsRef = AtomicReference>(emptyList()) + + val onSubscribeEvents get() = onSubscribeEventsRef.value + val onCompleteEvents get() = onCompleteEventsRef.value + val onErrorEvents get() = onErrorEventsRef.value override fun onSubscribe(cancellable: Cancellable) { - onSubscribeEvents += cancellable + onSubscribeEventsRef.update { it + cancellable } } override fun onComplete() { - onCompleteEvents += Unit + onCompleteEventsRef.update { it + Unit } } override fun onError(throwable: Throwable) { - onErrorEvents += throwable + onErrorEventsRef.update { it + throwable } } } From 33ae5f6c976ed1b61fa2c29392041d5fd2a20f8d Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 9 May 2020 01:07:16 +0100 Subject: [PATCH 28/31] Add ios targets --- mvicore-common/build.gradle | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mvicore-common/build.gradle b/mvicore-common/build.gradle index ba0ec706..4527995c 100644 --- a/mvicore-common/build.gradle +++ b/mvicore-common/build.gradle @@ -23,6 +23,16 @@ kotlin { framework() } } + iosX64 { + binaries { + framework() + } + } + iosArm64 { + binaries { + framework() + } + } linuxX64 { } targets { @@ -70,5 +80,11 @@ kotlin { linuxX64Main { dependsOn nativeMain } linuxX64Test { dependsOn nativeTest } + + iosX64Main { dependsOn nativeMain } + iosX64Test { dependsOn nativeTest } + + iosArm64Test { dependsOn nativeTest } + iosArm64Test { dependsOn nativeTest } } } From 9778557efdff236899ba78197669b504d43d523c Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 9 May 2020 04:39:54 +0100 Subject: [PATCH 29/31] Fix variance --- build.gradle | 6 +- mvicore-common/build.gradle | 18 --- .../com/badoo/mvicore/common/binder/Binder.kt | 2 +- .../badoo/mvicore/common/binder/BinderExt.kt | 2 +- .../badoo/mvicore/common/binder/Connection.kt | 10 +- .../badoo/mvicore/common/binder/Connector.kt | 4 +- .../mvicore/common/binder/NotNullConnector.kt | 4 +- .../mvicore/common/element/featureElements.kt | 12 +- .../common/feature/ActorReducerFeature.kt | 2 +- .../mvicore/common/feature/BaseFeature.kt | 12 +- .../badoo/mvicore/common/feature/Feature.kt | 2 +- .../mvicore/common/feature/ReducerFeature.kt | 8 +- .../mvicore/common/lifecycle/lifecycle.kt | 4 +- .../mvicore/common/middleware/Middleware.kt | 4 +- .../common/middleware/StandaloneMiddleware.kt | 4 +- .../kotlin/com/badoo/mvicore/common/sink.kt | 19 ++- .../kotlin/com/badoo/mvicore/common/source.kt | 16 +-- .../common/sources/DelayUntilSource.kt | 8 +- .../common/sources/MapNotNullSource.kt | 6 +- .../mvicore/common/sources/ValueSource.kt | 4 +- .../kotlin/com/badoo/mvicore/common/binder.kt | 40 +++--- .../com/badoo/mvicore/common/cancellable.kt | 4 +- .../common/feature/actorReducerFeature.kt | 12 +- .../mvicore/common/feature/baseFeature.kt | 16 +-- .../mvicore/common/feature/reducerFeature.kt | 14 +-- .../kotlin/com/badoo/mvicore/common/source.kt | 4 +- .../kotlin/com/badoo/mvicore/common/util.kt | 2 +- mvicore-rx/build.gradle | 53 ++------ .../main/java/com/badoo/mvicore/rx/Sink.kt | 4 +- .../main/java/com/badoo/mvicore/rx/Source.kt | 2 +- .../com/badoo/mvicore/rx/binder/Connector.kt | 8 +- .../mvicore/rx/element/featureElements.kt | 8 +- .../badoo/mvicore/rx/feature/BaseFeature.kt | 9 +- .../com/badoo/mvicore/rx/feature/Feature.kt | 3 +- .../mvicore/rx/feature/ReducerFeature.kt | 2 +- .../mvicore/rx/feature/reducerFeature.kt | 115 ++++++++++++++++++ 36 files changed, 256 insertions(+), 187 deletions(-) create mode 100644 mvicore-rx/src/test/java/com/badoo/mvicore/rx/feature/reducerFeature.kt diff --git a/build.gradle b/build.gradle index edb941ff..1cbd67f3 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.5.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.17" classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' @@ -55,6 +55,8 @@ ext { depsWithVersion = [ // Kotlin "org.jetbrains.kotlin:kotlin-stdlib-jdk7" : kotlinVersion, + "org.jetbrains.kotlin:kotlin-stdlib-jdk8" : kotlinVersion, + "org.jetbrains.kotlin:kotlin-stdlib" : kotlinVersion, // Android "android.arch.lifecycle:common-java8" : androidArchLifecycleVersion, @@ -68,7 +70,7 @@ ext { "io.reactivex.rxjava2:rxandroid" : rxAndroidVersion, // Reaktive - 'com.badoo.reaktive:utils' : reaktiveVersion, + 'com.badoo.reaktive:utils' : reaktiveVersion, // DI "com.google.dagger:dagger" : daggerVersion, diff --git a/mvicore-common/build.gradle b/mvicore-common/build.gradle index 4527995c..ec4d9950 100644 --- a/mvicore-common/build.gradle +++ b/mvicore-common/build.gradle @@ -35,10 +35,6 @@ kotlin { } linuxX64 { } - targets { - fromPreset(presets.macosX64, 'native') // remove when multiple targets are setup - } - sourceSets { commonMain { dependencies { @@ -72,19 +68,5 @@ kotlin { implementation kotlin('test-junit') } } - nativeMain { dependsOn commonMain } - nativeTest { dependsOn commonTest } - - macosX64Main { dependsOn nativeMain } - macosX64Test { dependsOn nativeTest } - - linuxX64Main { dependsOn nativeMain } - linuxX64Test { dependsOn nativeTest } - - iosX64Main { dependsOn nativeMain } - iosX64Test { dependsOn nativeTest } - - iosArm64Test { dependsOn nativeTest } - iosArm64Test { dependsOn nativeTest } } } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 5221ecab..11b11583 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -43,7 +43,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder { init { init() - initialized(true) + initialized.accept(true) } override fun connect(connection: Connection) { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt index 30ed0dbf..cadbc453 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/BinderExt.kt @@ -7,7 +7,7 @@ import com.badoo.mvicore.common.lifecycle.Lifecycle fun binder(init: Binder.() -> Unit = { }): Binder = SimpleBinder(init) fun binder(lifecycle: Lifecycle, init: Binder.() -> Unit = { }): Binder = LifecycleBinder(lifecycle, init) -fun Binder.bind(connection: Pair, Sink>) { +fun Binder.bind(connection: Pair, Sink>) { val (from, to) = connection connect(Connection(from = from, to = to)) } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt index 26d2da1b..85247870 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connection.kt @@ -4,8 +4,8 @@ import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source data class Connection( - val from: Source? = null, - val to: Sink, + val from: Source? = null, + val to: Sink, val connector: Connector? = null, val name: String? = null ) { @@ -22,17 +22,17 @@ data class Connection( } } -infix fun Pair, Sink>.using(transformer: (Out) -> In?): Connection = +infix fun Pair, Sink>.using(transformer: (Out) -> In?): Connection = using(NotNullConnector(transformer)) -infix fun Pair, Sink>.using(connector: Connector): Connection = +infix fun Pair, Sink>.using(connector: Connector): Connection = Connection( from = first, to = second, connector = connector ) -infix fun Pair, Sink>.named(name: String): Connection = +infix fun Pair, Sink>.named(name: String): Connection = Connection( from = first, to = second, diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt index 02982459..30f87ed0 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Connector.kt @@ -2,6 +2,6 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Source -interface Connector { - operator fun invoke(source: Source): Source +interface Connector { + operator fun invoke(source: Source): Source } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt index d3e079e4..a0b37a5d 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/NotNullConnector.kt @@ -3,8 +3,8 @@ package com.badoo.mvicore.common.binder import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.sources.MapNotNullSource -internal class NotNullConnector(private val mapper: (Out) -> In?): Connector { - override fun invoke(source: Source): Source { +internal class NotNullConnector(private val mapper: (Out) -> In?): Connector { + override fun invoke(source: Source): Source { return MapNotNullSource(source, mapper) } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt index bcd6c5e7..8d451263 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/element/featureElements.kt @@ -2,22 +2,22 @@ package com.badoo.mvicore.common.element import com.badoo.mvicore.common.Source -interface Bootstrapper { +interface Bootstrapper { operator fun invoke(): Source } -interface Actor { - operator fun invoke(state: State, action: Action): Source +interface Actor { + operator fun invoke(state: State, action: Action): Source } -interface Reducer { +interface Reducer { operator fun invoke(state: State, effect: Effect): State } -interface NewsPublisher { +interface NewsPublisher { operator fun invoke(old: State, action: Action, effect: Effect, new: State): News? } -interface PostProcessor { +interface PostProcessor { operator fun invoke(old: State, action: Action, effect: Effect, new: State): Action? } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt index 71442948..b9f1b781 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ActorReducerFeature.kt @@ -5,7 +5,7 @@ import com.badoo.mvicore.common.element.Bootstrapper import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.Reducer -open class ActorReducerFeature( +open class ActorReducerFeature( initialState: State, bootstrapper: Bootstrapper? = null, actor: Actor, diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt index ac5f514e..0861d3b7 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/BaseFeature.kt @@ -11,7 +11,7 @@ import com.badoo.mvicore.common.element.PostProcessor import com.badoo.mvicore.common.element.Reducer import com.badoo.mvicore.common.source -abstract class BaseFeature ( +abstract class BaseFeature ( initialState: State, private val wishToAction: (Wish) -> Action, private val actor: Actor, @@ -31,19 +31,19 @@ abstract class BaseFeature val newState = reducer(state, effect) - stateSource(newState) + stateSource.accept(newState) newsPublisher?.invoke(oldState, action, effect, newState)?.let { - newsSource(it) + newsSource.accept(it) } - postProcessor?.invoke(oldState, action, effect, newState)?.let(actionSource::invoke) + postProcessor?.invoke(oldState, action, effect, newState)?.let(actionSource::accept) } } cancellables += bootstrapper?.invoke()?.connect(actionSource) } - override fun invoke(wish: Wish) { + override fun accept(wish: Wish) { val action = wishToAction(wish) - actionSource.invoke(action) + actionSource.accept(action) } override fun connect(observer: Observer) = stateSource.connect(observer) diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt index 7cc9b46c..02da5e34 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/Feature.kt @@ -4,6 +4,6 @@ import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink import com.badoo.mvicore.common.Source -interface Feature: Sink, Source, Cancellable { +interface Feature: Sink, Source, Cancellable { val news: Source } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt index 057620fe..40cdacfc 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/feature/ReducerFeature.kt @@ -7,7 +7,7 @@ import com.badoo.mvicore.common.element.NewsPublisher import com.badoo.mvicore.common.element.Reducer import com.badoo.mvicore.common.sources.ValueSource -open class ReducerFeature( +open class ReducerFeature( initialState: State, bootstrapper: Bootstrapper? = null, reducer: Reducer, @@ -19,12 +19,12 @@ open class ReducerFeature( reducer = reducer, newsPublisher = newsPublisher?.let { NoEffectNewsPublisher(it) } ) { - private class PassthroughActor : Actor { - override fun invoke(state: State, wish: Wish): Source = + private class PassthroughActor : Actor { + override fun invoke(state: State, wish: Wish): Source = ValueSource(wish) } - private class NoEffectNewsPublisher( + private class NoEffectNewsPublisher( private val simpleNewsPublisher: SimpleNewsPublisher ) : NewsPublisher { override fun invoke(old: State, action: Wish, effect: Wish, new: State): News? = diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt index 9e7f650b..0ebcc37c 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/lifecycle/lifecycle.kt @@ -19,10 +19,10 @@ class ManualLifecycle( private val source: BehaviourSource = BehaviourSource() ) : Lifecycle, Source by source { fun begin() { - source(BEGIN) + source.accept(BEGIN) } fun end() { - source(END) + source.accept(END) } } diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt index 544034f2..8d7659bf 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/Middleware.kt @@ -11,8 +11,8 @@ abstract class Middleware( wrapped.applyIfMiddleware { onBind(connection) } } - override fun invoke(value: In) { - wrapped(value) + override fun accept(value: In) { + wrapped.accept(value) } open fun onElement(connection: Connection, element: In) { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt index d69b4def..8ad7bfc5 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/middleware/StandaloneMiddleware.kt @@ -22,9 +22,9 @@ internal class StandaloneMiddleware( wrappedMiddleware.onBind(connection) } - override fun invoke(value: In) { + override fun accept(value: In) { wrappedMiddleware.onElement(connection, value) - wrappedMiddleware.invoke(value) + wrappedMiddleware.accept(value) } override fun onComplete(connection: Connection) { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt index 90650547..48c8b104 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sink.kt @@ -2,11 +2,11 @@ package com.badoo.mvicore.common import com.badoo.reaktive.utils.atomic.AtomicReference -interface Sink { - operator fun invoke(value: T) +interface Sink { + fun accept(value: T) } -interface Observer : Sink { +interface Observer : Sink { fun onSubscribe(cancellable: Cancellable) fun onComplete() fun onError(throwable: Throwable) @@ -14,20 +14,19 @@ interface Observer : Sink { fun sinkOf(action: (T) -> Unit): Sink = SinkFromAction(action) -private class SinkFromAction(private val action: (T) -> Unit): Sink { - override fun invoke(value: T) { +private class SinkFromAction(private val action: (T) -> Unit): Sink { + override fun accept(value: T) { action(value) } } -fun ((T) -> Unit).toObserver(): Observer = - ObserverFromAction(this) +fun Sink.toObserver(): Observer = ObserverFromSink(this) -private class ObserverFromAction(val action: (T) -> Unit): Observer { +private class ObserverFromSink(private val sink: Sink): Observer { private val cancellable: AtomicReference = AtomicReference(null) - override fun invoke(value: T) { - action(value) + override fun accept(value: T) { + sink.accept(value) } override fun onSubscribe(cancellable: Cancellable) { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt index 26e50d28..f21243cc 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/source.kt @@ -7,7 +7,7 @@ import com.badoo.reaktive.utils.atomic.update * NOTE: in conversions from other frameworks you need to override equals and hashCode * to support binder "emit after dispose" functionality */ -interface Source { +interface Source { fun connect(observer: Observer): Cancellable } @@ -16,10 +16,10 @@ fun source(initialValue: T): BehaviourSource = BehaviourSource(initialVal fun source(): PublishSource = PublishSource() fun Source.connect(action: (T) -> Unit) = - connect(action.toObserver()) + connect(sinkOf(action)) fun Source.connect(sink: Sink) = - connect(sink::invoke) + connect(sink.toObserver()) class PublishSource internal constructor(): Source, Sink { @@ -36,9 +36,9 @@ class PublishSource internal constructor(): Source, Sink { return cancellable } - override fun invoke(value: T) { + override fun accept(value: T) { val observers = observers.value - observers.forEach { it(value) } + observers.forEach { it.accept(value) } } } @@ -57,16 +57,16 @@ class BehaviourSource internal constructor(initialValue: Any? = NoValue) : So val value = _value.value if (value !== NoValue) { - observer.invoke(value as T) + observer.accept(value as T) } return cancellable } - override fun invoke(value: T) { + override fun accept(value: T) { val observers = observers.value _value.update { value } - observers.forEach { it(value) } + observers.forEach { it.accept(value) } } val value: T? get() = diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt index 50e528b9..2849c5f0 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/DelayUntilSource.kt @@ -9,7 +9,7 @@ import com.badoo.reaktive.utils.atomic.AtomicBoolean import com.badoo.reaktive.utils.atomic.AtomicReference import com.badoo.reaktive.utils.atomic.update -internal class DelayUntilSource( +internal class DelayUntilSource( private val signal: Source, private val delegate: Source ): Source { @@ -25,9 +25,9 @@ internal class DelayUntilSource( private val events: AtomicReference> = AtomicReference(emptyList()) private val cancellable = signal.connect { if (it) send() } - override fun invoke(value: T) { + override fun accept(value: T) { if (passThrough.value) { - delegate(value) + delegate.accept(value) } else { events.update { it + value } } @@ -37,7 +37,7 @@ internal class DelayUntilSource( passThrough.compareAndSet(false, true) val events = this.events.value - events.forEach { delegate(it) } + events.forEach { delegate.accept(it) } this.events.update { emptyList() } completeDownstream() diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt index be661c61..c9c824fe 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/MapNotNullSource.kt @@ -4,7 +4,7 @@ import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Observer import com.badoo.mvicore.common.Source -internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { +internal class MapNotNullSource(private val delegate: Source, private val mapper: (Out) -> In?): Source { override fun connect(observer: Observer): Cancellable = delegate.connect(MapNotNullObserver(observer, mapper)) @@ -12,8 +12,8 @@ internal class MapNotNullSource(private val delegate: Source, priv val delegate: Observer, private val mapper: (Out) -> In? ): Observer { - override fun invoke(value: Out) { - mapper(value)?.let { delegate(it) } + override fun accept(value: Out) { + mapper(value)?.let { delegate.accept(it) } } override fun onSubscribe(cancellable: Cancellable) { diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt index c7884f17..8364d277 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/sources/ValueSource.kt @@ -5,7 +5,7 @@ import com.badoo.mvicore.common.Observer import com.badoo.mvicore.common.Source import com.badoo.mvicore.common.cancellableOf -class ValueSource(vararg values: T) : Source { +class ValueSource(vararg values: T) : Source { private val valuesToEmit = values override fun connect(observer: Observer): Cancellable { @@ -13,7 +13,7 @@ class ValueSource(vararg values: T) : Source { observer.onSubscribe(cancellable) valuesToEmit.forEach { - observer.invoke(it) + observer.accept(it) } if (!cancellable.isCancelled) { diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt index 7d42b80b..b72adb7d 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/binder.kt @@ -18,7 +18,7 @@ class BinderTest { bind(source to sink) } - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -30,7 +30,7 @@ class BinderTest { binder.cancel() - source.invoke(0) + source.accept(0) sink.assertNoValues() } @@ -40,7 +40,7 @@ class BinderTest { bind(source to sink using { it + 1 }) } - source.invoke(0) + source.accept(0) sink.assertValues(1) } @@ -50,9 +50,9 @@ class BinderTest { bind(source to sink using { if (it % 2 == 0) null else it }) } - source.invoke(0) - source.invoke(1) - source.invoke(2) + source.accept(0) + source.accept(1) + source.accept(2) sink.assertValues(1) } @@ -63,7 +63,7 @@ class BinderTest { bind(source to sink using connector) } - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -75,7 +75,7 @@ class BinderTest { } lifecycle.begin() - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -87,7 +87,7 @@ class BinderTest { bind(source to sink) } - source.invoke(0) + source.accept(0) lifecycle.begin() sink.assertNoValues() @@ -101,9 +101,9 @@ class BinderTest { } lifecycle.begin() - source.invoke(0) + source.accept(0) lifecycle.end() - source.invoke(1) + source.accept(1) sink.assertValues(0) } @@ -116,11 +116,11 @@ class BinderTest { } lifecycle.begin() - source.invoke(0) + source.accept(0) lifecycle.end() - source.invoke(1) + source.accept(1) lifecycle.begin() - source.invoke(2) + source.accept(2) sink.assertValues(0, 2) } @@ -133,11 +133,11 @@ class BinderTest { } lifecycle.begin() - source.invoke(0) + source.accept(0) lifecycle.end() binder.cancel() lifecycle.begin() - source.invoke(2) + source.accept(2) sink.assertValues(0) } @@ -151,7 +151,7 @@ class BinderTest { bind(source to sink) } - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -167,7 +167,7 @@ class BinderTest { lifecycle.begin() lifecycle.begin() - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -194,7 +194,7 @@ class BinderTest { binder.bind(source to sink2) binder.bind(source to sink) - source.invoke(0) + source.accept(0) sink.assertValues(0) } @@ -204,7 +204,7 @@ class BinderTest { val passThroughSource = source() binder { bind(source to passThroughSource) - source.invoke(0) + source.accept(0) bind(passThroughSource to sink) }.freeze() diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt index 3e467261..7a2f94ed 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/cancellable.kt @@ -15,7 +15,7 @@ class CancellableTest { @Test fun cancellable_action_is_executed_on_cancel() { val sink = TestSink() - val cancellable = cancellableOf { sink(Unit) }.freeze() + val cancellable = cancellableOf { sink.accept(Unit) }.freeze() cancellable.cancel() @@ -25,7 +25,7 @@ class CancellableTest { @Test fun cancellable_action_is_executed_only_once_on_cancel() { val sink = TestSink() - val cancellable = cancellableOf { sink(Unit) }.freeze() + val cancellable = cancellableOf { sink.accept(Unit) }.freeze() cancellable.cancel() cancellable.cancel() diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt index 0b9fa484..8f115f73 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/actorReducerFeature.kt @@ -29,7 +29,7 @@ class ActorReducerFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke(0) + feature.accept(0) sink.assertValues("", "0") } @@ -40,7 +40,7 @@ class ActorReducerFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke(1) + feature.accept(1) sink.assertValues("", "1", "12") } @@ -63,8 +63,8 @@ class ActorReducerFeatureTest { feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke(0) - feature.invoke(1) + feature.accept(0) + feature.accept(1) stateSink.assertValues("", "0", "01", "012") newsSink.assertValues(0, 2) @@ -79,10 +79,10 @@ class ActorReducerFeatureTest { feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke(0) + feature.accept(0) feature.cancel() - feature.invoke(1) + feature.accept(1) stateSink.assertValues("", "0") newsSink.assertValues(0) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt index 2b7a3034..ad2e4080 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/baseFeature.kt @@ -30,7 +30,7 @@ class BaseFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke("0") + feature.accept("0") sink.assertValues("", "0") } @@ -43,9 +43,9 @@ class BaseFeatureTest { feature.connect(sink) assertFailsWith(IllegalArgumentException::class) { - feature.invoke("1") + feature.accept("1") } - feature.invoke("0") + feature.accept("0") sink.assertValues("", "0") } @@ -56,7 +56,7 @@ class BaseFeatureTest { val sink = TestSink() feature.connect(sink) - feature.invoke("1") + feature.accept("1") sink.assertValues("", "1", "12") } @@ -79,8 +79,8 @@ class BaseFeatureTest { feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke("0") - feature.invoke("1") + feature.accept("0") + feature.accept("1") stateSink.assertValues("", "0", "01", "012") newsSink.assertValues(0, 2) @@ -95,10 +95,10 @@ class BaseFeatureTest { feature.news.connect(newsSink) feature.connect(stateSink) - feature.invoke("0") + feature.accept("0") feature.cancel() - feature.invoke("1") + feature.accept("1") stateSink.assertValues("", "0") newsSink.assertValues(0) diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt index 07730093..fbe1a4c6 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/feature/reducerFeature.kt @@ -27,7 +27,7 @@ class ReducerFeatureTest { feature.connect(sink) - feature.invoke(0) + feature.accept(0) sink.assertValues("", "0") } @@ -38,8 +38,8 @@ class ReducerFeatureTest { feature.connect(sink) - feature.invoke(0) - feature.invoke(1) + feature.accept(0) + feature.accept(1) sink.assertValues("", "0", "01") } @@ -50,7 +50,7 @@ class ReducerFeatureTest { feature.connect(sink) - feature.invoke(1) + feature.accept(1) sink.assertValues("0", "01") } @@ -61,8 +61,8 @@ class ReducerFeatureTest { feature.news.connect(sink) - feature.invoke(1) - feature.invoke(1) + feature.accept(1) + feature.accept(1) sink.assertValues(1, 2) } @@ -74,7 +74,7 @@ class ReducerFeatureTest { feature.connect(sink) feature.cancel() - feature.invoke(1) + feature.accept(1) sink.assertValues("") } diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt index 28d09f47..3e4a9fa0 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/source.kt @@ -30,7 +30,7 @@ class SourceTest { val sink = TestSink() source.connect(sink) - source.invoke(0) + source.accept(0) assertEquals(listOf(0), sink.values) } @@ -42,7 +42,7 @@ class SourceTest { val cancellable = source.connect(sink) cancellable.cancel() - source.invoke(0) + source.accept(0) assertEquals(emptyList(), sink.values) } diff --git a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt index 7d140d6a..5ed83c42 100644 --- a/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt +++ b/mvicore-common/src/commonTest/kotlin/com/badoo/mvicore/common/util.kt @@ -13,7 +13,7 @@ open class TestSink: Sink { val values: List get() = _values.value - override fun invoke(value: T) { + override fun accept(value: T) { _values.update { it + value } } } diff --git a/mvicore-rx/build.gradle b/mvicore-rx/build.gradle index e487c9b2..8ccc4e9d 100644 --- a/mvicore-rx/build.gradle +++ b/mvicore-rx/build.gradle @@ -1,7 +1,6 @@ -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: 'kotlin' -apply plugin: 'org.jetbrains.dokka' +plugins { + id 'org.jetbrains.kotlin.jvm' +} group = 'com.github.badoo.mvicore' @@ -9,49 +8,11 @@ dependencies { def deps = rootProject.ext.deps implementation deps('io.reactivex.rxjava2:rxjava') - implementation project(':mvicore-common') implementation deps('io.reactivex.rxjava2:rxkotlin') - implementation deps("org.jetbrains.kotlin:kotlin-stdlib-jdk7") - - testImplementation deps('junit:junit') - testImplementation deps('org.jetbrains.kotlin:kotlin-test-junit') - testImplementation deps('org.amshove.kluent:kluent') - testImplementation deps('com.nhaarman:mockito-kotlin') -} + implementation deps("org.jetbrains.kotlin:kotlin-stdlib") -sourceCompatibility = "1.7" -targetCompatibility = "1.7" - -buildscript { - repositories { - jcenter() - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$rootProject.ext.kotlinVersion" - } -} - -repositories { - jcenter() -} - -sourceSets { - main { - java {} - } -} - -task packageSources(type: Jar, dependsOn: 'classes') { - classifier = 'sources' - from sourceSets.main.allSource -} - -task packageJavadoc(type: Jar, dependsOn: javadoc) { - from javadoc.outputDirectory - classifier = 'javadoc' -} + implementation project(':mvicore-common') + testImplementation project(':mvicore-common') -artifacts { - archives packageSources - archives packageJavadoc + testImplementation deps('junit:junit') } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt index 6f55f2fd..83e7410a 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt @@ -4,7 +4,7 @@ import com.badoo.mvicore.common.Sink import io.reactivex.functions.Consumer fun Consumer.toSink() = object : Sink { - override fun invoke(value: T) { - accept(value) + override fun accept(value: T) { + this@toSink.accept(value) } } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt index a0637b6d..3792c3a4 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt @@ -18,7 +18,7 @@ internal class SourceAdapter(internal val delegate: Observable): Source override fun connect(observer: MVICoreObserver): Cancellable = DisposableAdapter( delegate.subscribe( - observer::invoke, + observer::accept, observer::onError, observer::onComplete, { observer.onSubscribe(DisposableAdapter(it)) } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt index e4569e3e..3112ee98 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Connector.kt @@ -6,14 +6,14 @@ import com.badoo.mvicore.rx.toSource import io.reactivex.ObservableSource import com.badoo.mvicore.common.binder.Connector as CommonConnector -interface Connector { - operator fun invoke(source: ObservableSource): ObservableSource +interface Connector { + operator fun invoke(source: ObservableSource): ObservableSource } internal fun Connector.toCommonConnector(): CommonConnector = CommonConnectorAdapter(this) -internal class CommonConnectorAdapter(private val delegate: Connector) : CommonConnector { - override fun invoke(source: Source): Source = +internal class CommonConnectorAdapter(private val delegate: Connector) : CommonConnector { + override fun invoke(source: Source): Source = delegate.invoke(source.toObservable()).toSource() } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt index 606fc247..68f9b7c2 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/element/featureElements.kt @@ -5,11 +5,11 @@ import com.badoo.mvicore.common.element.PostProcessor import com.badoo.mvicore.common.element.Reducer import io.reactivex.ObservableSource -interface Bootstrapper { - operator fun invoke(): ObservableSource +interface Bootstrapper { + operator fun invoke(): ObservableSource } -interface Actor { +interface Actor { operator fun invoke(state: State, action: Action): ObservableSource } @@ -18,3 +18,5 @@ typealias Reducer = Reducer typealias NewsPublisher = NewsPublisher typealias PostProcessor = PostProcessor + +typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt index 965c9520..a2ad5a34 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt @@ -34,7 +34,7 @@ abstract class BaseFeature @@ -61,4 +61,11 @@ abstract class BaseFeature = delegate.invoke().toSource() } + + override fun dispose() { + delegate.cancel() + } + + override fun isDisposed(): Boolean = + delegate.isCancelled } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt index d2576e62..96386e0c 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/Feature.kt @@ -1,9 +1,10 @@ package com.badoo.mvicore.rx.feature import io.reactivex.ObservableSource +import io.reactivex.disposables.Disposable import io.reactivex.functions.Consumer -interface Feature : Consumer, ObservableSource { +interface Feature : Consumer, ObservableSource, Disposable { val news: ObservableSource } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt index 5a0e7e78..bdcba580 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/ReducerFeature.kt @@ -4,6 +4,7 @@ import com.badoo.mvicore.rx.element.Actor import com.badoo.mvicore.rx.element.Bootstrapper import com.badoo.mvicore.rx.element.NewsPublisher import com.badoo.mvicore.rx.element.Reducer +import com.badoo.mvicore.rx.element.SimpleNewsPublisher import io.reactivex.Observable open class ReducerFeature( @@ -31,4 +32,3 @@ open class ReducerFeature( } } -typealias SimpleNewsPublisher = (old: State, wish: Wish, state: State) -> News? diff --git a/mvicore-rx/src/test/java/com/badoo/mvicore/rx/feature/reducerFeature.kt b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/feature/reducerFeature.kt new file mode 100644 index 00000000..05109523 --- /dev/null +++ b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/feature/reducerFeature.kt @@ -0,0 +1,115 @@ +package com.badoo.mvicore.rx.feature + +import com.badoo.mvicore.rx.element.Bootstrapper +import com.badoo.mvicore.rx.element.Reducer +import io.reactivex.Observable +import io.reactivex.ObservableSource +import io.reactivex.observers.TestObserver +import org.junit.Test + +class ReducerFeatureTest { + @Test + fun reducer_feature_emits_initial_state_on_connect() { + val feature = TestReducerFeature() + val sink = TestObserver() + + feature.subscribe(sink) + + sink.assertValues("") + } + + @Test + fun reducer_feature_emits_new_state_on_new_wish() { + val feature = TestReducerFeature() + val sink = TestObserver() + + feature.subscribe(sink) + + feature.accept(0) + sink.assertValues("", "0") + } + + @Test + fun reducer_feature_emits_new_state_on_new_wish_2() { + val feature = TestReducerFeature() + val sink = TestObserver() + + feature.subscribe(sink) + + feature.accept(0) + feature.accept(1) + sink.assertValues("", "0", "01") + } + + @Test + fun reducer_feature_emits_new_state_on_bootstrapper_action() { + val feature = TestReducerFeatureWBootstrapper() + val sink = TestObserver() + + feature.subscribe(sink) + + feature.accept(1) + sink.assertValues("0", "01") + } + + @Test + fun reducer_feature_emits_news_on_wish() { + val feature = TestReducerFeatureWNews() + val sink = TestObserver() + + feature.news.subscribe(sink) + + feature.accept(1) + feature.accept(1) + sink.assertValues(1, 2) + } + + @Test + fun reducer_feature_stop_processing_events_after_cancel() { + val feature = TestReducerFeature() + val sink = TestObserver() + + feature.subscribe(sink) + feature.dispose() + + feature.accept(1) + sink.assertValues("") + } + + class TestReducerFeature(initialState: String = ""): ReducerFeature( + initialState = initialState, + reducer = object : Reducer { + override fun invoke(state: String, wish: Int): String = + state + wish.toString() + } + ) + + class TestReducerFeatureWBootstrapper(initialState: String = ""): ReducerFeature( + initialState = initialState, + bootstrapper = object : Bootstrapper { + override fun invoke(): ObservableSource = + Observable.just(0) + }, + reducer = object : Reducer { + override fun invoke(state: String, wish: Int): String = + state + wish.toString() + } + ) + + class TestReducerFeatureWNews(initialState: String = ""): ReducerFeature( + initialState = initialState, + reducer = object : Reducer { + override fun invoke(state: String, wish: Int): String = + state + wish.toString() + }, + newsPublisher = { _: String, _: Int, state: String -> + if (state.isNotEmpty()) { + state.length + } else { + null + } + } + ) +} + + From 5cadb96b71796f7c36beb84b05cb135b132f42f2 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 9 May 2020 04:46:38 +0100 Subject: [PATCH 30/31] Add tests for rx reducer feature --- .../java/com/badoo/mvicore/rx/Cancellable.kt | 14 +++++++++++++ .../main/java/com/badoo/mvicore/rx/Sink.kt | 21 +++++++++++++++++++ .../badoo/mvicore/rx/feature/BaseFeature.kt | 9 ++------ 3 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 mvicore-rx/src/main/java/com/badoo/mvicore/rx/Cancellable.kt diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Cancellable.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Cancellable.kt new file mode 100644 index 00000000..9b8a0c4d --- /dev/null +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Cancellable.kt @@ -0,0 +1,14 @@ +package com.badoo.mvicore.rx + +import com.badoo.mvicore.common.Cancellable +import io.reactivex.disposables.Disposable + +fun Cancellable.toDisposable() = object : Disposable { + override fun isDisposed(): Boolean = + isCancelled + + override fun dispose() { + cancel() + } + +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt index 83e7410a..5d88593a 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt @@ -1,6 +1,8 @@ package com.badoo.mvicore.rx +import com.badoo.mvicore.common.Cancellable import com.badoo.mvicore.common.Sink +import io.reactivex.Observer import io.reactivex.functions.Consumer fun Consumer.toSink() = object : Sink { @@ -8,3 +10,22 @@ fun Consumer.toSink() = object : Sink { this@toSink.accept(value) } } + +fun Observer.toCommonObserver() = object : com.badoo.mvicore.common.Observer { + override fun onComplete() { + this@toCommonObserver.onComplete() + } + + override fun onError(e: Throwable) { + this@toCommonObserver.onError(e) + } + + override fun accept(value: T) { + onNext(value) + } + + override fun onSubscribe(cancellable: Cancellable) { + onSubscribe(cancellable.toDisposable()) + } + +} diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt index a2ad5a34..eaf61dfb 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/feature/BaseFeature.kt @@ -1,18 +1,17 @@ package com.badoo.mvicore.rx.feature import com.badoo.mvicore.common.Source -import com.badoo.mvicore.common.connect import com.badoo.mvicore.common.feature.BaseFeature import com.badoo.mvicore.rx.element.Actor import com.badoo.mvicore.rx.element.Bootstrapper import com.badoo.mvicore.rx.element.NewsPublisher import com.badoo.mvicore.rx.element.PostProcessor import com.badoo.mvicore.rx.element.Reducer +import com.badoo.mvicore.rx.toCommonObserver import com.badoo.mvicore.rx.toObservable import com.badoo.mvicore.rx.toSource import io.reactivex.ObservableSource import io.reactivex.Observer -import io.reactivex.disposables.Disposables abstract class BaseFeature ( initialState: State, @@ -41,11 +40,7 @@ abstract class BaseFeature) { - observer.onSubscribe( - Disposables.fromAction { - delegate.connect { observer.onNext(it) } - } - ) + delegate.connect(observer.toCommonObserver()) } private class ActorAdapter( From 0b65cb9165b237769ba183f9c304e097ec0c9489 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Sat, 9 May 2020 05:13:54 +0100 Subject: [PATCH 31/31] Add tests for binder in rx --- .../com/badoo/mvicore/common/binder/Binder.kt | 2 +- .../main/java/com/badoo/mvicore/rx/Sink.kt | 1 - .../main/java/com/badoo/mvicore/rx/Source.kt | 2 +- .../com/badoo/mvicore/rx/binder/Binder.kt | 6 +- .../test/java/com/badoo/mvicore/rx/binder.kt | 222 ++++++++++++++++++ .../test/java/com/badoo/mvicore/rx/util.kt | 46 ++++ 6 files changed, 273 insertions(+), 6 deletions(-) create mode 100644 mvicore-rx/src/test/java/com/badoo/mvicore/rx/binder.kt create mode 100644 mvicore-rx/src/test/java/com/badoo/mvicore/rx/util.kt diff --git a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt index 11b11583..4a3ea045 100644 --- a/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt +++ b/mvicore-common/src/commonMain/kotlin/com/badoo/mvicore/common/binder/Binder.kt @@ -60,7 +60,7 @@ internal class SimpleBinder(init: Binder.() -> Unit) : Binder { DelayUntilSource(initialized, transformedSource) } - delayInitialize.connect(connection.to as Sink) + delayInitialize.connect(connection.to) } private fun getInternalSourceFor(from: Source): PublishSource { diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt index 5d88593a..f7ca5b8e 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Sink.kt @@ -27,5 +27,4 @@ fun Observer.toCommonObserver() = object : com.badoo.mvicore.common.Obser override fun onSubscribe(cancellable: Cancellable) { onSubscribe(cancellable.toDisposable()) } - } diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt index 3792c3a4..2901dd8f 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/Source.kt @@ -26,7 +26,7 @@ internal class SourceAdapter(internal val delegate: Observable): Source ) override fun equals(other: Any?): Boolean = - other is SourceAdapter<*> && super.equals(other.delegate) + other is SourceAdapter<*> && delegate == other.delegate override fun hashCode(): Int = delegate.hashCode() diff --git a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt index 3f537cac..bd95e1d8 100644 --- a/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt +++ b/mvicore-rx/src/main/java/com/badoo/mvicore/rx/binder/Binder.kt @@ -9,12 +9,12 @@ import com.badoo.mvicore.rx.toSource import io.reactivex.ObservableSource import io.reactivex.functions.Consumer -fun Binder.bind(connection: Pair, Consumer>) { +fun Binder.bind(connection: Pair, Consumer>) { bind(connection.first.toSource() to connection.second.toSink()) } -infix fun Pair, Consumer>.using(mapper: (Out) -> In?): Connection = +infix fun Pair, Consumer>.using(mapper: (Out) -> In?): Connection = first.toSource() to second.toSink() using mapper -infix fun Pair, Consumer>.using(connector: Connector): Connection = +infix fun Pair, Consumer>.using(connector: Connector): Connection = first.toSource() to second.toSink() using connector.toCommonConnector() diff --git a/mvicore-rx/src/test/java/com/badoo/mvicore/rx/binder.kt b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/binder.kt new file mode 100644 index 00000000..98bd0a26 --- /dev/null +++ b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/binder.kt @@ -0,0 +1,222 @@ +package com.badoo.mvicore.rx + +import com.badoo.mvicore.common.binder.bind +import com.badoo.mvicore.common.binder.binder +import com.badoo.mvicore.common.lifecycle.Lifecycle +import com.badoo.mvicore.rx.binder.Connector +import com.badoo.mvicore.rx.binder.bind +import com.badoo.mvicore.rx.binder.using +import io.reactivex.Observable +import io.reactivex.Observable.wrap +import io.reactivex.ObservableSource +import io.reactivex.functions.Consumer +import io.reactivex.subjects.PublishSubject +import org.junit.Test + +class BinderTest { + private val source = PublishSubject.create() + private val sink = TestConsumer() + + @Test + fun binder_without_lifecycle_connects_source_and_sink() { + binder().apply { + bind(source to sink) + } + + source.onNext(0) + sink.assertValues(0) + } + + @Test + fun binder_without_lifecycle_does_not_connect_source_and_sink_after_cancel() { + val binder = binder().apply { + bind(source to sink) + } + + binder.cancel() + + source.onNext(0) + sink.assertNoValues() + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_using_mapper() { + binder().apply { + bind(source to sink using { it + 1 }) + } + + source.onNext(0) + sink.assertValues(1) + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_skips_nulls_from_mapper() { + binder().apply { + bind(source to sink using { if (it % 2 == 0) null else it }) + } + + source.onNext(0) + source.onNext(1) + source.onNext(2) + sink.assertValues(1) + } + + @Test + fun binder_without_lifecycle_connects_source_and_sink_using_connector() { + val connector = object : Connector { + override fun invoke(source: ObservableSource): ObservableSource = + wrap(source).flatMap { Observable.just(it, it + 1) } + } + + binder().apply { + bind(source to sink using connector) + } + + source.onNext(0) + sink.assertValues(0, 1) + } + + @Test + fun binder_with_lifecycle_connects_source_and_sink_when_active() { + val lifecycle = Lifecycle.manual() + binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.onNext(0) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_does_not_connect_source_and_sink_before_active() { + val lifecycle = Lifecycle.manual() + binder(lifecycle).apply { + bind(source to sink) + } + + source.onNext(0) + lifecycle.begin() + + sink.assertNoValues() + } + + @Test + fun binder_with_lifecycle_disconnect_source_and_sink_after_end() { + val lifecycle = Lifecycle.manual() + binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.onNext(0) + lifecycle.end() + source.onNext(1) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_reconnect_source_and_sink_after_begin() { + val lifecycle = Lifecycle.manual() + binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.onNext(0) + lifecycle.end() + source.onNext(1) + lifecycle.begin() + source.onNext(2) + + sink.assertValues(0, 2) + } + + @Test + fun binder_with_lifecycle_does_not_reconnect_source_and_sink_after_cancel() { + val lifecycle = Lifecycle.manual() + val binder = binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + source.onNext(0) + lifecycle.end() + binder.cancel() + lifecycle.begin() + source.onNext(2) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_connects_source_and_sink_if_lifecycle_started() { + val lifecycle = Lifecycle.manual() + lifecycle.begin() + + binder(lifecycle).apply { + bind(source to sink) + } + + source.onNext(0) + + sink.assertValues(0) + } + + @Test + fun binder_with_lifecycle_does_not_reconnect_on_duplicated_lifecycle_events() { + val lifecycle = Lifecycle.manual() + + binder(lifecycle).apply { + bind(source to sink) + } + + lifecycle.begin() + lifecycle.begin() + + source.onNext(0) + + sink.assertValues(0) + } + + @Test + fun binder_covariant_endpoints_compile_for_pair() { + val sink = Consumer { /* no-op */ } + binder().bind(source to sink) + } + + @Test + fun binder_covariant_endpoints_compile_for_connection() { + val sink = Consumer { _: Any -> /* no-op */ } + val intToString: (Int) -> String = { it.toString() } + binder().bind(source to sink using intToString) + } + + @Test + fun binder_delivers_message_to_all_sinks_on_dispose() { + val binder = binder { + val sink2 = Consumer { _: Int -> this.cancel() } + + bind(source to sink2) + bind(source to sink) + } + + source.onNext(0) + + sink.assertValues(0) + } + + @Test + fun binder_messages_sent_on_initialize_are_not_lost() { + val passThroughSource = PublishSubject.create() + binder { + bind(source to Consumer { passThroughSource.onNext(it) }) + source.onNext(0) + bind(passThroughSource to sink) + } + + sink.assertValues(0) + } +} diff --git a/mvicore-rx/src/test/java/com/badoo/mvicore/rx/util.kt b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/util.kt new file mode 100644 index 00000000..ecef915b --- /dev/null +++ b/mvicore-rx/src/test/java/com/badoo/mvicore/rx/util.kt @@ -0,0 +1,46 @@ +package com.badoo.mvicore.rx + +import com.badoo.mvicore.rx.element.Actor +import com.badoo.mvicore.rx.element.Bootstrapper +import com.badoo.mvicore.rx.element.NewsPublisher +import com.badoo.mvicore.rx.element.Reducer +import io.reactivex.ObservableSource +import io.reactivex.functions.Consumer +import org.junit.Assert.assertEquals + +open class TestConsumer: Consumer { + val values = mutableListOf() + + override fun accept(value: T) { + values += value + } +} + +fun TestConsumer.assertValues(vararg values: T) = + assertEquals(values.toList(), this.values) + +fun TestConsumer.assertNoValues() = + assertEquals(emptyList(), this.values) + +fun reducer(block: (state: State, effect: Effect) -> State) = + object : Reducer { + override fun invoke(state: State, effect: Effect): State = block(state, effect) + } + +fun actor(block: (state: State, wish: Wish) -> ObservableSource) = + object : Actor { + override fun invoke(state: State, action: Wish): ObservableSource = + block(state, action) + } + +fun bootstrapper(block: () -> ObservableSource) = + object : Bootstrapper { + override fun invoke(): ObservableSource = + block() + } + +fun newsPublisher(block: (old: State, action: Action, effect: Effect, new: State) -> News?) = + object : NewsPublisher { + override fun invoke(old: State, action: Action, effect: Effect, new: State): News? = + block(old, action, effect, new) + }