mirror of
https://github.com/r-lyeh-archived/fsm.git
synced 2025-10-14 02:27:27 +08:00
new fsm code
This commit is contained in:
330
fsm.hpp
330
fsm.hpp
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Simple FSM class
|
||||
* Simple FSM/HFSM class
|
||||
* Copyright (c) 2011, 2012, 2013, 2014 Mario 'rlyeh' Rodriguez
|
||||
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
@@ -53,14 +53,17 @@
|
||||
* - http://en.wikipedia.org/wiki/Finite-state_machine
|
||||
|
||||
* todo:
|
||||
* - HFSM? GOAP? behavior trees?
|
||||
* - stack: HFSM
|
||||
* - substates: state {0,1,2...}
|
||||
* - GOAP? behavior trees?
|
||||
* - trigger args
|
||||
* - counters
|
||||
|
||||
* - rlyeh
|
||||
*/
|
||||
|
||||
// - note on child states (tree of fsm's):
|
||||
// - triggers are handled to the most inner active state in the decision tree
|
||||
// - unhandled triggers are delegated to the parent state handler until handled or discarded by root state
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <deque>
|
||||
@@ -68,176 +71,217 @@
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
namespace fsm
|
||||
{
|
||||
// trigger and state are both child classes, which are named strings
|
||||
template<typename T>
|
||||
inline std::string to_string( const T &t ) {
|
||||
std::stringstream ss;
|
||||
return ss << t ? ss.str() : std::string();
|
||||
}
|
||||
|
||||
template< unsigned id >
|
||||
struct child : public std::string {
|
||||
explicit
|
||||
child( const std::string &name = std::string() ) : std::string(name)
|
||||
template<>
|
||||
inline std::string to_string( const std::string &t ) {
|
||||
return t;
|
||||
}
|
||||
|
||||
struct args : public std::vector< std::string > {
|
||||
args() : std::vector< std::string >()
|
||||
{}
|
||||
|
||||
template<typename T>
|
||||
bool operator()( T &t ) const {
|
||||
return id == 1 ? t( *this ) : false;
|
||||
template<typename T0>
|
||||
args( const T0 &t0 ) : std::vector< std::string >(1) {
|
||||
(*this)[0] = fsm::to_string(t0);
|
||||
}
|
||||
template<typename T0, typename T1>
|
||||
args( const T0 &t0, const T1 &t1 ) : std::vector< std::string >(2) {
|
||||
(*this)[0] = fsm::to_string(t0);
|
||||
(*this)[1] = fsm::to_string(t1);
|
||||
}
|
||||
};
|
||||
|
||||
typedef child<0> state;
|
||||
typedef child<1> trigger;
|
||||
class state;
|
||||
typedef std::function< void( const fsm::args &args ) > code;
|
||||
|
||||
typedef std::deque< state > states_history;
|
||||
typedef std::deque< trigger > triggers_history;
|
||||
class state : public std::string {
|
||||
public:
|
||||
|
||||
class core
|
||||
{
|
||||
public:
|
||||
fsm::args args;
|
||||
|
||||
// methods
|
||||
explicit
|
||||
state( const std::string &name = std::string() ) : std::string(name)
|
||||
{}
|
||||
|
||||
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("");
|
||||
state operator()() const {
|
||||
state self = *this;
|
||||
self.args = fsm::args();
|
||||
return self;
|
||||
}
|
||||
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("");
|
||||
template<typename T0>
|
||||
state operator()( const T0 &t0 ) const {
|
||||
state self = *this;
|
||||
self.args = fsm::args(t0);
|
||||
return self;
|
||||
}
|
||||
template<typename T0, typename T1>
|
||||
state operator()( const T0 &t0, const T1 &t1 ) const {
|
||||
state self = *this;
|
||||
self.args = fsm::args(t0,t1);
|
||||
return self;
|
||||
}
|
||||
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; }
|
||||
protected: std::vector< fsm::state > stack;
|
||||
};
|
||||
|
||||
bool has_triggered() const { return !triggers.back().empty(); }
|
||||
void clear_trigger_flag() { triggers.push_back( fsm::trigger("") ); }
|
||||
class stack {
|
||||
public:
|
||||
|
||||
// transitions to override
|
||||
stack( const fsm::state &start = fsm::state("{undefined}") ) : deque(1) {
|
||||
deque[0] = start;
|
||||
begin( deque.back() );
|
||||
}
|
||||
|
||||
virtual state first() = 0;
|
||||
virtual state next() = 0;
|
||||
void push( const fsm::state &state ) {
|
||||
if( deque.size() && deque.back() == state ) {
|
||||
return;
|
||||
}
|
||||
// queue
|
||||
pause( deque.back() );
|
||||
deque.push_back( state );
|
||||
begin( deque.back() );
|
||||
}
|
||||
void next( const fsm::state &state ) {
|
||||
if( deque.size() ) {
|
||||
follow( deque.back(), state );
|
||||
} else {
|
||||
push(state);
|
||||
}
|
||||
}
|
||||
void pop() {
|
||||
if( deque.size() ) {
|
||||
end( deque.back() );
|
||||
deque.pop_back();
|
||||
resume( deque.back() );
|
||||
}
|
||||
}
|
||||
size_t size() const {
|
||||
return deque.size();
|
||||
}
|
||||
void start(const fsm::state &state) {
|
||||
next( state );
|
||||
}
|
||||
|
||||
// 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() );
|
||||
bool exec( const fsm::state &trigger ) {
|
||||
size_t size = this->size();
|
||||
if( !size ) {
|
||||
return false;
|
||||
}
|
||||
current_trigger = std::string();
|
||||
std::deque< rit > cancelled;
|
||||
for( rit it = deque.rbegin(); it != deque.rend(); ++it ) {
|
||||
fsm::state &self = *it;
|
||||
if( !call(self,trigger) ) {
|
||||
cancelled.push_back(it);
|
||||
continue;
|
||||
}
|
||||
for( std::deque< rit >::iterator it = cancelled.begin(), end = cancelled.end(); it != end; ++it ) {
|
||||
(*it)->end();
|
||||
deque.erase(--(it->base()));
|
||||
}
|
||||
current_trigger = trigger;
|
||||
return true;
|
||||
}
|
||||
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() );
|
||||
// [] classic behaviour: "hello"[5] = undefined, "hello"[-1] = undefined
|
||||
// [] extended behaviour: "hello"[5] = h, "hello"[-1] = o, "hello"[-2] = l
|
||||
fsm::state get_state( signed int pos = -1 ) const {
|
||||
signed size = (signed)(deque.size());
|
||||
if( !size ) {
|
||||
return fsm::state();
|
||||
}
|
||||
return *( deque.begin() + ( pos >= 0 ? pos % size : size - 1 + ((pos+1) % size) ) );
|
||||
}
|
||||
std::string get_trigger() const {
|
||||
return current_trigger;
|
||||
}
|
||||
|
||||
// triggers history generation; push no dupes; max fixed size of 60
|
||||
if( triggers.empty() || triggers.front() != trigger )
|
||||
triggers.push_front( trigger );
|
||||
bool is( const fsm::state &state ) const {
|
||||
return deque.empty() ? false : ( deque.back() == state );
|
||||
}
|
||||
|
||||
if( triggers.size() > 60 )
|
||||
triggers.pop_back();
|
||||
/* (idle)___(trigger)__/''(hold)''''(release)''\__
|
||||
bool is_idle() const { return transition.previous == transition.current; }
|
||||
bool is_triggered() const { return transition.previous == transition.current; }
|
||||
bool is_hold() const { return transition.previous == transition.current; }
|
||||
bool is_released() const { return transition.previous == transition.current; } */
|
||||
|
||||
// states history generation; push no dupes; max fixed size of 60
|
||||
if( states.empty() || states.front() != current )
|
||||
states.push_front( current );
|
||||
std::string debug() const {
|
||||
std::string out;
|
||||
for( cit it = deque.begin(), end = deque.end(); it != end; ++it ) {
|
||||
out += (*it) + ',';
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
if( states.size() > 60 )
|
||||
states.pop_back();
|
||||
bool call( const fsm::state &from, const fsm::state &to ) const {
|
||||
std::map< bistate, fsm::code >::const_iterator code = callbacks.find(bistate(from,to));
|
||||
if( code == callbacks.end() ) {
|
||||
return false;
|
||||
} else {
|
||||
code->second( to.args );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
fsm::code &on( const fsm::state &from, const fsm::state &to ) {
|
||||
return callbacks[ bistate(from,to) ] = callbacks[ bistate(from,to) ];
|
||||
}
|
||||
|
||||
// debug
|
||||
|
||||
std::deque< std::string > get_log() const {
|
||||
return log;
|
||||
}
|
||||
|
||||
// fsm details
|
||||
bool operator()( const fsm::state &trigger ) {
|
||||
return exec( trigger );
|
||||
}
|
||||
|
||||
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() });
|
||||
void begin( const fsm::state &state ) {
|
||||
call( state, fsm::state("begin") );
|
||||
}
|
||||
}; // fsm::core
|
||||
} // ::fsm
|
||||
|
||||
void end( const fsm::state &state ) {
|
||||
call( state, fsm::state("end") );
|
||||
}
|
||||
|
||||
void pause( const fsm::state &state ) {
|
||||
call( state, fsm::state("pause") );
|
||||
}
|
||||
|
||||
void resume( const fsm::state &state ) {
|
||||
call( state, fsm::state("resume") );
|
||||
}
|
||||
|
||||
void follow( fsm::state ¤t, const fsm::state &next ) {
|
||||
end( current );
|
||||
current = next;
|
||||
begin( current );
|
||||
}
|
||||
|
||||
typedef std::pair<std::string, std::string> bistate;
|
||||
std::map< bistate, fsm::code > callbacks;
|
||||
|
||||
std::deque< fsm::state > log;
|
||||
std::deque< fsm::state > deque;
|
||||
std::string current_trigger;
|
||||
|
||||
struct status {
|
||||
fsm::state previous, trigger, current;
|
||||
} transition;
|
||||
|
||||
typedef std::deque< fsm::state >::const_iterator cit;
|
||||
typedef std::deque< fsm::state >::reverse_iterator rit;
|
||||
};
|
||||
}
|
||||
|
180
sample.cc
180
sample.cc
@@ -1,18 +1,22 @@
|
||||
#include <iostream>
|
||||
#include "fsm.hpp"
|
||||
|
||||
// available states and triggers
|
||||
using namespace fsm;
|
||||
|
||||
const fsm::state
|
||||
opened("opened"), closed("closed"),
|
||||
waiting("waiting"), playing("playing");
|
||||
//
|
||||
fsm::state begin("begin"), end("end"), pause("pause"), resume("resume");
|
||||
|
||||
const fsm::trigger
|
||||
open("open"), close("close"),
|
||||
play("play"), stop("stop"),
|
||||
insert("insert"), extract("extract");
|
||||
// available states and triggers
|
||||
fsm::state
|
||||
opening("opening"), closing("closing"),
|
||||
waiting("waiting"), playing("playing");
|
||||
|
||||
class cd_player : public fsm::core
|
||||
fsm::state
|
||||
open("open"), close("close"),
|
||||
play("play"), stop("stop"),
|
||||
insert("insert"), eject("eject");
|
||||
|
||||
class cd_player
|
||||
{
|
||||
// implementation conditions / guards
|
||||
bool good_disk_format() { return true; }
|
||||
@@ -21,79 +25,141 @@ class cd_player : public fsm::core
|
||||
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; }
|
||||
void start_playback( const std::string &track ) { std::cout << "playing track #" << track << std::endl; }
|
||||
|
||||
// implementation variables
|
||||
bool got_cd;
|
||||
bool has_cd;
|
||||
|
||||
public:
|
||||
|
||||
// setup
|
||||
fsm::stack fsm;
|
||||
|
||||
cd_player() : got_cd(false)
|
||||
{}
|
||||
cd_player() : has_cd(false)
|
||||
{
|
||||
// set initial fsm state
|
||||
fsm = opening;
|
||||
|
||||
// transitions (logic)
|
||||
// define states
|
||||
fsm.on(opening,close) = [&]( const fsm::args &args ) {
|
||||
close_tray();
|
||||
if( !has_cd ) {
|
||||
fsm.start( closing );
|
||||
} else {
|
||||
get_cd_info();
|
||||
fsm.start( waiting );
|
||||
}
|
||||
};
|
||||
fsm.on(opening,insert) = [&]( const fsm::args &args ) {
|
||||
has_cd = true;
|
||||
fsm.start( opening );
|
||||
};
|
||||
fsm.on(opening,eject) = [&]( const fsm::args &args ) {
|
||||
has_cd = false;
|
||||
fsm.start( opening );
|
||||
};
|
||||
|
||||
virtual fsm::state first() {
|
||||
return closed;
|
||||
}
|
||||
fsm.on(closing,open) = [&]( const fsm::args &args ) {
|
||||
open_tray();
|
||||
fsm.start( opening );
|
||||
};
|
||||
|
||||
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) :
|
||||
fsm.on(waiting,play) = [&]( const fsm::args &args ) {
|
||||
if( !good_disk_format() ) {
|
||||
fsm.start( waiting );
|
||||
} else {
|
||||
start_playback( args[0] );
|
||||
fsm.start( playing );
|
||||
}
|
||||
};
|
||||
fsm.on(waiting,open) = [&]( const fsm::args &args ) {
|
||||
open_tray();
|
||||
fsm.start( opening );
|
||||
};
|
||||
|
||||
is(closed) && did(open) ? (open_tray(), opened) :
|
||||
fsm.on(playing,open) = [&]( const fsm::args &args ) {
|
||||
open_tray();
|
||||
fsm.start( opening );
|
||||
};
|
||||
fsm.on(playing,stop) = [&]( const fsm::args &args ) {
|
||||
fsm.start( waiting );
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
fsm::state update("udpate");
|
||||
fsm::state walking("walking"), attacking("attacking");
|
||||
|
||||
class ant {
|
||||
public:
|
||||
fsm::stack fsm;
|
||||
int health, distance, flow;
|
||||
|
||||
ant() : health(0), distance(0), flow(1) {
|
||||
// initial state
|
||||
fsm = walking;
|
||||
|
||||
// setup
|
||||
fsm.on(walking,resume) = [&]( const fsm::args &args ) {
|
||||
std::cout << "pre-walk! distance:" << distance << std::endl;
|
||||
};
|
||||
fsm.on(walking,update) = [&]( const fsm::args &args ) {
|
||||
std::cout << "\r" << "\\|/-"[ distance % 4 ] << " walking " << (flow > 0 ? "-->" : "<--") << " ";
|
||||
distance += flow;
|
||||
if( 1000 == distance ) {
|
||||
std::cout << "at food!" << std::endl;
|
||||
flow = -flow;
|
||||
}
|
||||
if( -1000 == distance ) {
|
||||
std::cout << "at home!" << std::endl;
|
||||
flow = -flow;
|
||||
}
|
||||
};
|
||||
fsm.on(attacking,begin) = [&]( const fsm::args &args ) {
|
||||
std::cout << "pre-attack!" << std::endl;
|
||||
health = 1000;
|
||||
};
|
||||
fsm.on(attacking,update) = [&]( const fsm::args &args ) {
|
||||
std::cout << "\r" << "\\|/-$"[ distance % 4 ] << " fighting !! hp:(" << health << ") ";
|
||||
--health;
|
||||
if( health < 0 ) {
|
||||
std::cout << std::endl;
|
||||
fsm.pop();
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
int main( int argc, const char **argv ) {
|
||||
// create fsm
|
||||
//fsm cd;
|
||||
|
||||
// basic fsm sample
|
||||
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::cout << "[" << cd.fsm.get_state() << "] ";
|
||||
std::cout << "(o)pen lid/(c)lose lid, (i)nsert cd/(e)ject 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 'p': cd.fsm(play(1+rand()%10)); break;
|
||||
case 'o': cd.fsm(open()); break;
|
||||
case 'c': cd.fsm(close()); break;
|
||||
case 's': cd.fsm(stop()); break;
|
||||
case 'i': cd.fsm(insert()); break;
|
||||
case 'e': cd.fsm(eject()); break;
|
||||
|
||||
case 'q': return 0;
|
||||
case 'q': break;
|
||||
default : std::cout << "what?" << std::endl;
|
||||
}
|
||||
if( cmd == 'q' ) break;
|
||||
}
|
||||
|
||||
// hfsm ant sample
|
||||
ant mini;
|
||||
for(;;) {
|
||||
if( 0 == rand() % 10000 ) mini.fsm.push(attacking);
|
||||
mini.fsm(update());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user