Skip to content

Commit

Permalink
Add CompositeStore
Browse files Browse the repository at this point in the history
  • Loading branch information
alex2069 committed Mar 5, 2021
1 parent 40bf350 commit 5553bed
Show file tree
Hide file tree
Showing 3 changed files with 912 additions and 0 deletions.
132 changes: 132 additions & 0 deletions rekotlin/src/main/kotlin/org/rekotlin/Compose.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
@file:Suppress("UNCHECKED_CAST", "unused")

package org.rekotlin

typealias Compose<State> = (Array<out Store<*>>) -> State
typealias Compose1<S1, State> = (S1) -> State
typealias Compose2<S1, S2, State> = (S1, S2) -> State
typealias Compose3<S1, S2, S3, State> = (S1, S2, S3) -> State
typealias Compose4<S1, S2, S3, S4, State> = (S1, S2, S3, S4) -> State
typealias Compose5<S1, S2, S3, S4, S5, State> = (S1, S2, S3, S4, S5) -> State
typealias Compose6<S1, S2, S3, S4, S5, S6, State> = (S1, S2, S3, S4, S5, S6) -> State
typealias Compose7<S1, S2, S3, S4, S5, S6, S7, State> = (S1, S2, S3, S4, S5, S6, S7) -> State
typealias Compose8<S1, S2, S3, S4, S5, S6, S7, S8, State> = (S1, S2, S3, S4, S5, S6, S7, S8) -> State
typealias Compose9<S1, S2, S3, S4, S5, S6, S7, S8, S9, State> = (S1, S2, S3, S4, S5, S6, S7, S8, S9) -> State

inline fun <S1, State> composeStores(
store1: Store<S1>,
vararg middleware: Middleware<State>,
crossinline compose: Compose1<S1, State> = { it as State }
): Store<State> = CompositeStore(store1, middleware = middleware.toList()) {
compose(it.s1())
}

inline fun <S1, S2, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
vararg middleware: Middleware<State>,
crossinline compose: Compose2<S1, S2, State>
): Store<State> = CompositeStore(store1, store2, middleware = middleware.toList()) {
compose(it.s1(), it.s2())
}

inline fun <S1, S2, S3, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
vararg middleware: Middleware<State>,
crossinline compose: Compose3<S1, S2, S3, State>
): Store<State> = CompositeStore(store1, store2, store3, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3())
}

inline fun <S1, S2, S3, S4, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
vararg middleware: Middleware<State>,
crossinline compose: Compose4<S1, S2, S3, S4, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4())
}

inline fun <S1, S2, S3, S4, S5, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
store5: Store<S5>,
vararg middleware: Middleware<State>,
crossinline compose: Compose5<S1, S2, S3, S4, S5, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, store5, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4(), it.s5())
}

inline fun <S1, S2, S3, S4, S5, S6, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
store5: Store<S5>,
store6: Store<S6>,
vararg middleware: Middleware<State>,
crossinline compose: Compose6<S1, S2, S3, S4, S5, S6, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, store5, store6, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4(), it.s5(), it.s6())
}

inline fun <S1, S2, S3, S4, S5, S6, S7, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
store5: Store<S5>,
store6: Store<S6>,
store7: Store<S7>,
vararg middleware: Middleware<State>,
crossinline compose: Compose7<S1, S2, S3, S4, S5, S6, S7, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, store5, store6, store7, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4(), it.s5(), it.s6(), it.s7())
}

inline fun <S1, S2, S3, S4, S5, S6, S7, S8, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
store5: Store<S5>,
store6: Store<S6>,
store7: Store<S7>,
store8: Store<S8>,
vararg middleware: Middleware<State>,
crossinline compose: Compose8<S1, S2, S3, S4, S5, S6, S7, S8, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, store5, store6, store7, store8, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4(), it.s5(), it.s6(), it.s7(), it.s8())
}

inline fun <S1, S2, S3, S4, S5, S6, S7, S8, S9, State> composeStores(
store1: Store<S1>,
store2: Store<S2>,
store3: Store<S3>,
store4: Store<S4>,
store5: Store<S5>,
store6: Store<S6>,
store7: Store<S7>,
store8: Store<S8>,
store9: Store<S9>,
vararg middleware: Middleware<State>,
crossinline compose: Compose9<S1, S2, S3, S4, S5, S6, S7, S8, S9, State>
): Store<State> = CompositeStore(store1, store2, store3, store4, store5, store6, store7, store8, store9, middleware = middleware.toList()) {
compose(it.s1(), it.s2(), it.s3(), it.s4(), it.s5(), it.s6(), it.s7(), it.s8(), it.s9())
}

fun <S> Array<out Store<*>>.s1() = this[0].state as S
fun <S> Array<out Store<*>>.s2() = this[1].state as S
fun <S> Array<out Store<*>>.s3() = this[2].state as S
fun <S> Array<out Store<*>>.s4() = this[3].state as S
fun <S> Array<out Store<*>>.s5() = this[4].state as S
fun <S> Array<out Store<*>>.s6() = this[5].state as S
fun <S> Array<out Store<*>>.s7() = this[6].state as S
fun <S> Array<out Store<*>>.s8() = this[7].state as S
fun <S> Array<out Store<*>>.s9() = this[8].state as S
165 changes: 165 additions & 0 deletions rekotlin/src/main/kotlin/org/rekotlin/CompositeStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package org.rekotlin

import java.util.IdentityHashMap
import java.util.concurrent.CopyOnWriteArrayList

class CompositeStore<State>(
private vararg val stores: Store<*>,
middleware: List<Middleware<State>> = emptyList(),
private val skipRepeats: Boolean = true,
private val compose: Compose<State>
) : Store<State> {

private val subscriptions: MutableList<SubscriptionBox<State, *>> = CopyOnWriteArrayList()
private val listeners: MutableList<ListenerBox<out Effect>> = CopyOnWriteArrayList()
private val middlewares: MutableList<Middleware<State>> = CopyOnWriteArrayList(middleware)

private val effectDispatcher = EffectDispatcher()
private val dispatchEffect: DispatchEffect = { effect ->
effectDispatcher.dispatch(effect) {
stores.forEach { it.dispatch(effect) }
listeners.forEach { it.onEffect(effect) }
}
}

private var dispatchAction: DispatchAction = buildDispatchAction()

init {
stores.forEach { store ->
store.subscribeTo { _ ->
val prevState = _state
val newState = compose(stores)
_state = newState
subscriptions.forEach {
it.newValues(prevState, newState)
}
}
store.listenTo { effect: Effect ->
if (!effect.isDispatching) {
listeners.forEach {
it.onEffect(effect)
}
}
}
}
}

private var _state: State? = null
override val state: State
get() = _state!!

override fun dispatch(dispatchable: Dispatchable) = dispatchFunction(dispatchable)
override val dispatchFunction: DispatchFunction
get() = { dispatchable: Dispatchable ->
when (dispatchable) {
is Effect -> dispatchEffect(dispatchable)
is Action -> dispatchAction(dispatchable)
}
}

operator fun plusAssign(middleware: Middleware<State>) {
middlewares += middleware
dispatchAction = buildDispatchAction()
}

operator fun minusAssign(middleware: Middleware<State>) {
middlewares -= middleware
dispatchAction = buildDispatchAction()
}

private fun buildDispatchAction() =
middlewares.reversed()
.fold(this::defaultDispatch as DispatchFunction,
{ dispatch, middleware ->
middleware(this::dispatch, this::state)(dispatch)
}
)

private fun defaultDispatch(dispatchable: Dispatchable) =
when (dispatchable) {
is Dispatcher -> dispatchable.dispatchTo(stores)
else -> Dispatcher(dispatchable).dispatchTo(stores)
}

override fun <S : Subscriber<State>> subscribe(subscriber: S) = subscribe(subscriber, { this })
override fun <SelectedState, S : Subscriber<SelectedState>> subscribe(
subscriber: S,
selector: Subscription<State>.() -> Subscription<SelectedState>
) {
unsubscribe(subscriber)

val actualSelector = when {
skipRepeats -> compose(selector, Subscription<SelectedState>::skipRepeats)
else -> selector
}

val box = SubscriptionBox(actualSelector, subscriber)
subscriptions.add(box)

_state?.let {
box.newValues(null, it)
}
}

override fun <SelectedState> unsubscribe(subscriber: Subscriber<SelectedState>) {
val index = subscriptions.indexOfFirst { it.subscriber === subscriber }
if (index != -1) {
subscriptions.removeAt(index)
}
}

override fun subscribe(listener: Listener<Effect>) = subscribe(listener) { it }
override fun <E : Effect> subscribe(listener: Listener<E>, selector: (Effect) -> E?) {
unsubscribe(listener)
listeners.add(ListenerBox(listener, selector))
}

override fun <E : Effect> unsubscribe(listener: Listener<E>) {
val index = listeners.indexOfFirst { it.listener === listener }
if (index != -1) {
listeners.removeAt(index)
}
}

private val Effect.isDispatching get() = effectDispatcher.isDispatching(this)
}

inline fun <State> Store<State>.subscribeTo(crossinline subscriber: (State) -> Unit) =
object : Subscriber<State> {
override fun newState(state: State) {
subscriber(state)
}
}.also { subscribe(it) }

inline fun <reified EffectType : Effect> SubscribeStore<*>.listenTo(crossinline onEffect: (EffectType) -> Unit) =
listener<EffectType> { onEffect(it) }
.also { listener -> subscribe(listener) { it as? EffectType } }

private class EffectDispatcher {
private val dispatching = mutableListOf<Effect>()

fun isDispatching(effect: Effect) = dispatching.any { it === effect }

fun dispatch(effect: Effect, body: () -> Unit) {
dispatching += effect
body()
dispatching -= effect
}
}

private class Dispatcher(private val dispatchable: Dispatchable) : Dispatchable {
private val dispatched = IdentityHashMap<Store<*>, Unit>()

fun dispatchTo(stores: Array<out Store<*>>) {
stores.forEach { store ->
if (store !in dispatched) {
dispatched += store to Unit
if (store is CompositeStore<*>) {
store.dispatch(this)
} else {
store.dispatch(dispatchable)
}
}
}
}
}
Loading

0 comments on commit 5553bed

Please sign in to comment.