forked from ReKotlin/ReKotlin
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
912 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
165
rekotlin/src/main/kotlin/org/rekotlin/CompositeStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.