PUML (C++-20), experimental
Prev | Chapter 3. Tutorial | Next

## 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.

Prev | Up | Next
Functor front-end | Home | eUML