Skip to content

Commit

Permalink
Merge pull request #10 from motorro/typed_multi_machine
Browse files Browse the repository at this point in the history
Typed multi machine
  • Loading branch information
motorro authored Nov 8, 2023
2 parents 477d7fa + c5bb1ee commit 279fdd9
Show file tree
Hide file tree
Showing 60 changed files with 1,310 additions and 146 deletions.
6 changes: 6 additions & 0 deletions .idea/copyright/Apache.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions .idea/copyright/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ val commonMain by getting {
- [Welcome](examples/welcome/welcome) - multi-module example of user on-boarding flow
- [Parallel](examples/multi/parallel) - two machines running in parallel in one proxy state
- [Navbar](examples/multi/navbar) - several machines running in proxy state, one of them active at a time
- [Mixed](examples/multi/mixed) - two machines of different gesture/UI system mixed in one state
- [Lifecycle](examples/lifecycle) - track your Android app lifecycle to pause pending operations when the app is suspended

## The basic task - Load-Content-Error
Expand Down Expand Up @@ -1258,12 +1259,12 @@ private sealed class MultiGesture {
data class StringGesture(val data: String) : MultiGesture()
}

private open class TestState : MultiMachineState<MultiGesture, String>() {
private open class TestState : MultiMachineState<MultiGesture, String, Any, Any>() {

private data object IntKey : MachineKey<Int, Int>(null) // Int for gesture and state
private data object StringKey : MachineKey<String, String>(null) // String for gesture and state

override val container: ProxyMachineContainer = AllTogetherMachineContainer(
override val container: ProxyMachineContainer<Any, Any> = AllTogetherMachineContainer(
listOf(
object : MachineInit<Int, Int> {
override val key: MachineKey<Int, Int> = IntKey
Expand Down Expand Up @@ -1310,8 +1311,8 @@ private open class TestState : MultiMachineState<MultiGesture, String>() {
private data object StringKey : MachineKey<String, String>(null) // String for gesture and state

// ... machine init omitted
override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): String {

override fun mapUiState(provider: UiStateProvider<Any>, changedKey: MachineKey<*, out Any>?): String {
val i: Int = provider.getValue(IntKey) // Cast to Int
val s: String = provider.getValue(StringKey) // Cast to String
return "$i - $s" // Combined state of any kind you like
Expand Down Expand Up @@ -1347,7 +1348,7 @@ private open class TestState : MultiMachineState<MultiGesture, String>() {
// ... machine init omitted

// Our parent gesture is
override fun mapGesture(parent: MultiGesture, processor: GestureProcessor) = when(parent) {
override fun mapGesture(parent: MultiGesture, processor: GestureProcessor<Any, Any>) = when(parent) {
is MultiGesture.IntGesture -> {
processor.process(IntKey, parent.data) // Int expected
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ package com.motorro.commonstatemachine
* Common state machine input - from the outside world to the current state
* @param G UI gesture
*/
interface MachineInput<G: Any> {
interface MachineInput<in G: Any> {
/**
* Updates state with UI gesture
* @param gesture UI gesture to proceed
Expand Down Expand Up @@ -51,7 +51,7 @@ interface MachineOutput<G: Any, U: Any> {
/**
* Current public machine status
*/
interface MachineStatus<U : Any> {
interface MachineStatus<out U : Any> {
/**
* Checks if machine is started
*/
Expand All @@ -71,6 +71,11 @@ interface MachineStatus<U : Any> {
*/
interface CommonStateMachine<G: Any, U: Any> : MachineInput<G>, MachineOutput<G, U>, MachineStatus<U> {

/**
* Starts the machine
*/
fun start()

/**
* Base state-machine implementation
* @param G UI gesture
Expand Down Expand Up @@ -104,9 +109,9 @@ interface CommonStateMachine<G: Any, U: Any> : MachineInput<G>, MachineOutput<G,
}

/**
* Starts machine
* Starts the machine
*/
fun start() {
override fun start() {
if (started.not()) {
activeState = init()
startMachineState()
Expand Down Expand Up @@ -136,5 +141,4 @@ interface CommonStateMachine<G: Any, U: Any> : MachineInput<G>, MachineOutput<G,
activeState.start(this)
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import com.motorro.commonstatemachine.lifecycle.MachineLifecycle
*/
internal class ActiveStateMachine<G: Any, U: Any>(
init: MachineInit<G, U>,
onUiChanged: (MachineKey<*, *>, Any) -> Unit
onUiChanged: (MachineKey<G, U>, U) -> Unit
) : CommonStateMachine<G, U>, Activated {
/**
* Machine lifecycle
Expand All @@ -41,7 +41,10 @@ internal class ActiveStateMachine<G: Any, U: Any>(
{ onUiChanged(init.key, it.child) }
)

init {
/**
* Starts the machine
*/
override fun start() {
machine.start()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,55 +13,71 @@

package com.motorro.commonstatemachine.multi

import com.motorro.commonstatemachine.CommonStateMachine
/**
* Provides access to the state-machine
* @param CG Child gesture system
* @param CU Child UI-state system
*/
interface MachineAccess<CG: Any, CU: Any> {
/**
* Keys collection
*/
val keys: Set<MachineKey<*, out CU>>

/**
* Retrieves UI state.
* [MachineInit] and [key] bind types securely.
* @param U Concrete UI state bound with the [key], subtype of CU
* @param key Machine key
*/
fun <U: CU> getState(key: MachineKey<*, U>): U?

/**
* Processes machine gesture.
* [MachineInit] and [key] bind types securely.
* @param G Concrete gesture, subtype of th [CG]
* @param key Machine key
* @param gesture Gesture to process
*/
fun <G: CG> process(key: MachineKey<G, out CU>, gesture: G)
}

/**
* Retrieves a ui-state given the [MachineKey]
*/
interface UiStateProvider {
interface UiStateProvider<CU: Any> {

/**
* Retrieves all running machine keys
*/
fun getMachineKeys(): Set<MachineKey<*, *>>
fun getMachineKeys(): Set<MachineKey<*, out CU>>

/**
* Gets a concrete UI-state
* @param key Machine key your state is bound to
* @throws IllegalStateException if state is not found in common state
*/
fun <U: Any> getValue(key: MachineKey<*, U>): U = checkNotNull(get(key)) {
fun <U: CU> getValue(key: MachineKey<*, out U>): U = checkNotNull(get(key)) {
"Key $key not found in machine map"
}

/**
* Gets a concrete UI-state
* @param key Machine key your state is bound to
*/
operator fun <U: Any> get(key: MachineKey<*, U>): U?
operator fun <U: CU> get(key: MachineKey<*, U>): U?
}

/**
* Redirects your gesture to be processed with a child machine
* identified by [MachineKey]
*/
interface GestureProcessor {
interface GestureProcessor<CG: Any, CU: Any> {
/**
* Redirects your gesture to be processed by child machine
* if machine identified by [key] is found
* @param key Machine key
* @param gesture Gesture to process
*/
fun <G: Any> process(key: MachineKey<G, *>, gesture: G)
}

/**
* Runs [block] with machine stored in [machineMap] under this key
*/
@Suppress("UNCHECKED_CAST")
internal inline fun <G: Any, U: Any, R> withMachine(
key: MachineKey<G, U>,
machineMap: MachineMap,
block: CommonStateMachine<G, U>.() -> R
): R? = (machineMap[key] as? CommonStateMachine<G, U>)?.block()

fun <G: CG> process(key: MachineKey<G, out CU>, gesture: G)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ import com.motorro.commonstatemachine.CommonMachineState
* - Override [mapUiState] to build a combined UI state af all the machines
* - Override [container] with a [ProxyMachineContainer] of your choice
*/
abstract class MultiMachineState<PG: Any, PU: Any> : CommonMachineState<PG, PU>() {
abstract class MultiMachineState<PG: Any, PU: Any, CG: Any, CU: Any> : CommonMachineState<PG, PU>() {
/**
* Proxy machines container
*/
protected abstract val container: ProxyMachineContainer
protected abstract val container: ProxyMachineContainer<CG, CU>

/**
* A part of [start] template to initialize state
Expand All @@ -45,20 +45,18 @@ abstract class MultiMachineState<PG: Any, PU: Any> : CommonMachineState<PG, PU>(
* Updates machine view-state
*/
@Suppress("UNUSED_PARAMETER")
private fun onUiStateChange(key: MachineKey<*, *>, uiState: Any) {
private fun onUiStateChange(key: MachineKey<*, out CU>, uiState: CU) {
setUiState(buildUiState(key))
}

/**
* Builds common UI state
*/
private fun buildUiState(changedKey: MachineKey<*, *>?): PU {
val machineMap = container.getMachines()
val uiStateProvider = object : UiStateProvider {
override fun getMachineKeys(): Set<MachineKey<*, *>> = machineMap.keys
override fun <U : Any> get(key: MachineKey<*, U>): U? {
return withMachine(key, machineMap) { getUiState() }
}
private fun buildUiState(changedKey: MachineKey<*, out CU>?): PU {
val access = container.machineAccess
val uiStateProvider = object : UiStateProvider<CU> {
override fun getMachineKeys(): Set<MachineKey<*, out CU>> = access.keys
override fun <U : CU> get(key: MachineKey<*, U>): U? = access.getState(key)
}
return mapUiState(uiStateProvider, changedKey)
}
Expand All @@ -74,10 +72,10 @@ abstract class MultiMachineState<PG: Any, PU: Any> : CommonMachineState<PG, PU>(
* A part of [process] template to process UI gesture
*/
override fun doProcess(gesture: PG) {
val machineMap = container.getMachines()
val processor = object : GestureProcessor {
override fun <G : Any> process(key: MachineKey<G, *>, gesture: G) {
withMachine(key, machineMap) { process(gesture) }
val access = container.machineAccess
val processor = object : GestureProcessor<CG, CU> {
override fun <G : CG> process(key: MachineKey<G, out CU>, gesture: G) {
access.process(key, gesture)
}
}
mapGesture(gesture, processor)
Expand All @@ -88,13 +86,13 @@ abstract class MultiMachineState<PG: Any, PU: Any> : CommonMachineState<PG, PU>(
* @param parent Parent gesture
* @param processor Use it to send child gesture to the relevant child machine
*/
protected abstract fun mapGesture(parent: PG, processor: GestureProcessor)
protected abstract fun mapGesture(parent: PG, processor: GestureProcessor<CG, CU>)

/**
* Maps combined child UI state to parent
* @param provider Provides child UI states
* @param changedKey Key of machine that changed the UI state. Null if called explicitly via [updateUi]
* @see updateUi
*/
protected abstract fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): PU
protected abstract fun mapUiState(provider: UiStateProvider<CU>, changedKey: MachineKey<*, out CU>?): PU
}
Loading

0 comments on commit 279fdd9

Please sign in to comment.