new fsm code

This commit is contained in:
r-lyeh
2014-03-25 08:41:48 +01:00
parent ea8f4819a4
commit cef0e945a4
2 changed files with 310 additions and 200 deletions

330
fsm.hpp
View File

@@ -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 &current, 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
View File

@@ -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());
}
}