/Hsm.jl

Primary LanguageJulia

Hsm.jl

This package provides tools for implementing hierarchical state machines in Julia, in an efficient and zero-allocation way.

This library started as a fork of HSM.jl by Andrew Lin. See their accompanying blog post for more information.

Usage

To use this package, define a state machine type. This can contain any state fields you might want to use in your application. It must inherit from Hsm.AbstractStateMachine and contain a field called context of type Hsm.StateMachineContext:

using Hsm

mutable struct MyStateMachine <: Hsm.AbstractStateMachine
    context::Hsm.StateMachineContext
    # Your state variables here
    foo::Int
end

# Create an instances
mysm = MyStateMachine(
    Hsm.StateMachineContext(),
    1
)

Next, describe the possible states and their nesting:

Hsm.add_state!(mysm, name = :State1, ancestor=:Top)
Hsm.add_state!(mysm, name = :State1_substate1, ancestor=:State1)
Hsm.add_state!(mysm, name = :State1_substate2, ancestor=:State1)

An ancestor of :Top should be included for your outermost state.

Now, define the ways in which your state machine enters and exits those (potentially nested) states.

Hsm.register_events!(mysm) do sm

    Hsm.on_initial!(sm, :State1) do
        # When initializing the outer State1, let's
        # immediately transition into this sub-state.
        Hsm.transition!(sm, :State1_substate1)
    end

    Hsm.on_entry!(sm, :State1) do 
        println("entered state 1")
    end
    Hsm.on_entry!(sm, :State1_substate1) do 
        println("entered substate 1")
    end
    Hsm.on_entry!(sm, :State1_substate2) do 
        println("entered substate 2")
    end

    Hsm.on_exit!(sm, :State1_substate2) do 
        println("exitted substate 2")
    end
end

Next, define what events your state machine can response to:

Hsm.register_events!(mysm) do sm
    # If we receive an `Event_A`, we go to the other substate
    Hsm.on_event!(sm, :State1_substate1, :Event_A) do payload
        Hsm.transition!(sm, :State1_substate2)
        # Indicate that we handled this event, so the event won't
        # bubble up to the ancestor state.
        return Hsm.Handled
    end
    # If we receive an `Event_A`, we go to the other substate
    Hsm.on_event!(sm, :State1_substate2, :Event_B) do payload
        Hsm.transition!(sm, :State1_substate1)
        # Indicate that we handled this event, so the event won't
        # bubble up to the ancestor state.
        return Hsm.Handled
    end
end

Finally, we are done! We can use our state machine by dispatching events to it.

# Start by transitioning into the outer state
Hsm.transition!(mysm, :State1)
# After this point, we only interact with the state machine
# by sending events

Hsm.dispatch!(mysm, :Event_A)
Hsm.dispatch!(mysm, :Event_B)

# Calling dispatch! will not allocate except for any allocations
# that occur in the user's callback (printing in this example).