Skip to content

Commit 1ccb23c

Browse files
committed
1 parent 742af71 commit 1ccb23c

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// SPDX-FileCopyrightText: 2015 - 2024 Rime community
2+
//
3+
// SPDX-License-Identifier: GPL-3.0-or-later
4+
package com.osfans.trime.util
5+
6+
/**
7+
* Adapted from fcitx5-android.
8+
*
9+
* @see <a href="https://github.com/fcitx5-android/fcitx5-android/blob/b381b414312e4180416dcb1d35b7339d575e6c94/app/src/main/java/org/fcitx/fcitx5/android/utils/EventStateMachine.kt">EventStateMachine</a>
10+
*/
11+
class EventStateMachine<State : Any, Event : EventStateMachine.TransitionEvent<State, B>, B : EventStateMachine.BooleanStateKey>(
12+
private val initialState: State,
13+
private val externalBooleanStates: MutableMap<B, Boolean>,
14+
) {
15+
interface BooleanStateKey {
16+
val name: String
17+
}
18+
19+
interface TransitionEvent<State : Any, B : BooleanStateKey> {
20+
/**
21+
* INVARIANT: No side effects
22+
* @return the next state
23+
*/
24+
fun accept(
25+
initialState: State,
26+
currentState: State,
27+
useBoolean: (B) -> Boolean?,
28+
): State
29+
}
30+
31+
var onNewStateListener: ((State) -> Unit)? = null
32+
33+
var currentState = initialState
34+
private set
35+
36+
/**
37+
* Push an event that may trigger a transition of state
38+
*/
39+
fun push(event: Event) {
40+
val newState = event.accept(initialState, currentState) { externalBooleanStates[it] }
41+
if (newState == currentState) {
42+
return
43+
}
44+
currentState = newState
45+
onNewStateListener?.invoke(newState)
46+
}
47+
48+
/**
49+
* Update boolean states and push an event
50+
*/
51+
fun push(
52+
event: Event,
53+
vararg booleanStates: Pair<B, Boolean>,
54+
) {
55+
booleanStates.forEach {
56+
setBooleanState(it.first, it.second)
57+
}
58+
push(event)
59+
}
60+
61+
fun setBooleanState(
62+
key: B,
63+
value: Boolean,
64+
) {
65+
externalBooleanStates[key] = value
66+
}
67+
68+
fun getBooleanState(key: B) = externalBooleanStates[key]
69+
70+
fun unsafeJump(state: State) {
71+
currentState = state
72+
onNewStateListener?.invoke(state)
73+
}
74+
}
75+
76+
// DSL
77+
class TransitionEventBuilder<State : Any, B : EventStateMachine.BooleanStateKey> {
78+
private var raw: ((State, State, (B) -> Boolean?) -> State)? = null
79+
80+
inner class Builder(val source: State) {
81+
lateinit var target: State
82+
var pred: ((B) -> Boolean?) -> Boolean = { _ -> true }
83+
}
84+
85+
infix fun Builder.transitTo(state: State) =
86+
apply {
87+
target = state
88+
}
89+
90+
infix fun Builder.on(expected: Pair<B, Boolean>) =
91+
apply {
92+
this.pred = { it(expected.first) == expected.second }
93+
}
94+
95+
infix fun Builder.onF(pred: ((B) -> Boolean?) -> Boolean) =
96+
apply {
97+
this.pred = pred
98+
}
99+
100+
val builders = mutableListOf<Builder>()
101+
102+
fun from(state: State) = Builder(state).also { builders += it }
103+
104+
/**
105+
* Use either [from] or [accept] to build the transition event
106+
*/
107+
fun accept(block: ((State, State, (B) -> Boolean?) -> State)) =
108+
apply {
109+
raw = block
110+
}
111+
112+
fun build() =
113+
object : EventStateMachine.TransitionEvent<State, B> {
114+
override fun accept(
115+
initialState: State,
116+
currentState: State,
117+
useBoolean: (B) -> Boolean?,
118+
): State {
119+
if (raw != null) {
120+
return raw!!(initialState, currentState, useBoolean)
121+
}
122+
val filtered = builders.filter { it.source == currentState && it.pred(useBoolean) }
123+
return when (filtered.size) {
124+
0 -> currentState
125+
1 -> filtered[0].target
126+
else -> {
127+
val first = filtered[0].target
128+
first
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
typealias TransitionBuildBlock<State, B> = TransitionEventBuilder<State, B>.() -> Unit
136+
137+
class BuildTransitionEvent<State : Any, B : EventStateMachine.BooleanStateKey>(block: TransitionBuildBlock<State, B>) :
138+
EventStateMachine.TransitionEvent<State, B> {
139+
private val delegate: EventStateMachine.TransitionEvent<State, B> by lazy {
140+
TransitionEventBuilder<State, B>().also(block).build()
141+
}
142+
143+
override fun accept(
144+
initialState: State,
145+
currentState: State,
146+
useBoolean: (B) -> Boolean?,
147+
): State = delegate.accept(initialState, currentState, useBoolean)
148+
}

0 commit comments

Comments
 (0)