Skip to content
LeeGod edited this page Dec 21, 2022 · 5 revisions

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

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

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"));
        });

Example

    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());
Clone this wiki locally