Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] on_exit never call for initial state #188

Open
dfleury2 opened this issue Dec 7, 2023 · 1 comment
Open

[BUG] on_exit never call for initial state #188

dfleury2 opened this issue Dec 7, 2023 · 1 comment
Labels
bug Something isn't working

Comments

@dfleury2
Copy link

dfleury2 commented Dec 7, 2023

Hi,
Describe the bug
The on_exit is never called for the initial state neither in the main state nor a substate. But the on_entry is sometimes call.

To Reproduce

    #include <hsm/hsm.h>
    
    #include <iostream>
    
    #define ON_ENTRY(STATE)                                                                                                                   \
        static constexpr auto on_entry()                                                                                                      \
        {                                                                                                                                     \
            return [](const auto& event, const auto& source, const auto& target) { std::cout << "      -- ENTRY: " << #STATE << std::endl; }; \
        }
    
    #define ON_EXIT(STATE)                                                                                                                   \
        static constexpr auto on_exit()                                                                                                      \
        {                                                                                                                                    \
            return [](const auto& event, const auto& source, const auto& target) { std::cout << "      -- EXIT: " << #STATE << std::endl; }; \
        }
    
    #define ON(STATE)   \
        ON_ENTRY(STATE) \
        ON_EXIT(STATE)
    
    namespace ee {
    struct Idle {
        ON(Idle);
    };
    
    struct A0 {
        ON(A0);
    };
    // --------------------------------------------------------------------------
    // States
    struct A1 {
        ON(A1);
    };
    
    struct A2 {
        ON(A2);
    };
    
    // --------------------------------------------------------------------------
    // Events
    struct press {};
    struct start {};
    struct stop {};
    
    // --------------------------------------------------------------------------
    // Guard
    const auto success = [](auto /*event*/, auto /*source*/, auto /*target*/) { return true; };
    
    // --------------------------------------------------------------------------
    // Actions
    const auto log = [](auto event, auto source, auto target, const char* msg = "") {
        std::cout << msg << typeid(source).name() << " + " << typeid(event).name() << " = " << typeid(target).name() << std::endl;
    };
    
    // --------------------------------------------------------------------------
    // State machines
    struct SubState {
        static constexpr auto make_transition_table()
        {
            // clang-format off
                return hsm::transition_table(
                    // Source              + Event            [Guard]   / Action  = Target
                    // +-------------------+------------------+---------+---------+----------------------+
                    * hsm::state<A0>       + hsm::event<press>          / log     = hsm::state<A1>,
                      hsm::state<A1>       + hsm::event<press>          / log     = hsm::state<A2>,
                      hsm::state<A2>       + hsm::event<press>          / log     = hsm::state<A0>
                    );
    
            // clang-format on
        }
    
        ON(SubState);
    
        static constexpr auto on_unexpected_event()
        {
            return [](auto& event, const auto& state) { log(event, state, state, "unexpected event: "); };
        }
    };
    
    struct Initial {
    
        static constexpr auto make_transition_table()
        {
            // clang-format off
                return hsm::transition_table(
                    // Source              + Event            [Guard]   / Action  = Target
                    // +-------------------+------------------+---------+---------+----------------------+
                    * hsm::state<Idle>       + hsm::event<start>          / log     = hsm::state<SubState>,
                      hsm::state<SubState>   + hsm::event<stop>           / log     = hsm::state<Idle>
                    );
    
            // clang-format on
        }
    
        ON(Initial);
    
        static constexpr auto on_unexpected_event()
        {
            return [](auto& event, const auto& state) { log(event, state, state, "unexpected event: "); };
        }
    };
    
    }   // namespace ee
    
    int main()
    {
        std::cout << "------------------------------------------- Initial" << std::endl;
        {
            hsm::sm<ee::Initial> fsm;
    
            fsm.process_event(ee::start{});
    
            for (int i = 0; i < 4; ++i) {
                std::cout << "----- process_event(press) ----- " << std::endl;
                fsm.process_event(ee::press{});
            }
    
            fsm.process_event(ee::stop{});
        }
    
        std::cout << "------------------------------------------- SubState" << std::endl;
        {
            hsm::sm<ee::SubState> fsm;
    
            for (int i = 0; i < 4; ++i) {
                std::cout << "----- process_event(press) ----- " << std::endl;
                fsm.process_event(ee::press{});
            }
        }
    }

Expected behavior
On_exit called every time a state is exited (at least when the on_entry was called)

Additional context

output:

------------------------------------------- Initial (with a substate)
struct ee::Idle + struct ee::start = struct ee::A0
-- ENTRY: SubState
-- ENTRY: A0
----- process_event(press) -----
here expecting EXIT: A0

struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
----- process_event(press) -----
-- EXIT: A1
struct ee::A1 + struct ee::press = struct ee::A2
-- ENTRY: A2
----- process_event(press) -----
-- EXIT: A2
struct ee::A2 + struct ee::press = struct ee::A0
-- ENTRY: A0
----- process_event(press) -----

here expecting EXIT: A0

struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
-- EXIT: SubState
<- here the sate on_exit is called which is great
struct ee::SubState + struct ee::stop = struct ee::Idle
-- ENTRY: Idle
<- here ENTRY is called but was not called at the start, nor the EXIT is called

------------------------------------------- SubState
----- process_event(press) -----
here, may be EXIT expected, may by ENTRY too for A0
struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1
----- process_event(press) -----
-- EXIT: A1
struct ee::A1 + struct ee::press = struct ee::A2
-- ENTRY: A2
----- process_event(press) -----
-- EXIT: A2
struct ee::A2 + struct ee::press = struct ee::A0
-- ENTRY: A0
----- process_event(press) -----
here ENTRY: A0 was called, but not EXIT
struct ee::A0 + struct ee::press = struct ee::A1
-- ENTRY: A1

Unfortunately, the code itself is beeyond my template/boost.Hana knowledge, If I have some tips to find the issue or change the behavior, may be I can help.

Regards,

@dfleury2 dfleury2 added the bug Something isn't working label Dec 7, 2023
@dfleury2
Copy link
Author

dfleury2 commented Dec 7, 2023

After some investigations,
I found something that can help (at least for on_exit), in resolve_state.h, function resolveExitAction,

    if constexpr (transition.internal()) {
        return [](auto&&...) {};
    } else if constexpr (has_exit_action(transition.source())) {
        return get_exit_action(transition.source());
    }
    // -----------------------
    // Adding a check for initial state and underlying source state
    else if constexpr (is_initial_state(transition.source()) && has_exit_action(resolveSrc(transition))) {
        return get_exit_action(resolveSrc(transition));
    }
    // -----------------------
    else if constexpr (hasPseudoExitAction) {
        return get_exit_action(resolveSrcParent(transition));
    } else {
        return [](auto&&...) {};
    }

Do you have any opinion about this ?

[EDIT] a simplification do the job, using resolveSrc on transition instead of transition.source()

        if constexpr (transition.internal()) {
            return [](auto&&...) {};
        } else if constexpr (has_exit_action(resolveSrc(transition))) {
            return get_exit_action(resolveSrc(transition));
        } else if constexpr (has_pseudo_exit_action(transition)) {
            return get_exit_action(resolveSrcParent(transition));
        } else {
            return [](auto&&...) {};
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant