Skip to content

Commit 628693b

Browse files
authored
fix: resolving transition object creation issue (#109)
1 parent 5ae8020 commit 628693b

File tree

2 files changed

+48
-37
lines changed

2 files changed

+48
-37
lines changed

src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/StateMachine.kt

Lines changed: 42 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import at.ac.uibk.dps.cirrina.csm.Csml.EventChannel
66
import at.ac.uibk.dps.cirrina.execution.`object`.StateMachine.Factory
77
import at.ac.uibk.dps.cirrina.spec.Instance
88
import at.ac.uibk.dps.cirrina.spec.StateMachine as StateMachineSpec
9-
import at.ac.uibk.dps.cirrina.spec.Transition as TransitionSpec
109
import dagger.assisted.Assisted
1110
import dagger.assisted.AssistedFactory
1211
import dagger.assisted.AssistedInject
1312
import io.micrometer.observation.Observation
1413
import io.micrometer.observation.ObservationRegistry
15-
import kotlin.collections.get
1614
import kotlin.properties.Delegates
1715
import kotlinx.coroutines.*
1816
import kotlinx.coroutines.channels.Channel
@@ -23,6 +21,8 @@ private val logger = KotlinLogging.logger {}
2321

2422
private const val VAR_PREFIX = "$"
2523

24+
private data class ActiveTransition(val transition: Transition, val isOr: Boolean)
25+
2626
class StateMachine
2727
@AssistedInject
2828
internal constructor(
@@ -51,6 +51,25 @@ internal constructor(
5151
private val stateInstances =
5252
specification.vertexSet().associate { it.name to stateFactory.create(it, this) }
5353

54+
private val onTransitions: Map<String, Map<String, List<Transition>>> =
55+
specification.vertexSet().associate { state ->
56+
state.name to
57+
specification
58+
.outgoingEdgesOf(state)
59+
.filter { it.event != null }
60+
.groupBy { it.event!! }
61+
.mapValues { (_, specs) -> specs.map { transitionFactory.create(it) } }
62+
}
63+
64+
private val alwaysTransitions: Map<String, List<Transition>> =
65+
specification.vertexSet().associate { state ->
66+
state.name to
67+
specification
68+
.outgoingEdgesOf(state)
69+
.filter { it.event == null }
70+
.map { transitionFactory.create(it) }
71+
}
72+
5473
private val started = CompletableDeferred<Unit>()
5574
private val eventChannel = Channel<Event>(Channel.UNLIMITED)
5675
private var activeState: State? = null
@@ -131,10 +150,9 @@ internal constructor(
131150
}
132151
}
133152

134-
private fun handleEvent(event: Event): Transition? {
153+
private fun handleEvent(event: Event): ActiveTransition? {
135154
val current = activeState ?: error("received event '$event' before entering initial state")
136-
val candidates =
137-
specification.getOnTransitionsFromStateByEventName(current.specification, event.topic)
155+
val candidates = onTransitions[current.specification.name]?.get(event.topic).orEmpty()
138156

139157
if (candidates.isEmpty()) return null
140158

@@ -153,36 +171,36 @@ internal constructor(
153171
return true
154172
}
155173

156-
private fun step(initialTransition: Transition) {
157-
var currentTransition: Transition? = initialTransition
174+
private fun step(initialTransition: ActiveTransition) {
175+
var currentActive: ActiveTransition? = initialTransition
158176

159-
while (currentTransition != null) {
160-
val transition = currentTransition
177+
while (currentActive != null) {
178+
val transition = currentActive.transition
179+
val isOr = currentActive.isOr
161180

162181
if (transition.isInternal) {
163-
doTransition(transition)
182+
doTransition(transition, isOr)
164183
return
165184
}
166185

167-
val target =
168-
stateInstances[transition.targetStateName]
169-
?: error("target state '${transition.targetStateName}' not found")
186+
val targetName = transition.targetStateName(isOr) ?: error("target state string is null")
187+
val target = stateInstances[targetName] ?: error("target state '$targetName' not found")
170188
val current = activeState ?: error("no active state to transition from")
171189

172190
doExit(current)
173-
doTransition(transition)
191+
doTransition(transition, isOr)
174192

175-
currentTransition = doEnter(target)
193+
currentActive = doEnter(target)
176194
}
177195
}
178196

179-
private fun trySelect(transitions: List<TransitionSpec>, evalExtent: Extent): Transition? {
197+
private fun trySelect(transitions: List<Transition>, evalExtent: Extent): ActiveTransition? {
180198
val selected =
181199
transitions.mapNotNull { transition ->
182-
val result = transition.evaluate(evalExtent)
200+
val spec = transition.specification
183201
when {
184-
result -> transitionFactory.create(transition, isOr = false)
185-
transition.or != null -> transitionFactory.create(transition, isOr = true)
202+
spec.evaluate(evalExtent) -> ActiveTransition(transition, isOr = false)
203+
spec.or != null -> ActiveTransition(transition, isOr = true)
186204
else -> null
187205
}
188206
}
@@ -194,11 +212,11 @@ internal constructor(
194212
}
195213
}
196214

197-
private fun doEnter(state: State): Transition? =
215+
private fun doEnter(state: State): ActiveTransition? =
198216
Observation.createNotStarted("stateMachine.enter", observationRegistry)
199217
.lowCardinalityKeyValue("stateMachine.instanceName", name)
200218
.lowCardinalityKeyValue("state.name", state.specification.name)
201-
.observe<Transition?> {
219+
.observe<ActiveTransition?> {
202220
activeState = state
203221

204222
execute(state.entryActions, state)
@@ -209,7 +227,7 @@ internal constructor(
209227
handleTermination()
210228

211229
if (!isTerminated()) {
212-
trySelect(specification.getAlwaysTransitionsFromState(state.specification), extent)
230+
trySelect(alwaysTransitions[state.specification.name].orEmpty(), extent)
213231
} else null
214232
}
215233

@@ -222,9 +240,9 @@ internal constructor(
222240
execute(state.exitActions, state)
223241
}
224242

225-
private fun doTransition(transition: Transition) =
243+
private fun doTransition(transition: Transition, isOr: Boolean) =
226244
Observation.createNotStarted("stateMachine.transition", observationRegistry).observe {
227-
if (!transition.isOr) {
245+
if (!isOr) {
228246
execute(transition.actions, activeState!!)
229247
}
230248
}

src/main/kotlin/at/ac/uibk/dps/cirrina/execution/object/Transition.kt

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,26 +6,19 @@ import dagger.assisted.AssistedFactory
66
import dagger.assisted.AssistedInject
77
import org.jgrapht.traverse.TopologicalOrderIterator
88

9-
class Transition
10-
@AssistedInject
11-
internal constructor(@Assisted private val spec: TransitionSpec, @Assisted val isOr: Boolean) {
12-
val actions: List<Action> = TopologicalOrderIterator(spec.actions).asSequence().toList()
9+
class Transition @AssistedInject internal constructor(@Assisted val specification: TransitionSpec) {
10+
val actions: List<Action> = TopologicalOrderIterator(specification.actions).asSequence().toList()
1311

1412
val isInternal: Boolean
15-
get() = spec.to == null
13+
get() = specification.to == null
1614

17-
val targetStateName: String?
18-
get() = if (isOr) spec.or else spec.to
19-
20-
init {
21-
require(!isOr || spec.or != null) { "or transition must have a valid 'or' target state" }
22-
}
15+
fun targetStateName(isOr: Boolean): String? = if (isOr) specification.or else specification.to
2316

2417
override fun toString(): String =
25-
"${this::class.simpleName}(internal='$isInternal', target='$targetStateName', or='$isOr')"
18+
"${this::class.simpleName}(internal='$isInternal', target='${specification.to}', or='${specification.or}')"
2619

2720
@AssistedFactory
2821
interface Factory {
29-
fun create(spec: TransitionSpec, isOr: Boolean): Transition
22+
fun create(specification: TransitionSpec): Transition
3023
}
3124
}

0 commit comments

Comments
 (0)