Skip to content

No autotransition in sub statemachine #544

@mechatheo

Description

@mechatheo

Today I wanted to update the Boost::SML library in our codebase, we used version 1.1.0 and I wanted to switch to 1.1.6. We have an extensive unit test suite and one of the tests failed.

We have the following state machine:

image

With a nested Playing machine:

image

All transitions are automatic, no events are needed. So in the unit test, you typically would set the result of the dice throw up front and when the state machine is instantiated, the test expected it to get into one of the final states immediately. So X or Success

This used to be the case with version 1.1.0, now that I'm trying to upgrade to 1.1.6 the endstate is Playing.Initial. This is especially weird to me, as the initial should always auto-transition.

We generate our state machines from the UML, here is the corresponding amalgamated code:

#ifndef SUBSTATEMACHINEEXITPOINT_STATE_MACHINE_HPP
#define SUBSTATEMACHINEEXITPOINT_STATE_MACHINE_HPP

#include <queue>

namespace SubStateMachineExitPoint {

class UserData
{
    friend struct Callbacks;
    friend struct Machine;

public:
    int m_dice{1};
};

struct Callbacks
{
public:
    Callbacks(UserData& userData) : m_data(userData){}
    bool IsEven() { return m_data.m_dice % 2 == 0; }
    UserData& m_data; 
};

namespace _private {
namespace Events {
struct Lose : public boost::sml::utility::id_impl<2147483646>
{
    static auto c_str() { return "Lose";}
};
struct Anonymous : public boost::sml::utility::id_impl<2147483645>
{
    static auto c_str() { return "Anonymous";}
};
} // namespace Events

struct Lose
{
    static auto c_str() { return "Lose"; }
};
} // namespace _private

namespace Playing {

/* states */
struct Initial
{
    static auto c_str() { return "Initial"; }
};

struct ThrowDice
{
    static auto c_str() { return "ThrowDice"; }
};

/* transition table */
struct Machine
{
    static auto c_str() { return "Playing"; }
    explicit Machine(UserData* data) : m_calls(*data) {}

    auto operator()() const
    {
        using namespace boost;
        using namespace boost::sml;
        using namespace Events;
        /* clang-format off */
        return make_transition_table(
            *sml::state<Initial> = sml::state<ThrowDice>,
            sml::state<ThrowDice> [([=](){return !m_calls.IsEven();})] = sml::state<_private::Lose>,
            sml::state<_private::Lose> / sml::process(_private::Events::Lose{}) = sml::X,
            sml::state<ThrowDice> [([=](){return m_calls.IsEven();})] / process(_private::Events::Anonymous{}) = sml::X
        );
        /* clang-format on */
    }

    mutable Callbacks m_calls;
};

} // namespace Playing

/* states */
struct Initial
{
    static auto c_str() { return "Initial"; }
};

struct Success
{
    static auto c_str() { return "Success";}
};

/* transition table */
struct Machine
{
    static auto c_str() { return "SubStateMachineExitPoint"; }

    explicit Machine(UserData* data) : m_calls(*data) {}

    auto operator()() const
    {
        using namespace boost;
        using namespace boost::sml;
        using namespace Events;
        /* clang-format off */
        return make_transition_table(
            sml::state<Playing::Machine> + sml::event<_private::Events::Lose> = sml::X,
            *sml::state<Initial> = sml::state<Playing::Machine>,
            sml::state<Playing::Machine> + sml::event<_private::Events::Anonymous> = sml::state<Success>
        );
        /* clang-format on */
    }

    mutable Callbacks m_calls;
};

template <typename... SMPolicies>
auto MakeMachine(UserData& data) //-> boost::sml::sm<Machine, SMPolicies...>
{
    return boost::sml::sm<Machine, boost::sml::process_queue<std::queue>, SMPolicies...>{
        Machine{&data}, Playing::Machine{&data}};
}

template <typename Logger, typename... SMPolicies>
auto MakeMachine(
    UserData& data,
    Logger& logger) //-> boost::sml::sm<Machine, boost::sml::logger<Logger>, SMPolicies...>
{
    return boost::sml::sm<Machine, boost::sml::process_queue<std::queue>,
                          boost::sml::logger<Logger>, SMPolicies...>{
        Machine{&data}, Playing::Machine{&data}, logger};
}

} // namespace SubStateMachineExitPoint

#endif /* SUBSTATEMACHINEEXITPOINT_STATE_MACHINE_HPP */

the now-failing test then goes like this:

TEST(BoostSML, SubStateMachineExitPoint)
{
    using namespace SubStateMachineExitPoint;
    UserData data;
    data.m_dice = 2;
    auto winner = MakeMachine(data);
    EXPECT_TRUE(winner.is(state<Success>));

    data.m_dice = 1;
    auto looser = MakeMachine(data);
    EXPECT_TRUE(looser.is(sml::X));
}

@krzysztof-jusiak any ideas on that?

Specifications

  • Version: 1.1.6
  • Platform: Linux Ubuntu 20.04

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions