Last active
November 5, 2022 09:26
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Output: