334 lines
11 KiB
C++
334 lines
11 KiB
C++
/*! \file pistatemachine.h
|
|
* \brief Base class for custom state machine
|
|
*/
|
|
/*
|
|
PIP - Platform Independent Primitives
|
|
State machine
|
|
Copyright (C) 2013 Ivan Pelipenko peri4ko@gmail.com
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#ifndef PISTATEMACHINE_H
|
|
#define PISTATEMACHINE_H
|
|
|
|
#include "piobject.h"
|
|
|
|
/*! \brief Base class for custom state machine
|
|
*
|
|
* \section PIStateMachine_synopsis Synopsis
|
|
* This class provide functionality of state machine.
|
|
* You should inherit from this class, implement \a execution()
|
|
* and \a transition() functions, set rules and periodically
|
|
* call \a tick() function to proper work of machine.
|
|
*
|
|
* \section PIStateMachine_prepare Prepare for work
|
|
* %State machine operates with "state", "rule" and "condition".
|
|
* * "State" is some class (by default \c int), associated name and
|
|
* optional "handler" - pointer to function executed on every \a tick();
|
|
* * "Rule" define rule of transition from one machine state to other.
|
|
* It is also has optional "handler";
|
|
* * "Condition" is a part of rule and define possibility of transition.
|
|
*
|
|
* First of all you should define states of your machine by function
|
|
* \a addState(). Then you should define transition rules for machine
|
|
* by function \a addRule(). Finally you can set initial state by function
|
|
* \a setInitialState() and provide periodically execution of function
|
|
* \a tick().
|
|
*
|
|
* \section PIStateMachine_principle Principle of work
|
|
* At any time the state machine is in some state. You can ask machine
|
|
* to enter in new state by function \a switchToState(). If all conditions
|
|
* done machine switch it state immediately, else machine remember request
|
|
* and will be try switch to the new state every tick. Successfull state
|
|
* switching execute function \a transition(), every tick execute
|
|
* function \a execution() with current state. On successfull transition
|
|
* if rule "handler" is not null it execute. Every \a tick() if current
|
|
* state "handler" is not null it execute.
|
|
*
|
|
* \section PIStateMachine_conditions Conditions
|
|
* Each rule has transition condition. Condition is array of pairs
|
|
* (string, number). It means that every condition by name "string"
|
|
* should be performed as least "number" times. Empty condition always
|
|
* permits transition.
|
|
*
|
|
* %State machine have current performed conditions. You can read this
|
|
* conditions by function \a currentConditions() and perform new
|
|
* conditions by functions \a performCondition() and \a performConditions().
|
|
* Currend conditions can de erased by function \a resetConditions().
|
|
*
|
|
* \section PIStateMachine_example Example
|
|
* This is simple example demonstrates all features:
|
|
* \snippet pistatemachine.cpp main
|
|
*/
|
|
template <typename Type = int>
|
|
class PIP_EXPORT PIStateMachine: public PIObject
|
|
{
|
|
PIOBJECT(PIStateMachine)
|
|
public:
|
|
//! Constructs an empty state machine
|
|
PIStateMachine(void * _parent = 0) {if (_parent == 0) parent_ = this; else parent_ = _parent; resetConditions();}
|
|
~PIStateMachine() {;}
|
|
|
|
//! %Condition is a pair (string, number)
|
|
typedef PIPair<PIString, int> Condition;
|
|
|
|
//! %Rule of transition between states of machine
|
|
struct Rule {
|
|
//! Constuctor
|
|
Rule() {handler = 0;}
|
|
//! Constuctor
|
|
Rule(Type f, Type t, const PIStringList & c = PIStringList(), Handler h = 0, bool at = false, bool rac = false) {
|
|
from = f;
|
|
to = t;
|
|
for (int i = 0; i < c.size_s(); ++i)
|
|
conditions << Condition(c[i], 1);
|
|
autoTransition = at;
|
|
resetAllConditions = rac;
|
|
handler = h;
|
|
}
|
|
//! Source state
|
|
Type from;
|
|
//! Destination state
|
|
Type to;
|
|
//! %Conditions of transition
|
|
PIVector<Condition> conditions;
|
|
//! Automatic transition
|
|
bool autoTransition;
|
|
//! Reset or not all performed conditions of machine on transition
|
|
bool resetAllConditions;
|
|
//! Pointer to function executed on transition
|
|
Handler handler;
|
|
//! Add condition of transition
|
|
void addCondition(const PIString & name, int times = 1) {if (times > 0) conditions << Condition(name, times);}
|
|
bool operator ==(const Rule & other) const {return (from == other.from) && (to == other.to);}
|
|
bool operator !=(const Rule & other) const {return (from != other.from) || (to != other.to);}
|
|
};
|
|
|
|
//! %State of machine
|
|
struct State {
|
|
//! Constuctor
|
|
State() {handler = 0;}
|
|
//! Constuctor
|
|
State(Type v, const PIString & n = "", Handler h = 0) {value = v; name = n; handler = h;}
|
|
//! %State value
|
|
Type value;
|
|
//! %State name
|
|
PIString name;
|
|
//! Pointer to function executed on tick
|
|
Handler handler;
|
|
bool operator ==(const State & other) const {return value == other.value;}
|
|
bool operator !=(const State & other) const {return value != other.value;}
|
|
};
|
|
|
|
void * parent() const {return parent_;}
|
|
void setParent(void * parent) {parent_ = parent;}
|
|
|
|
//! Add state of machine
|
|
void addState(Type value, const PIString & name = "", Handler handler = 0) {if (states_.contains(State(value, name))) return; states_ << State(value, name, handler);}
|
|
|
|
//! States count
|
|
int statesCount() const {return states_.size_s();}
|
|
|
|
//! Remove all states
|
|
void clearStates() {states_.clear();}
|
|
|
|
|
|
//! Add rule of transition
|
|
void addRule(Type from, Type to, const PIString & condition, Handler handler = 0, bool autoTransition = false, bool resetAllConditions = false) {if (rules_.contains(Rule(from, to))) return; rules_ << Rule(from, to, PIStringList(condition), handler, autoTransition, resetAllConditions);}
|
|
|
|
//! Add rule of transition
|
|
void addRule(Type from, Type to, Handler handler, bool autoTransition = false, bool resetAllConditions = false) {if (rules_.contains(Rule(from, to))) return; rules_ << Rule(from, to, PIStringList(), handler, autoTransition, resetAllConditions);}
|
|
|
|
//! Add rule of transition
|
|
void addRule(Type from, Type to, const PIStringList & conditions = PIStringList(), Handler handler = 0, bool autoTransition = false, bool resetAllConditions = false) {if (rules_.contains(Rule(from, to))) return; rules_ << Rule(from, to, conditions, handler, autoTransition, resetAllConditions);}
|
|
|
|
//! Add rule of transition
|
|
void addRule(const Rule & rule) {if (rules_.contains(rule)) return; rules_ << rule;}
|
|
|
|
//! Rules count
|
|
int rulesCount() const {return rules_.size_s();}
|
|
|
|
//! Remove all rules
|
|
void clearRules() {rules_.clear();}
|
|
|
|
|
|
//! Setup initial state. \a reset() will set machine state to "value"
|
|
void setInitialState(Type value) {
|
|
for (int i = 0; i < states_.size_s(); ++i)
|
|
if (states_[i].value == value) {
|
|
init_ = state_ = states_[i];
|
|
return;
|
|
}
|
|
}
|
|
|
|
/** \brief Try to switch machine state to state "to"
|
|
* \details If there is rule of transition exists and this rule conditions
|
|
* is performed then machine switched to new state immediately. Otherwise machine
|
|
* will be try to enter to new state every \a tick().
|
|
* \return \c true if state switched immediately, otherwise \c false */
|
|
bool switchToState(Type to) {
|
|
switch_to = to;
|
|
for (int i = 0; i < rules_.size_s(); ++i) {
|
|
Rule & r(rules_[i]);
|
|
if ((r.from != state_.value) || (r.to != to)) continue;
|
|
if (!checkConditions(r)) continue;
|
|
State ts = findState(to);
|
|
if (r.handler != 0 && parent_ != 0) r.handler(parent_);
|
|
transition(state_, ts);
|
|
state_ = ts;
|
|
resetConditions(r);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//! Reset machine state to initial and clear all conditions
|
|
void reset() {state_ = init_; resetConditions();}
|
|
|
|
//! Returns current state of machine
|
|
const State & currentState() const {return state_;}
|
|
|
|
|
|
//! Reset all performed conditions
|
|
void resetConditions() {cond.clear();}
|
|
|
|
//! Reset performed condition with name "name"
|
|
void resetCondition(const PIString & name) {
|
|
for (int i = 0; i < cond.size_s(); ++i)
|
|
if (cond[i].first == name) {
|
|
cond.remove(i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
//! Perform condition with name "name" "times" times.
|
|
void performCondition(const PIString & name, int times = 1) {
|
|
if (times <= 0) return;
|
|
for (int i = 0; i < cond.size_s(); ++i)
|
|
if (cond[i].first == name) {
|
|
cond[i].second += times;
|
|
return;
|
|
}
|
|
cond << Condition(name, times);
|
|
}
|
|
|
|
//! Perform every condition with name from "names" one time.
|
|
void performConditions(const PIStringList & names) {
|
|
bool ok;
|
|
for (int n = 0; n < names.size_s(); ++n) {
|
|
ok = false;
|
|
for (int i = 0; i < cond.size_s(); ++i) {
|
|
if (cond[i].first == names[n]) {
|
|
cond[i].second++;
|
|
ok = true;
|
|
break;
|
|
}
|
|
}
|
|
if (ok) continue;
|
|
cond << Condition(names[n], 1);
|
|
}
|
|
}
|
|
|
|
//! Returns all current performed conditions
|
|
const PIVector<Condition> & currentConditions() const {return cond;}
|
|
|
|
Type * currentState_ptr() {return &state_.value;}
|
|
int * conditionsCount_ptr() {static int c = 0; c = cond.size_s(); return &c;}
|
|
|
|
//! \handlers
|
|
//! \{
|
|
|
|
//! \fn void tick()
|
|
//! \brief Main function of machine. Execute \a execution() and check if need to switch state
|
|
|
|
//! \fn void tick(void * data, int delim)
|
|
//! \brief Main function of machine. Execute \a execution() and check if need to switch state
|
|
|
|
//! \}
|
|
|
|
EVENT_HANDLER(void, tick) {tick(0, 0);}
|
|
EVENT_HANDLER2(void, tick, void * , data, int, delim) {
|
|
execution(state_);
|
|
if (state_.handler != 0 && parent_ != 0) state_.handler(parent_);
|
|
if (switch_to != state_.value) switchToState(switch_to);
|
|
else {
|
|
piForeachC (Rule & r, rules_) {
|
|
if (!r.autoTransition || r.from != state_.value) continue;
|
|
if (checkConditions(r)) {
|
|
switchToState(r.to);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected:
|
|
|
|
//! Reimplement this function to process current state of machine
|
|
virtual void execution(const State & state) {;}
|
|
|
|
//! Reimplement this function to process switching current state of machine
|
|
virtual void transition(const State & from, const State & to) {;}
|
|
|
|
private:
|
|
State findState(Type value) {
|
|
for (int i = 0; i < states_.size_s(); ++i)
|
|
if (states_[i].value == value)
|
|
return states_[i];
|
|
return State();
|
|
}
|
|
bool checkConditions(const Rule & rule) {
|
|
//if (cond.size_s() < rule.conditions.size_s()) return false;
|
|
int oc = 0;
|
|
for (int i = 0; i < cond.size_s(); ++i) {
|
|
PIString & rn(cond[i].first);
|
|
for (int j = 0; j < rule.conditions.size_s(); ++j) {
|
|
if (rn != rule.conditions[j].first) continue;
|
|
if (cond[i].second < rule.conditions[j].second) return false;
|
|
oc++;
|
|
}
|
|
}
|
|
return (rule.conditions.size_s() == oc);
|
|
}
|
|
void resetConditions(const Rule & rule) {
|
|
if (rule.resetAllConditions) {
|
|
cond.clear();
|
|
return;
|
|
}
|
|
for (int i = 0; i < cond.size_s(); ++i) {
|
|
PIString & rn(cond[i].first);
|
|
for (int j = 0; j < rule.conditions.size_s(); ++j) {
|
|
if (rn != rule.conditions[j].first) continue;
|
|
cond[i].second -= rule.conditions[j].second;
|
|
if (cond[i].second <= 0) {
|
|
cond.remove(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
PIVector<State> states_;
|
|
PIVector<Rule> rules_;
|
|
State init_, state_;
|
|
Type switch_to;
|
|
void * parent_;
|
|
PIVector<Condition> cond;
|
|
|
|
};
|
|
|
|
|
|
#endif // PISTATEMACHINE_H
|