mirror of
https://github.com/tkem/fsmlite.git
synced 2025-05-09 08:14:32 +08:00
316 lines
9.4 KiB
C++
316 lines
9.4 KiB
C++
#include "fsm.h"
|
|
|
|
#include <cassert>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#if __cplusplus >= 201703L || _MSVC_LANG >= 201703L
|
|
|
|
class player: public fsmlite::fsm<player> {
|
|
friend class fsmlite::fsm<player>; // base class needs access to transition_table
|
|
|
|
std::string cd_title;
|
|
bool autoplay = false;
|
|
|
|
public:
|
|
enum states { Stopped, Open, Empty, Playing, Paused };
|
|
|
|
player(state_type init_state = Empty) : fsm(init_state) { }
|
|
|
|
void set_autoplay(bool f) { autoplay = f; }
|
|
|
|
bool is_autoplay() const { return autoplay; }
|
|
|
|
const std::string& get_cd_title() const { return cd_title; }
|
|
|
|
struct play {};
|
|
struct open_close {};
|
|
struct cd_detected {
|
|
std::string title;
|
|
bool bad() const { return title.empty(); }
|
|
};
|
|
struct stop {};
|
|
struct pause {};
|
|
|
|
private:
|
|
void start_playback();
|
|
void start_autoplay(const cd_detected& cd);
|
|
void open_drawer();
|
|
void close_drawer();
|
|
void store_cd_info(const cd_detected& cd);
|
|
void stop_playback();
|
|
void pause_playback();
|
|
void resume_playback();
|
|
void stop_and_open();
|
|
|
|
private:
|
|
using m = player; // for brevity
|
|
|
|
using transition_table = table<
|
|
// Start Event Target Action Guard (optional)
|
|
// ----+--------+------------+--------+-------------------+-----------------+-
|
|
row< Stopped, play, Playing, &m::start_playback >,
|
|
row< Stopped, open_close, Open, &m::open_drawer >,
|
|
row< Open, open_close, Empty, &m::close_drawer >,
|
|
row< Empty, open_close, Open, &m::open_drawer >,
|
|
row< Empty, cd_detected, Open, &m::open_drawer, &cd_detected::bad >,
|
|
row< Empty, cd_detected, Playing, &m::start_autoplay, &m::is_autoplay >,
|
|
row< Empty, cd_detected, Stopped, &m::store_cd_info /* fallback */ >,
|
|
row< Playing, stop, Stopped, &m::stop_playback >,
|
|
row< Playing, pause, Paused, &m::pause_playback >,
|
|
row< Playing, open_close, Open, &m::stop_and_open >,
|
|
row< Paused, play, Playing, &m::resume_playback >,
|
|
row< Paused, stop, Stopped, &m::stop_playback >,
|
|
row< Paused, open_close, Open, &m::stop_and_open >
|
|
// ----+--------+------------+--------+-------------------+-----------------+-
|
|
>;
|
|
};
|
|
|
|
void player::start_playback()
|
|
{
|
|
std::cout << "Starting playback\n";
|
|
}
|
|
|
|
void player::start_autoplay(const cd_detected& cd)
|
|
{
|
|
std::cout << "Starting playback of '" << cd.title << "'\n";
|
|
cd_title = cd.title;
|
|
}
|
|
|
|
void player::open_drawer()
|
|
{
|
|
std::cout << "Opening drawer\n";
|
|
cd_title.clear();
|
|
}
|
|
|
|
void player::close_drawer()
|
|
{
|
|
std::cout << "Closing drawer\n";
|
|
}
|
|
|
|
void player::store_cd_info(const cd_detected& cd)
|
|
{
|
|
std::cout << "Detected CD '" << cd.title << "'\n";
|
|
cd_title = cd.title;
|
|
}
|
|
|
|
void player::stop_playback()
|
|
{
|
|
std::cout << "Stopping playback\n";
|
|
}
|
|
|
|
void player::pause_playback()
|
|
{
|
|
std::cout << "Pausing playback\n";
|
|
}
|
|
|
|
void player::stop_and_open()
|
|
{
|
|
std::cout << "Stopping and opening drawer\n";
|
|
cd_title.clear();
|
|
}
|
|
|
|
void player::resume_playback()
|
|
{
|
|
std::cout << "Resuming playback\n";
|
|
}
|
|
|
|
#else
|
|
|
|
class player: public fsmlite::fsm<player> {
|
|
friend class fsmlite::fsm<player>; // base class needs access to transition_table
|
|
|
|
std::string cd_title;
|
|
bool autoplay = false;
|
|
|
|
public:
|
|
enum states { Stopped, Open, Empty, Playing, Paused };
|
|
|
|
player(state_type init_state = Empty) : fsm(init_state) { }
|
|
|
|
void set_autoplay(bool f) { autoplay = f; }
|
|
|
|
bool is_autoplay() const { return autoplay; }
|
|
|
|
const std::string& get_cd_title() const { return cd_title; }
|
|
|
|
struct play {};
|
|
struct open_close {};
|
|
struct cd_detected { std::string title; };
|
|
struct stop {};
|
|
struct pause {};
|
|
|
|
private:
|
|
// guards
|
|
bool is_autoplay(const cd_detected&) const { return autoplay; }
|
|
bool is_bad_cd(const cd_detected& cd) const { return cd.title.empty(); }
|
|
|
|
// actions
|
|
void start_playback(const play&);
|
|
void start_autoplay(const cd_detected& cd);
|
|
void open_drawer(const open_close&);
|
|
void open_drawer(const cd_detected& cd);
|
|
void close_drawer(const open_close&);
|
|
void store_cd_info(const cd_detected& cd);
|
|
void stop_playback(const stop&);
|
|
void pause_playback(const pause&);
|
|
void stop_and_open(const open_close&);
|
|
void resume_playback(const play&);
|
|
|
|
private:
|
|
using m = player; // for brevity
|
|
|
|
using transition_table = table<
|
|
// Start Event Target Action Guard (optional)
|
|
// -----------+--------+------------+--------+-------------------+---------------+-
|
|
mem_fn_row< Stopped, play, Playing, &m::start_playback >,
|
|
mem_fn_row< Stopped, open_close, Open, &m::open_drawer >,
|
|
mem_fn_row< Open, open_close, Empty, &m::close_drawer >,
|
|
mem_fn_row< Empty, open_close, Open, &m::open_drawer >,
|
|
mem_fn_row< Empty, cd_detected, Open, &m::open_drawer, &m::is_bad_cd >,
|
|
mem_fn_row< Empty, cd_detected, Playing, &m::start_autoplay, &m::is_autoplay >,
|
|
mem_fn_row< Empty, cd_detected, Stopped, &m::store_cd_info /* fallback */ >,
|
|
mem_fn_row< Playing, stop, Stopped, &m::stop_playback >,
|
|
mem_fn_row< Playing, pause, Paused, &m::pause_playback >,
|
|
mem_fn_row< Playing, open_close, Open, &m::stop_and_open >,
|
|
mem_fn_row< Paused, play, Playing, &m::resume_playback >,
|
|
mem_fn_row< Paused, stop, Stopped, &m::stop_playback >,
|
|
mem_fn_row< Paused, open_close, Open, &m::stop_and_open >
|
|
// -----------+--------+------------+--------+-------------------+---------------+-
|
|
>;
|
|
};
|
|
|
|
void player::start_playback(const play&)
|
|
{
|
|
std::cout << "Starting playback\n";
|
|
}
|
|
|
|
void player::start_autoplay(const cd_detected& cd)
|
|
{
|
|
std::cout << "Starting playback of '" << cd.title << "'\n";
|
|
cd_title = cd.title;
|
|
}
|
|
|
|
void player::open_drawer(const open_close&)
|
|
{
|
|
std::cout << "Opening drawer\n";
|
|
cd_title.clear();
|
|
}
|
|
|
|
void player::open_drawer(const cd_detected&)
|
|
{
|
|
std::cout << "Ejecting bad CD\n";
|
|
cd_title.clear();
|
|
}
|
|
|
|
void player::close_drawer(const open_close&)
|
|
{
|
|
std::cout << "Closing drawer\n";
|
|
}
|
|
|
|
void player::store_cd_info(const cd_detected& cd)
|
|
{
|
|
std::cout << "Detected CD '" << cd.title << "'\n";
|
|
cd_title = cd.title;
|
|
}
|
|
|
|
void player::stop_playback(const stop&)
|
|
{
|
|
std::cout << "Stopping playback\n";
|
|
}
|
|
|
|
void player::pause_playback(const pause&)
|
|
{
|
|
std::cout << "Pausing playback\n";
|
|
}
|
|
|
|
void player::stop_and_open(const open_close&)
|
|
{
|
|
std::cout << "Stopping and opening drawer\n";
|
|
cd_title.clear();
|
|
}
|
|
|
|
void player::resume_playback(const play&)
|
|
{
|
|
std::cout << "Resuming playback\n";
|
|
}
|
|
|
|
#endif
|
|
|
|
void test_player()
|
|
{
|
|
player p;
|
|
assert(p.current_state() == player::Empty);
|
|
assert(!p.is_autoplay());
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Open);
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Empty);
|
|
p.process_event(player::cd_detected{"louie, louie"});
|
|
assert(p.current_state() == player::Stopped);
|
|
assert(p.get_cd_title() == "louie, louie");
|
|
p.process_event(player::play());
|
|
assert(p.current_state() == player::Playing);
|
|
p.process_event(player::pause());
|
|
assert(p.current_state() == player::Paused);
|
|
p.process_event(player::play());
|
|
assert(p.current_state() == player::Playing);
|
|
p.process_event(player::stop());
|
|
assert(p.current_state() == player::Stopped);
|
|
p.process_event(player::play());
|
|
assert(p.current_state() == player::Playing);
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Open);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Empty);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::play());
|
|
assert(p.current_state() == player::Empty);
|
|
assert(p.get_cd_title().empty());
|
|
}
|
|
|
|
void test_bad_cd()
|
|
{
|
|
player p;
|
|
assert(p.current_state() == player::Empty);
|
|
assert(!p.is_autoplay());
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Open);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Empty);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::cd_detected{});
|
|
assert(p.current_state() == player::Open);
|
|
assert(p.get_cd_title().empty());
|
|
}
|
|
|
|
void test_autoplay()
|
|
{
|
|
player p;
|
|
p.set_autoplay(true);
|
|
assert(p.current_state() == player::Empty);
|
|
assert(p.is_autoplay());
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Open);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::open_close());
|
|
assert(p.current_state() == player::Empty);
|
|
assert(p.get_cd_title().empty());
|
|
p.process_event(player::cd_detected{"louie, louie"});
|
|
assert(p.current_state() == player::Playing);
|
|
assert(p.get_cd_title() == "louie, louie");
|
|
}
|
|
|
|
int main()
|
|
{
|
|
test_player();
|
|
test_bad_cd();
|
|
test_autoplay();
|
|
return 0;
|
|
}
|