-
Notifications
You must be signed in to change notification settings - Fork 10
State Machine
State machine is a State pattern built-in Fairy framework allows you to easily built things related to state easily. You can have multiple states in a state machine, and each state can have multiple handlers for different signals and events.
Starting up, you need to create a builder instance for StateMachine.
StateMachineBuilder stateMachineBuilder = StateMachine.builder();
After having state machine, we can start giving it some states by calling stateMachineBuilder.state(State)
.
And then you should start seeing a lot of methods you can use to add behaviors to the state.
You can handle 3 of the life cycles in each states, which is start, tick, and stop.
You can also handle 3 of them using StateHandler
, a StateHandler can either be implemented using custom class or using StateHandler.builder()
.
stateMachine.state(State.of("A"))
// Handle the start of the state A
.handleStart(() -> System.out.println("A has started in handleStart()!"))
// Handle the ticking
.handleTick(() -> System.out.println("A is being ticked in handleTick()!"))
// Handle the stop
.handleStop(() -> System.out.println("A has stopped in handleStop()!"))
// Or do 3 of them in StateHandler, you can also override StateHandler to implement it.
.handler(StateHandler.builder()
.onStart(() -> System.out.println("We can also do all of them in StateHandler approach!"))
.build());
After having states,
You must specify the initial state of the state machine by calling stateMachineBuilder.initialState(State)
.
And optionally specify the interval of the ticking by calling stateMachineBuilder.interval(long)
.
finally build and start using it by calling StateMachine stateMachine = stateMachineBuilder.build()
.
You can use stateMachine.transform(State, Signal)
to transform current state to a specified state.
Inside the state machine, it will handle the transition and state life cycle for you.
This is basically all you need for a simple state machine, but to make it easier, we provided another system called Signal.
Signal are the system allows you to fire and listen to different events easier between states. A signal can be fired from anywhere, and you can listen it globally by event node, listen from state machine, or listen from individual states.
To have a Signal
instance, simply use Signal.of(String)
to get one.
To fire a signal, you simply call stateMachine.signal(Signal)
.
And to add transition to a signal, you can do it while in the state machine building codes.
stateMachineBuilder.transition()
// when signal A is fired, transform to state B
.when(Signal.of("A")).to(State.of("B"));
stateMachineBuilder.transition()
// when signal E is fired and state is on A, transform to state C
.on(State.of("A"))
.when(Signal.of("E")).to(State.of("C"));
stateMachineBuilder.transition()
// and you can also add multiple handlers by doing this
.when(Signal.of("A"), t -> {
t.run(() -> System.out.println("A is fired!"));
t.to(State.of("B"));
})
.when(Signal.of("B"), t -> {
t.run(() -> System.out.println("B is fired!"));
t.to(State.of("C"));
});
private enum ExampleState implements State {
A, B, C
}
private enum ExampleSignal implements Signal {
T1, T2, T3
}
StateMachineBuilder builder = StateMachine.builder();
builder.initialState(ExampleState.A);
builder.state(ExampleState.A)
.handleStart(() -> System.out.println("A started"))
.handleStop(() -> System.out.println("A stopped"));
builder.state(ExampleState.B)
.handleStart(() -> System.out.println("B started"))
.handleStop(() -> System.out.println("B stopped"));
builder.state(ExampleState.C)
.handleStart(() -> System.out.println("C started"))
.handleStop(() -> System.out.println("C stopped"));
builder.transition()
.when(ExampleSignal.T1).to(ExampleState.B);
builder.transition()
.when(ExampleSignal.T2).to(ExampleState.C);
builder.transition()
.when(ExampleSignal.T3).to(ExampleState.A);
// Start at A, current state should be A
StateMachine stateMachine = builder.build();
Assertions.assertEquals(ExampleState.A, stateMachine.getCurrentState());
// Fire T1, current state should be B
stateMachine.signal(ExampleSignal.T1);
Assertions.assertEquals(ExampleState.B, stateMachine.getCurrentState());
// Fire T2, current state should be C
stateMachine.signal(ExampleSignal.T2);
Assertions.assertEquals(ExampleState.C, stateMachine.getCurrentState());