Skip to content

Commit de111b3

Browse files
committed
api: optimize state property accessor to be inline
1 parent fee4e92 commit de111b3

File tree

13 files changed

+78
-54
lines changed

13 files changed

+78
-54
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package pro.respawn.flowmvi.benchmarks
2+
3+
import kotlinx.coroutines.awaitCancellation
4+
import kotlinx.coroutines.isActive
5+
import kotlinx.coroutines.launch
6+
import kotlinx.coroutines.runBlocking
7+
import kotlinx.coroutines.yield
8+
import pro.respawn.flowmvi.benchmarks.setup.BenchmarkIntent.Increment
9+
import pro.respawn.flowmvi.benchmarks.setup.optimized.optimizedStore
10+
11+
fun main() = runBlocking {
12+
println(ProcessHandle.current().pid())
13+
val store = optimizedStore(this)
14+
launch {
15+
while (isActive) {
16+
store.intent(Increment)
17+
yield()
18+
}
19+
}
20+
awaitCancellation()
21+
Unit
22+
}

compose/src/commonMain/kotlin/pro/respawn/flowmvi/compose/dsl/ComposeDsl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import pro.respawn.flowmvi.api.MVIIntent
1818
import pro.respawn.flowmvi.api.MVIState
1919
import pro.respawn.flowmvi.api.SubscriberLifecycle
2020
import pro.respawn.flowmvi.api.SubscriptionMode
21+
import pro.respawn.flowmvi.dsl.state
2122
import pro.respawn.flowmvi.dsl.subscribe
2223
import pro.respawn.flowmvi.util.immediateOrDefault
2324
import kotlin.jvm.JvmName

core/src/commonMain/kotlin/pro/respawn/flowmvi/StoreImpl.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
@file:OptIn(InternalFlowMVIAPI::class)
2+
13
package pro.respawn.flowmvi
24

35
import kotlinx.coroutines.CoroutineScope
46
import kotlinx.coroutines.Job
57
import kotlinx.coroutines.cancel
68
import kotlinx.coroutines.flow.Flow
79
import kotlinx.coroutines.launch
10+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
811
import pro.respawn.flowmvi.annotation.NotIntendedForInheritance
912
import pro.respawn.flowmvi.api.ActionProvider
1013
import pro.respawn.flowmvi.api.ActionReceiver
@@ -105,8 +108,8 @@ internal class StoreImpl<S : MVIState, I : MVIIntent, A : MVIAction>(
105108
}
106109

107110
// region contract
108-
override val state: S by stateModule::state
109111
override val name by config::name
112+
override val states by stateModule::states
110113
override val actions: Flow<A> by _actions::actions
111114
override fun send(action: A) = _actions.send(action)
112115
override suspend fun emit(intent: I) = intents.emit(intent)

core/src/commonMain/kotlin/pro/respawn/flowmvi/annotation/InternalFlowMVIAPI.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package pro.respawn.flowmvi.annotation
22

33
private const val Message = """
4-
This API is internal to the library.
5-
Do not use this API directly as public API already exists for the same thing.
6-
If you have the use-case for this api you can't avoid, please submit an issue.
4+
This API is internal to the library and is exposed for performance reasons.
5+
Do NOT use this API directly as public API already exists for the same thing.
6+
If you have the use-case for this api you can't avoid, please submit an issue BEFORE you use it.
7+
This API may be removed, changed or change behavior without prior notice at any moment.
78
"""
89

910
/**
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
package pro.respawn.flowmvi.api
22

3+
import kotlinx.coroutines.flow.StateFlow
4+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
5+
36
/**
47
* [StateReceiver] version that can only accept immediate state updates. It is recommended to use [StateReceiver] and
58
* its methods if possible. See the method docs for details
69
*/
7-
public interface ImmediateStateReceiver<S : MVIState> {
10+
public interface ImmediateStateReceiver<S : MVIState> : StateProvider<S> {
811

912
@FlowMVIDSL
1013
public fun compareAndSet(old: S, new: S): Boolean
1114

12-
/**
13-
* Obtain the current value of state in an unsafe manner.
14-
* It is recommended to always use [StateReceiver.withState] or [StateReceiver.updateState] as obtaining this value can lead
15-
* to data races when the state transaction changes the value of the state previously obtained.
16-
*/
17-
@DelicateStoreApi
18-
public val state: S
15+
@InternalFlowMVIAPI
16+
override val states: StateFlow<S>
1917
}

core/src/commonMain/kotlin/pro/respawn/flowmvi/api/ImmutableStore.kt

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package pro.respawn.flowmvi.api
22

33
import kotlinx.coroutines.CoroutineScope
44
import kotlinx.coroutines.Job
5-
import kotlinx.coroutines.flow.Flow
5+
import kotlinx.coroutines.flow.StateFlow
6+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
67
import pro.respawn.flowmvi.api.lifecycle.ImmutableStoreLifecycle
78
import pro.respawn.flowmvi.api.lifecycle.StoreLifecycle
89

910
/**
1011
* A [Store] that does not allow sending intents.
1112
* @see Store
1213
*/
13-
public interface ImmutableStore<out S : MVIState, in I : MVIIntent, out A : MVIAction> : ImmutableStoreLifecycle {
14+
public interface ImmutableStore<out S : MVIState, in I : MVIIntent, out A : MVIAction> :
15+
ImmutableStoreLifecycle,
16+
StateProvider<S> {
1417

1518
/**
1619
* The name of the store. Used for debugging purposes and when storing multiple stores in a collection.
@@ -48,17 +51,8 @@ public interface ImmutableStore<out S : MVIState, in I : MVIIntent, out A : MVIA
4851
*/
4952
public fun CoroutineScope.subscribe(block: suspend Provider<S, I, A>.() -> Unit): Job
5053

51-
/**
52-
* Obtain the current state in an unsafe manner.
53-
* This property is not thread-safe and parallel state updates will introduce a race condition when not
54-
* handled properly.
55-
* Such race conditions arise when using multiple data streams such as [Flow]s.
56-
*
57-
* Accessing the state this way will **circumvent ALL plugins**.
58-
*/
59-
@DelicateStoreApi
60-
public val state: S
61-
54+
@InternalFlowMVIAPI
55+
override val states: StateFlow<S>
6256
override fun hashCode(): Int
6357
override fun equals(other: Any?): Boolean
6458
}
Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package pro.respawn.flowmvi.api
22

3-
import kotlinx.coroutines.flow.Flow
43
import kotlinx.coroutines.flow.StateFlow
54

65
/**
@@ -10,20 +9,7 @@ import kotlinx.coroutines.flow.StateFlow
109
public interface StateProvider<out S : MVIState> {
1110

1211
/**
13-
* A flow of states to be handled by the subscriber.
12+
* A flow of states to be rendered by the subscriber.
1413
*/
1514
public val states: StateFlow<S>
16-
17-
/**
18-
* Obtain the current state in an unsafe manner.
19-
* This property is not thread-safe and parallel state updates will introduce a race condition when not
20-
* handled properly.
21-
* Such race conditions arise when using multiple data streams such as [Flow]s.
22-
*
23-
* Accessing and modifying the state this way will **circumvent ALL plugins** and will not make state updates atomic.
24-
*
25-
* Consider accessing state via [StateReceiver.withState] or [StateReceiver.updateState] instead.
26-
*/
27-
@DelicateStoreApi
28-
public val state: S
2915
}

core/src/commonMain/kotlin/pro/respawn/flowmvi/api/Store.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ public interface Store<out S : MVIState, in I : MVIIntent, out A : MVIAction> :
2020

2121
// override with a mutable return type
2222
override fun start(scope: CoroutineScope): StoreLifecycle
23+
2324
}

core/src/commonMain/kotlin/pro/respawn/flowmvi/dsl/StateDsl.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import pro.respawn.flowmvi.api.FlowMVIDSL
55
import pro.respawn.flowmvi.api.ImmediateStateReceiver
66
import pro.respawn.flowmvi.api.MVIState
77
import pro.respawn.flowmvi.api.PipelineContext
8+
import pro.respawn.flowmvi.api.StateProvider
89
import pro.respawn.flowmvi.api.StateReceiver
910
import pro.respawn.flowmvi.api.StoreConfiguration
1011
import pro.respawn.flowmvi.exceptions.InvalidStateException
@@ -14,6 +15,19 @@ import kotlin.contracts.InvocationKind
1415
import kotlin.contracts.contract
1516
import kotlin.jvm.JvmName
1617

18+
/**
19+
* Obtain the current state in an unsafe manner.
20+
*
21+
* This property is not thread-safe and parallel state updates will introduce a race condition when not
22+
* handled properly.
23+
* Such race conditions arise when using multiple data streams such as [Flow]s.
24+
*
25+
* Accessing and modifying the state this way will **circumvent ALL plugins** and will not make state updates atomic.
26+
*
27+
* Consider accessing state via [StateReceiver.withState] or [StateReceiver.updateState] instead.
28+
*/
29+
public inline val <S : MVIState> StateProvider<S>.state get() = states.value
30+
1731
/**
1832
* A function that obtains current state and updates it atomically (in the thread context), and non-atomically in
1933
* the coroutine context, which means it can cause races when you want to update states in parallel.

core/src/commonMain/kotlin/pro/respawn/flowmvi/modules/StateModule.kt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,31 @@
1+
@file:OptIn(InternalFlowMVIAPI::class)
2+
13
package pro.respawn.flowmvi.modules
24

35
import kotlinx.coroutines.flow.MutableStateFlow
46
import kotlinx.coroutines.flow.StateFlow
57
import kotlinx.coroutines.flow.asStateFlow
68
import kotlinx.coroutines.sync.Mutex
7-
import pro.respawn.flowmvi.api.DelicateStoreApi
9+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
810
import pro.respawn.flowmvi.api.ImmediateStateReceiver
911
import pro.respawn.flowmvi.api.MVIAction
1012
import pro.respawn.flowmvi.api.MVIIntent
1113
import pro.respawn.flowmvi.api.MVIState
1214
import pro.respawn.flowmvi.api.PipelineContext
13-
import pro.respawn.flowmvi.api.StateProvider
1415
import pro.respawn.flowmvi.dsl.updateStateImmediate
1516
import pro.respawn.flowmvi.util.withReentrantLock
1617

1718
internal class StateModule<S : MVIState, I : MVIIntent, A : MVIAction>(
1819
initial: S,
1920
atomic: Boolean,
2021
private val transform: (suspend PipelineContext<S, I, A>.(old: S, new: S) -> S?)?
21-
) : StateProvider<S>, ImmediateStateReceiver<S> {
22+
) : ImmediateStateReceiver<S> {
2223

2324
@Suppress("VariableNaming")
2425
private val _states = MutableStateFlow(initial)
2526
override val states: StateFlow<S> = _states.asStateFlow()
2627
private val mutex = if (atomic) Mutex() else null
2728

28-
@DelicateStoreApi
29-
override val state: S by states::value
30-
3129
override fun compareAndSet(expect: S, new: S) = _states.compareAndSet(expect, new)
3230

3331
suspend inline fun withState(

core/src/jvmTest/kotlin/pro/respawn/flowmvi/test/store/StoreStatesTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import kotlinx.coroutines.awaitCancellation
77
import pro.respawn.flowmvi.dsl.LambdaIntent
88
import pro.respawn.flowmvi.dsl.intent
99
import pro.respawn.flowmvi.dsl.send
10+
import pro.respawn.flowmvi.dsl.state
1011
import pro.respawn.flowmvi.dsl.updateStateImmediate
1112
import pro.respawn.flowmvi.test.subscribeAndTest
1213
import pro.respawn.flowmvi.util.TestAction

test/src/commonMain/kotlin/pro/respawn/flowmvi/test/StoreTestScope.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package pro.respawn.flowmvi.test
22

3+
import kotlinx.coroutines.flow.StateFlow
34
import kotlinx.coroutines.flow.firstOrNull
45
import kotlinx.coroutines.withTimeout
5-
import pro.respawn.flowmvi.api.DelicateStoreApi
6+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
67
import pro.respawn.flowmvi.api.MVIAction
78
import pro.respawn.flowmvi.api.MVIIntent
89
import pro.respawn.flowmvi.api.MVIState
@@ -14,14 +15,14 @@ import kotlin.test.assertIs
1415
/**
1516
* A class which implements a dsl for testing [Store].
1617
*/
18+
@OptIn(InternalFlowMVIAPI::class)
1719
public class StoreTestScope<S : MVIState, I : MVIIntent, A : MVIAction> @PublishedApi internal constructor(
1820
public val provider: Provider<S, I, A>,
1921
public val store: Store<S, I, A>,
2022
public val timeoutMs: Long = 3000L,
2123
) : Store<S, I, A> by store, Provider<S, I, A> by provider {
2224

23-
@OptIn(DelicateStoreApi::class)
24-
override val state: S by store::state
25+
override val states: StateFlow<S> by provider::states
2526
override fun hashCode(): Int = store.hashCode()
2627
override fun equals(other: Any?): Boolean = store == other
2728
override suspend fun emit(intent: I): Unit = store.emit(intent)

test/src/commonMain/kotlin/pro/respawn/flowmvi/test/plugin/TestPipelineContext.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ package pro.respawn.flowmvi.test.plugin
44

55
import kotlinx.atomicfu.atomic
66
import kotlinx.coroutines.Job
7+
import kotlinx.coroutines.flow.MutableStateFlow
8+
import kotlinx.coroutines.flow.StateFlow
9+
import kotlinx.coroutines.flow.asStateFlow
710
import kotlinx.coroutines.launch
811
import pro.respawn.flowmvi.annotation.ExperimentalFlowMVIAPI
12+
import pro.respawn.flowmvi.annotation.InternalFlowMVIAPI
913
import pro.respawn.flowmvi.annotation.NotIntendedForInheritance
1014
import pro.respawn.flowmvi.api.DelicateStoreApi
1115
import pro.respawn.flowmvi.api.MVIAction
@@ -15,21 +19,21 @@ import pro.respawn.flowmvi.api.PipelineContext
1519
import pro.respawn.flowmvi.api.StoreConfiguration
1620
import pro.respawn.flowmvi.api.StorePlugin
1721
import pro.respawn.flowmvi.api.lifecycle.StoreLifecycle
22+
import pro.respawn.flowmvi.dsl.state
1823
import pro.respawn.flowmvi.dsl.updateStateImmediate
1924
import pro.respawn.flowmvi.test.TestStoreLifecycle
2025
import pro.respawn.flowmvi.test.ensureStarted
2126

22-
@OptIn(ExperimentalFlowMVIAPI::class, NotIntendedForInheritance::class)
27+
@OptIn(ExperimentalFlowMVIAPI::class, NotIntendedForInheritance::class, InternalFlowMVIAPI::class)
2328
internal class TestPipelineContext<S : MVIState, I : MVIIntent, A : MVIAction> @PublishedApi internal constructor(
2429
override val config: StoreConfiguration<S>,
2530
val plugin: StorePlugin<S, I, A>,
2631
) : PipelineContext<S, I, A>, StoreLifecycle by TestStoreLifecycle(config.coroutineContext[Job]) {
2732

2833
override val coroutineContext by config::coroutineContext
2934

30-
private var _state = atomic(config.initial)
31-
override val state by _state::value
32-
35+
private val _state = MutableStateFlow(config.initial)
36+
override val states: StateFlow<S> = _state.asStateFlow()
3337
override fun compareAndSet(old: S, new: S): Boolean = _state.compareAndSet(old, new)
3438

3539
@DelicateStoreApi
@@ -54,7 +58,7 @@ internal class TestPipelineContext<S : MVIState, I : MVIIntent, A : MVIAction> @
5458

5559
override suspend fun updateState(transform: suspend S.() -> S) = with(plugin) {
5660
ensureStarted()
57-
updateStateImmediate { onState(state, state.transform()) ?: this }
61+
updateStateImmediate { onState(this, transform()) ?: this }
5862
}
5963

6064
override suspend fun withState(block: suspend S.() -> Unit) {

0 commit comments

Comments
 (0)