Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PUML (C++-20), experimental

PlantUML basics

PlantUML is a modelling tool with a nice extension for state machine diagrams. The result can then be viewed, for example VSCode has add-ons for previewing the result.

Our usual player example could look like this in PlantUML syntax:

@startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Empty
                Stopped     -> Playing   : play 
                Stopped     -> Open      : open_close    / open_drawer
                Stopped     -> Stopped   : stop

                Open        -> Empty     : open_close    / close_drawer               [can_close_drawer]
                Empty       --> Open     : open_close    / open_drawer
                Empty       ---> Stopped : cd_detected   / store_cd_info              [good_disk_format && always_true]
                Playing     --> Stopped  : stop          / stop_playback
                Playing     -> Paused    : pause
                Playing     --> Open     : open_close    / stop_playback, open_drawer
                Paused      -> Playing   : end_pause     / resume_playback
                Paused      --> Stopped  : stop          / stop_playback
                Paused      --> Open     : open_close    / stop_playback, open_drawer

                Playing : flag CDLoaded
                Playing : entry start_playback [always_true]
                Paused  : entry pause_playback
                Paused  : flag CDLoaded
                Stopped : flag CDLoaded                
            }
 @enduml

A few key points of the syntax:

  • Initial states are marked with [*] -> State

  • Terminal states are marked with Terminal -> [*]

  • Transitions floow the syntax: Source State -> Target State : event / Action1,Action2 [guard conditions]

  • Varying the number of "-" will make the layouter use longe arrows for transitions

  • "--" will make orthogonal regions clearer

  • Supported guard conditions are guard names &&... ||... !... (), for example !G1 && (G2 || G3)

We also want to add these non-standard PlantUML features:

  • Flags. State Name : [keyword] flag flag-name. Add a flag per line.

  • entry / exit actions. State name: [keyword] entry-or-exit comma-separated-actions-sequence [guard conditions]

  • An anonymous transition has an empty event name

  • An any event is defined using the Kleene syntax "*" as the event name.

  • a defer function is already provided for conditional event deferring

  • an internal transition is implemented using an equal source and target state and a "-" before the event name

    Open        -> Open         : -play         / defer
  • An internal Kleen would then be:

    Empty       -> Empty        : -*            / defer               [is_play_event]

Before PUML one had to convert this syntax in the classical MSM transition table, states with entry/exit/flags, events, etc. which takes long and is error-prone.

Good news: This is no more necessary. Now we can copy-paste our PlantUML and directly use it in the code, which gives us:

    // front-end: define the FSM structure 
    struct player_ : public msm::front::state_machine_def<player_>
    {
        // here is the whole FSM structure defined:
        // Initial states [*]
        // Transitions with actions starting with / and separated by ,
        // and guards between [ ]. Supported are !, &&, || and ()
        // State Entry / Exit with guards
        // Flags
        // -> can have different lengths for nicer PlantUML Viewer preview

        BOOST_MSM_PUML_DECLARE_TABLE(
            R"( 
            @startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Empty
                Stopped     -> Playing   : play 
                Stopped     -> Open      : open_close    / open_drawer
                Stopped     -> Stopped   : stop

                Open        -> Empty     : open_close    / close_drawer               [can_close_drawer]
                Empty       --> Open     : open_close    / open_drawer
                Empty       ---> Stopped : cd_detected   / store_cd_info              [good_disk_format && always_true]
                Playing     --> Stopped  : stop          / stop_playback
                Playing     -> Paused    : pause
                Playing     --> Open     : open_close    / stop_playback, open_drawer
                Paused      -> Playing   : end_pause     / resume_playback
                Paused      --> Stopped  : stop          / stop_playback
                Paused      --> Open     : open_close    / stop_playback, open_drawer

                Playing : flag CDLoaded
                Playing : entry start_playback [always_true]
                Paused  : entry pause_playback
                Paused  : flag CDLoaded
                Stopped : flag CDLoaded                
            }
            @enduml
        )"
        )

        // Replaces the default no-transition response.
        template <class FSM, class Event>
        void no_transition(Event const&, FSM&, int)
        {
        }
    };
    // Pick a back-end
    typedef msm::back11::state_machine<player_> player;

The PlantUML string is parsed at compile-time and generates a classical Functor front-end.

States and event do not need to be defined any more, unless one needs to provide them with attributes, or if the state are submachines. Actions and Guards do need to be implemented to reduced bugs because of typos:

namespace boost::msm::front::puml {
    template<>
    struct Action<by_name("close_drawer")>
    {
        template <class EVT, class FSM, class SourceState, class TargetState>
        void operator()(EVT const&, FSM&, SourceState&, TargetState&)
        {
        }
    };
    template<>
    struct Guard<by_name("always_true")>
    {
        template <class EVT, class FSM, class SourceState, class TargetState>
        bool operator()(EVT const&, FSM&, SourceState&, TargetState&)
        {
            return true;
        }
    };    
    }

The events are also referenced by name:

p.process_event(Event<by_name("open_close")>{});                        
                    

Please note that C++-20 is required. A complete implementation of the player is provided.

Composite State Machines

At the moment, the PUML front-end does not support submachines in a single text string, so we need to split. First we define the submachine:

struct playing_ : public msm::front::state_machine_def<playing_>
{
        typedef boost::fusion::vector<PlayingPaused, CDLoaded>        flag_list;
        // optional
        template <class Event, class FSM>
        void on_entry(Event const&, FSM&) { }
        template <class Event, class FSM>
        void on_exit(Event const&, FSM&) { }

        BOOST_MSM_PUML_DECLARE_TABLE(
            R"( 
            @startuml Player
            skinparam linetype polyline
            state Player{
                [*]-> Song1
                Song1     -> Song2   : NextSong         
                Song2     -> Song1   : PreviousSong   / start_prev_song [start_prev_song_guard]
                Song2     -> Song3   : NextSong       / start_next_song
                Song3     -> Song2   : PreviousSong                     [start_prev_song_guard]
            }
            @enduml
        )"
        )

        // Replaces the default no-transition response.
        template <class FSM, class Event>
        void no_transition(Event const&, FSM&, int)
        {
        }
};                    
namespace boost::msm::front::puml {
    // define submachine with desired back-end
    template<>
    struct State<by_name("PlayingFsm")> : public msm::back11::state_machine<playing_>
    {
    };
}
                

We can the reference the submachine within the upper state machine:

PlayingFsm  --> Stopped       : stop          / stop_playback

Again, a complete implementation of the player is provided. Interesting are the orthogonal regions delimited with "--", comments and the possibility to declare terminate or interrupt state using the standard MSM states.