I’m having difficulty managing a Stateless4j state machine in a spring application, and perhaps it’s a misunderstanding on my part. I have a workaround but I’d like perspectives and advice.
I have a game with Spring, JavaFX and Stateless4j. In order to synchronise music and video transitions it is very helpful for the statemachine to transition between substates by itself using some variation of:
@Configuration
public class StateMachineConfiguration {
@Autowired
private StateMachineActions stateMachineActions;
@Bean
public StateMachine buildStateMachine() {
StateMachineConfig<State, Trigger> config = new StateMachineConfig<State, Trigger>();
config.configure(ATTRACTING)
.substateOf(RUNNING)
.onEntry(() -> System.out.println("Entering Superstate ATTRACTING"))
.permit(ADVANCE, IDLE)
.onEntry(() -> stateMachineActions::gameThings) // *** This method fires at the state machine
// or
.onEntry(stateMachine.fireTransition(ADVANCE)) // *** State machine is self-firing
;
...
StateMachine stateMachine = new StateMachine(RUNNING, config);
stateMachine.fireInitialTransition();
return stateMachine;
}
}
@Configuration
class StateMachineActions {
@Autowired
StateMachine stateMachine;
public void playIntro() {
mediaClip.play();
sceneController.show();
... etc ...
stateMachine.fire(BUTTON_PRESSED); // *** Problem, Circular reference
}
}
So the state machine references the config (to initialise it) and the config has a reference (possibly hidden in method references / lambdas) and spring isn’t falling for that, and identifies a circular reference. Even if I expressly allow circular references on the spring config it won’t help because the actual call will cause bean construction to fail. Setter @Lazy injection doesn’t help fundamentally either.
The workaround I’ve found is to wrap the call up in a structure like TimerTask or MediaPlayer.onEndOfMedia() so that the actual invocation of the call to the stateMachine is outside of the spring beans:
@Configuration
class StateMachineActions {
public void playIntro() {
mediaClip.play();
sceneController.show();
... etc ...
timer.schedule(new TimerTask() {
public void run() {
stateMachine.fire(BUTTON_PRESSED);
}
}, 100);
}
}
For my current purposes I am happy with this, but I am wondering if I am missing a concept here?
Is it bad to have self-poking Stateless4j machines, even if they work great?
Do I need a Spring idiom that will sidestep this kind of case?
Would a better API to Stateless4j improve matters?
Should the Stateless4j config have a method like .fireAfterEntry() that actually runs the .onEntry()s of the target state to simulate this without needing a reference to the stateMachine (Which would help in only some cases)?