Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typed multi machine #10

Merged
merged 5 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading