Skip to content

Instantly share code, notes, and snippets.

@martinmoene
Last active November 5, 2022 09:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save martinmoene/797b1923f9c6c1ae355bb2d6870be25e to your computer and use it in GitHub Desktop.
Save martinmoene/797b1923f9c6c1ae355bb2d6870be25e to your computer and use it in GitHub Desktop.
C++17 example of user interface with state machine based on std::variant.
// C++17 example of user interface with state machine based on std::variant.
// The state machine engine is based on code by Mateusz Pusz,
// https://github.com/mpusz/fsm-variant (MIT-license)
// CppCon 2016. Ben Deane: Using Types Effectively,
// https://www.youtube.com/watch?v=ojZbFIQSdl8
//------------------------------------------------------------------------
// State machine engine:
#include <optional>
#include <variant>
template< typename Derived, typename StateVariant >
class fsm
{
public:
const StateVariant & state() const
{
return m_state;
}
StateVariant & state()
{
return m_state;
}
template<typename Event>
void dispatch( Event && event )
{
Derived & child = static_cast<Derived &>( *this );
if ( auto new_state = std::visit(
[&]( auto & s ) -> std::optional<StateVariant> {
return { child.on_event( s, std::forward<Event>( event ) ) };
}, m_state )
)
{
m_state = *std::move( new_state );
}
}
private:
StateVariant m_state;
};
//------------------------------------------------------------------------
// Application
#include <iostream>
#include <string>
class PlayerApp
{
public:
PlayerApp() : player( *this ) {}
using text = std::string;
void play( text title ) { player.dispatch( event_play{ title } ); }
void play() { player.dispatch( event_resume{} ); }
void pause() { player.dispatch( event_pause{} ); }
void stop() { player.dispatch( event_stop{} ); }
private:
struct event_play{ text title; };
struct event_pause{};
struct event_resume{};
struct event_stop{};
struct state_idle{};
struct state_playing{ text title; };
struct state_paused { text title; };
template< typename State, typename Event >
auto on_error( State &, Event const & )
{
std::cout << "[unsupported transition]\n";
return std::nullopt;
}
auto on_play( state_idle &, event_play const & e )
{
std::cout << "idle|play -> playing: '" << e.title << "'\n";
return state_playing{ e.title };
}
auto on_pause( state_playing & s, event_pause const & )
{
std::cout << "playing|pause -> paused: '" << s.title << "'\n";
return state_paused{ s.title };
}
auto on_resume( state_paused & s, event_resume const & )
{
std::cout << "paused|resume -> playing: '" << s.title << "'\n";
return state_playing{ s.title };
}
auto on_stop( state_playing & s, event_stop const & )
{
std::cout << "playing|stop -> idle: '" << s.title << "'\n";
return state_idle{};
}
auto on_stop( state_paused & s, event_stop const & )
{
std::cout << "paused|stop -> idle: '" << s.title << "'\n";
return state_idle{};
}
// State machine:
using player_state = std::variant<state_idle, state_playing, state_paused>;
class Player : public fsm<Player, player_state>
{
public:
Player( PlayerApp & app_ ) : app( app_) {}
template<typename State, typename Event>
auto on_event( State & s, Event const & e )
{
return app.on_error( s, e );
}
auto on_event( state_idle & s, event_play const & e )
{
return app.on_play( s, e );
}
auto on_event( state_playing & s, event_pause const & e )
{
return app.on_pause( s, e );
}
auto on_event( state_paused & s, event_resume const & e )
{
return app.on_resume( s, e );
}
auto on_event( state_playing & s, event_stop const & e )
{
return app.on_stop( s, e );
}
auto on_event( state_paused & s, event_stop const & e )
{
return app.on_stop( s, e );
}
private:
PlayerApp & app;
};
Player player;
};
int main()
{
PlayerApp app;
app.play("any");
app.stop();
app.play("optional");
app.pause();
app.stop();
app.play("variant");
app.pause();
app.play();
app.stop();
// here, unsupported:
app.pause(); // idle|pause
app.play(); // idle|resume playing
app.stop(); // idle|stop
}
// cl -std:c++17 -EHsc app-state-machine.cpp && app-state-machine.exe
// g++ -std=c++17 -o app-state-machine.exe app-state-machine.cpp && app-state-machine.exe
@martinmoene
Copy link
Author

Output:

idle|play -> playing: 'any'
playing|stop -> idle: 'any'
idle|play -> playing: 'optional'
playing|pause -> paused: 'optional'
paused|stop -> idle: 'optional'
idle|play -> playing: 'variant'
playing|pause -> paused: 'variant'
paused|resume -> playing: 'variant'
playing|stop -> idle: 'variant'
[unsupported transition]
[unsupported transition]
[unsupported transition]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment