mirror of
https://github.com/r-lyeh-archived/fsm.git
synced 2025-10-14 02:27:27 +08:00
Initial commit
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
4
README.md
Normal file
4
README.md
Normal file
@@ -0,0 +1,4 @@
|
||||
fsm
|
||||
===
|
||||
|
||||
Lightweight Finite-state Machine (FSM) class
|
243
fsm.hpp
Normal file
243
fsm.hpp
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Simple FSM class
|
||||
* Copyright (c) 2011, 2012, 2013, 2014 Mario 'rlyeh' Rodriguez
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
|
||||
* - inspiration: BOOST MSM (eUML FSM)
|
||||
* format: source + event [guard(s)] [/ action(s)] == target
|
||||
* original sample:
|
||||
*
|
||||
* BOOST_MSM_EUML_TRANSITION_TABLE((
|
||||
* Stopped + play [some_guard] / (some_action , start_playback) == Playing ,
|
||||
* Stopped + open_close/ open_drawer == Open ,
|
||||
* Stopped + stop == Stopped ,
|
||||
* Open + open_close / close_drawer == Empty ,
|
||||
* Empty + open_close / open_drawer == Open ,
|
||||
* Empty + cd_detected [good_disk_format] / store_cd_info == Stopped
|
||||
* ),transition_table)
|
||||
*
|
||||
* - proposal:
|
||||
* format: return
|
||||
* current_state && trigger [&& guard(s)] ? ( [action(s),] new_state) :
|
||||
* [...]
|
||||
* 0
|
||||
* sample becomes:
|
||||
*
|
||||
* return
|
||||
* Stopped && play && some_guard() ? (some_action(), start_playback(), Playing) :
|
||||
* Stopped && open_close ? (open_drawer(), Open) :
|
||||
* Stopped && stop ? (Stopped) :
|
||||
* Open && open_close ? (close_drawer(), Empty) :
|
||||
* Empty && open_close ? (open_drawer(), Open) :
|
||||
* Empty && cd_detected && good_disk_format() ? (store_cd_info(), Stopped) :
|
||||
* 0
|
||||
|
||||
* refs:
|
||||
* - http://en.wikipedia.org/wiki/Finite-state_machine
|
||||
|
||||
* todo:
|
||||
* - HFSM? GOAP? behavior trees?
|
||||
* - stack: HFSM
|
||||
* - substates: state {0,1,2...}
|
||||
* - counters
|
||||
|
||||
* - rlyeh
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace fsm
|
||||
{
|
||||
// trigger and state are both child classes, which are named strings
|
||||
|
||||
template< unsigned id >
|
||||
struct child : public std::string {
|
||||
explicit
|
||||
child( const std::string &name = std::string() ) : std::string(name)
|
||||
{}
|
||||
|
||||
template<typename T>
|
||||
bool operator()( T &t ) const {
|
||||
return id == 1 ? t( *this ) : false;
|
||||
}
|
||||
};
|
||||
|
||||
typedef child<0> state;
|
||||
typedef child<1> trigger;
|
||||
|
||||
typedef std::deque< state > states_history;
|
||||
typedef std::deque< trigger > triggers_history;
|
||||
|
||||
class core
|
||||
{
|
||||
public:
|
||||
|
||||
// methods
|
||||
|
||||
core()
|
||||
{}
|
||||
|
||||
virtual ~core()
|
||||
{}
|
||||
|
||||
core &add_trigger( const fsm::trigger &name ) {
|
||||
return preconfig(), *this;
|
||||
}
|
||||
|
||||
core &add_state( const fsm::state &name ) {
|
||||
return preconfig(), *this;
|
||||
}
|
||||
|
||||
state get_state( int pos = 0 ) const {
|
||||
signed size = signed(states.size());
|
||||
if( !size ) {
|
||||
static fsm::state invalid;
|
||||
return invalid = fsm::state("");
|
||||
}
|
||||
return states[ pos >= 0 ? pos % size : size - 1 + ((pos+1) % size) ];
|
||||
}
|
||||
|
||||
trigger get_trigger( int pos = 0 ) const {
|
||||
signed size = signed(triggers.size());
|
||||
if( !size ) {
|
||||
static fsm::trigger invalid;
|
||||
return invalid = fsm::trigger("");
|
||||
}
|
||||
return triggers[ pos >= 0 ? pos % size : size - 1 + ((pos+1) % size) ];
|
||||
}
|
||||
|
||||
const states_history &get_states_history() const { return states; }
|
||||
const triggers_history &get_triggers_history() const { return triggers; }
|
||||
|
||||
bool has_triggered() const { return !triggers.back().empty(); }
|
||||
void clear_trigger_flag() { triggers.push_back( fsm::trigger("") ); }
|
||||
|
||||
// transitions to override
|
||||
|
||||
virtual state first() = 0;
|
||||
virtual state next() = 0;
|
||||
|
||||
// optional callbacks to override
|
||||
|
||||
std::function< fsm::state( void ) > on_first;
|
||||
std::function< fsm::state( void ) > on_next;
|
||||
std::function< void( std::string ) > on_warning;
|
||||
std::function< void( std::string ) > on_verbose;
|
||||
|
||||
// sugars:
|
||||
|
||||
core &operator<<( const state &state ) { return add_state( state ); }
|
||||
core &operator<<( const trigger &trigger ) { return add_trigger( trigger ); }
|
||||
|
||||
state get_current_state() const { return states.front(); }
|
||||
state get_previous_state() const { return *++states.begin(); }
|
||||
trigger get_current_trigger() const { return triggers.front(); }
|
||||
trigger get_previous_trigger() const { return *++triggers.begin(); }
|
||||
|
||||
// fsm engine core
|
||||
|
||||
bool did( const fsm::trigger &trigger ) const {
|
||||
return trigger == triggers.front();
|
||||
}
|
||||
|
||||
bool is( const fsm::state &state ) const {
|
||||
return state == states.front();
|
||||
}
|
||||
|
||||
bool operator[]( const fsm::state &state ) const {
|
||||
return is( state );
|
||||
}
|
||||
|
||||
bool exec( const fsm::trigger &trigger ) {
|
||||
return operator()( trigger );
|
||||
}
|
||||
|
||||
bool operator()( const fsm::trigger &trigger ) {
|
||||
|
||||
triggers.push_front( trigger );
|
||||
state current = next();
|
||||
triggers.pop_front();
|
||||
|
||||
if( current.empty() ) {
|
||||
if( on_warning ) {
|
||||
std::stringstream line;
|
||||
line << "<fsm/fsm.hpp> says: [FAIL] invalid fsm::trigger " << trigger << "() raised on state [" << get_current_state() << "]";
|
||||
on_warning( line.str() );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// info
|
||||
std::stringstream line;
|
||||
line << "[" << get_current_state() << "]->" << trigger << "->[" << current << "]";
|
||||
|
||||
log.push_back( line.str() );
|
||||
|
||||
if( log.size() > 60 )
|
||||
log.pop_front();
|
||||
|
||||
if( on_verbose ) {
|
||||
on_verbose( std::string() + "<fsm/fsm.hpp> says: " + log.back() );
|
||||
}
|
||||
|
||||
// triggers history generation; push no dupes; max fixed size of 60
|
||||
if( triggers.empty() || triggers.front() != trigger )
|
||||
triggers.push_front( trigger );
|
||||
|
||||
if( triggers.size() > 60 )
|
||||
triggers.pop_back();
|
||||
|
||||
// states history generation; push no dupes; max fixed size of 60
|
||||
if( states.empty() || states.front() != current )
|
||||
states.push_front( current );
|
||||
|
||||
if( states.size() > 60 )
|
||||
states.pop_back();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// debug
|
||||
|
||||
std::deque< std::string > get_log() const {
|
||||
return log;
|
||||
}
|
||||
|
||||
// fsm details
|
||||
|
||||
protected:
|
||||
|
||||
states_history states;
|
||||
triggers_history triggers;
|
||||
std::deque< std::string > log;
|
||||
|
||||
void preconfig() {
|
||||
// init history
|
||||
states = states_history({ first(), fsm::state() });
|
||||
triggers = triggers_history({ fsm::trigger() });
|
||||
}
|
||||
}; // fsm::core
|
||||
} // ::fsm
|
99
sample.cc
Normal file
99
sample.cc
Normal file
@@ -0,0 +1,99 @@
|
||||
#include <iostream>
|
||||
#include "fsm.hpp"
|
||||
|
||||
// available states and triggers
|
||||
|
||||
const fsm::state
|
||||
opened("opened"), closed("closed"),
|
||||
waiting("waiting"), playing("playing");
|
||||
|
||||
const fsm::trigger
|
||||
open("open"), close("close"),
|
||||
play("play"), stop("stop"),
|
||||
insert("insert"), extract("extract");
|
||||
|
||||
class cd_player : public fsm::core
|
||||
{
|
||||
// implementation conditions / guards
|
||||
bool good_disk_format() { return true; }
|
||||
|
||||
// implementation actions
|
||||
void open_tray() { std::cout << "opening tray" << std::endl; }
|
||||
void close_tray() { std::cout << "closing tray" << std::endl; }
|
||||
void get_cd_info() { std::cout << "retrieving CD info" << std::endl; }
|
||||
void start_playback() { std::cout << "playing track" << std::endl; }
|
||||
|
||||
// implementation variables
|
||||
bool got_cd;
|
||||
|
||||
public:
|
||||
|
||||
// setup
|
||||
|
||||
cd_player() : got_cd(false)
|
||||
{}
|
||||
|
||||
// transitions (logic)
|
||||
|
||||
virtual fsm::state first() {
|
||||
return closed;
|
||||
}
|
||||
|
||||
virtual fsm::state next() {
|
||||
return
|
||||
is(opened) && did(close) ? (close_tray(), got_cd ? get_cd_info(), waiting : closed) :
|
||||
is(opened) && did(insert) ? (got_cd = true, opened) :
|
||||
is(opened) && did(extract) ? (got_cd = false, opened) :
|
||||
|
||||
is(closed) && did(open) ? (open_tray(), opened) :
|
||||
|
||||
is(waiting) && did(play) && good_disk_format() ? (start_playback(), playing) :
|
||||
is(waiting) && did(open) ? (open_tray(), opened) :
|
||||
|
||||
is(playing) && did(open) ? (open_tray(), opened) :
|
||||
is(playing) && did(stop) ? (closed) :
|
||||
fsm::state();
|
||||
}
|
||||
};
|
||||
|
||||
int main( int argc, const char **argv ) {
|
||||
// create fsm
|
||||
//fsm cd;
|
||||
cd_player cd;
|
||||
|
||||
// register states and triggers
|
||||
cd << opened << closed << waiting << playing;
|
||||
cd << play << open << close << stop << insert << extract;
|
||||
|
||||
// create logic (@todo)
|
||||
// cd.first = []() {};
|
||||
// cd.next = []() {};
|
||||
|
||||
// streaming configuration (optional)
|
||||
cd.on_warning = []( const std::string &line ) {
|
||||
std::cout << line << std::endl;
|
||||
};
|
||||
cd.on_verbose = []( const std::string &line ) {
|
||||
std::cout << line << std::endl;
|
||||
};
|
||||
|
||||
for(;;) {
|
||||
char cmd;
|
||||
std::cout << "[" << cd.get_current_state() << "] ";
|
||||
std::cout << "(o)pen lid/(c)lose lid, (i)nsert cd/(e)xtract cd, (p)lay/(s)top cd, (q)uit? ";
|
||||
std::cin >> cmd;
|
||||
|
||||
switch( cmd )
|
||||
{
|
||||
case 'p': play(cd); break;
|
||||
case 'o': open(cd); break;
|
||||
case 'c': close(cd); break;
|
||||
case 's': stop(cd); break;
|
||||
case 'i': insert(cd); break;
|
||||
case 'e': extract(cd); break;
|
||||
|
||||
case 'q': return 0;
|
||||
default : std::cout << "what?" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user