Files
simgear/simgear/structure/StateMachine.cxx
2013-01-25 12:05:16 +01:00

494 lines
13 KiB
C++

/* -*-c++-*-
*
* Copyright (C) 2013 James Turner
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
*/
#ifdef HAVE_CONFIG_H
# include <simgear_config.h>
#endif
#include "StateMachine.hxx"
#include <algorithm>
#include <cassert>
#include <set>
#include <boost/foreach.hpp>
#include <simgear/debug/logstream.hxx>
#include <simgear/structure/SGBinding.hxx>
#include <simgear/props/condition.hxx>
#include <simgear/timing/timestamp.hxx>
#include <simgear/structure/exception.hxx>
namespace simgear
{
typedef std::vector<StateMachine::State_ptr> StatePtrVec;
static void readBindingList(SGPropertyNode* desc, const std::string& name,
SGPropertyNode* root, SGBindingList& result)
{
BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) {
SGBinding* bind = new SGBinding;
bind->read(b, root);
result.push_back(bind);
}
}
///////////////////////////////////////////////////////////////////////////
class StateMachine::State::StatePrivate
{
public:
std::string _name;
SGBindingList _updateBindings,
_entryBindings,
_exitBindings;
};
///////////////////////////////////////////////////////////////////////////
class StateMachine::Transition::TransitionPrivate
{
public:
std::string _name;
SGBindingList _bindings;
std::set<State*> _sourceStates; ///< weak refs to source states
State* _target;
bool _excludeTarget;
SGSharedPtr<SGCondition> _condition;
};
///////////////////////////////////////////////////////////////////////////
class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
{
public:
StateMachinePrivate(StateMachine* p) : _p(p) { }
void computeEligibleTransitions()
{
_eligible.clear();
BOOST_FOREACH(Transition_ptr t, _transitions) {
if (t->applicableForState(_currentState)) {
_eligible.push_back(t.ptr());
}
}
}
StateMachine* _p;
bool _initialised;
State_ptr _currentState;
StatePtrVec _states;
std::vector<Transition_ptr> _transitions;
std::vector<Transition*> _eligible;
SGTimeStamp _timeInState;
bool _listenerLockout; ///< block our listener when self-updating props
virtual void valueChanged(SGPropertyNode* changed)
{
if (_listenerLockout) {
return;
}
if (changed == _currentStateIndex) {
State_ptr s = _p->stateByIndex(changed->getIntValue());
_p->changeToState(s);
} else if (changed == _currentStateName) {
_p->changeToStateName(changed->getStringValue());
}
}
// exposed properties
SGPropertyNode_ptr _root;
SGPropertyNode_ptr _currentStateIndex;
SGPropertyNode_ptr _currentStateName;
SGPropertyNode_ptr _timeInStateProp;
};
///////////////////////////////////////////////////////////////////////////
StateMachine::State::State(const std::string& aName) :
d(new StatePrivate)
{
d->_name = aName;
}
StateMachine::State::~State()
{
}
std::string StateMachine::State::name() const
{
return d->_name;
}
void StateMachine::State::update()
{
fireBindingList(d->_updateBindings);
}
void StateMachine::State::fireEntryBindings()
{
fireBindingList(d->_entryBindings);
}
void StateMachine::State::fireExitBindings()
{
fireBindingList(d->_exitBindings);
}
void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
{
d->_updateBindings.push_back(aBinding);
}
void StateMachine::State::addEntryBinding(SGBinding* aBinding)
{
d->_entryBindings.push_back(aBinding);
}
void StateMachine::State::addExitBinding(SGBinding* aBinding)
{
d->_exitBindings.push_back(aBinding);
}
///////////////////////////////////////////////////////////////////////////
StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
d(new TransitionPrivate)
{
assert(aTarget);
d->_name = aName;
d->_target = aTarget;
d->_excludeTarget = true;
}
StateMachine::Transition::~Transition()
{
}
StateMachine::State* StateMachine::Transition::target() const
{
return d->_target;
}
void StateMachine::Transition::addSourceState(State* aSource)
{
if (aSource == d->_target) { // should this be disallowed outright?
SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
}
d->_sourceStates.insert(aSource);
}
bool StateMachine::Transition::applicableForState(State* aCurrent) const
{
if (d->_excludeTarget && (aCurrent == d->_target)) {
return false;
}
if (d->_sourceStates.empty()) {
return true;
}
return d->_sourceStates.count(aCurrent);
}
bool StateMachine::Transition::evaluate() const
{
return d->_condition->test();
}
void StateMachine::Transition::fireBindings()
{
fireBindingList(d->_bindings);
}
std::string StateMachine::Transition::name() const
{
return d->_name;
}
void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
{
d->_condition = aCondition;
}
void StateMachine::Transition::addBinding(SGBinding* aBinding)
{
d->_bindings.push_back(aBinding);
}
void StateMachine::Transition::setExcludeTarget(bool aExclude)
{
d->_excludeTarget = aExclude;
}
///////////////////////////////////////////////////////////////////////////
StateMachine::StateMachine() :
d(new StateMachinePrivate(this))
{
d->_root = new SGPropertyNode();
d->_listenerLockout = false;
d->_initialised = false;
}
StateMachine::~StateMachine()
{
}
void StateMachine::init()
{
if (d->_initialised) {
return;
}
if (d->_states.empty()) {
throw sg_range_exception("StateMachine::init: no states defined");
}
d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
d->_currentStateIndex->setIntValue(0);
d->_currentStateName = d->_root->getChild("current-name", 0, true);
d->_currentStateName->setStringValue("");
d->_currentStateIndex->addChangeListener(d.get());
d->_currentStateName->addChangeListener(d.get());
d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
d->_timeInStateProp->setIntValue(0);
// TODO go to default state if found
innerChangeState(d->_states[0], NULL);
d->_initialised = true;
}
void StateMachine::shutdown()
{
d->_currentStateIndex->removeChangeListener(d.get());
d->_currentStateName->removeChangeListener(d.get());
}
void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
{
if (d->_currentState) {
d->_currentState->fireExitBindings();
}
// fire bindings before we change the state, hmmmm
if (aTrans) {
aTrans->fireBindings();
}
// update our private state and properties
d->_listenerLockout = true;
d->_currentState = aState;
d->_timeInState.stamp();
d->_currentStateName->setStringValue(d->_currentState->name());
d->_currentStateIndex->setIntValue(indexOfState(aState));
d->_timeInStateProp->setIntValue(0);
d->_listenerLockout = false;
// fire bindings
d->_currentState->fireEntryBindings();
d->_currentState->update();
d->computeEligibleTransitions();
}
void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
{
assert(aState != NULL);
if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) {
throw sg_exception("Requested change to state not in machine");
}
if (aOnlyIfDifferent && (aState == d->_currentState)) {
return;
}
innerChangeState(aState, NULL);
}
void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
{
State_ptr st = findStateByName(aName);
if (!st) {
throw sg_range_exception("unknown state:" + aName);
}
changeToState(st, aOnlyIfDifferent);
}
StateMachine::State_ptr StateMachine::state() const
{
return d->_currentState;
}
SGPropertyNode* StateMachine::root()
{
return d->_root;
}
void StateMachine::update(double aDt)
{
// do this first, for triggers which depend on time in current state
// (spring-loaded transitions)
d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
Transition_ptr trigger;
BOOST_FOREACH(Transition* trans, d->_eligible) {
if (trans->evaluate()) {
if (trigger != Transition_ptr()) {
SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! "
<< trans->name() << " or " << trigger->name());
}
trigger = trans;
}
}
if (trigger != Transition_ptr()) {
SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
innerChangeState(trigger->target(), trigger);
}
d->_currentState->update();
}
StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
{
BOOST_FOREACH(State_ptr sp, d->_states) {
if (sp->name() == aName) {
return sp;
}
}
SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
return State_ptr();
}
StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
{
if (aIndex >= d->_states.size()) {
throw sg_range_exception("invalid state index, out of bounds");
}
return d->_states[aIndex];
}
int StateMachine::indexOfState(State_ptr aState) const
{
StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
if (it == d->_states.end()) {
return -1;
}
return it - d->_states.begin();
}
StateMachine::State_ptr StateMachine::createState(const std::string& aName)
{
if (findStateByName(aName) != NULL) {
throw sg_range_exception("duplicate state name");
}
State_ptr st = new State(aName);
addState(st);
return st;
}
StateMachine::Transition_ptr
StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
{
Transition_ptr t = new Transition(aName, aTarget);
addTransition(t);
return t;
}
void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
{
std::string path = desc->getStringValue("branch");
if (!path.empty()) {
d->_root = root->getNode(path, 0, true);
assert(d->_root);
}
BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
std::string nm = stateDesc->getStringValue("name");
State_ptr st(new State(nm));
readBindingList(stateDesc, "enter", root, st->d->_updateBindings);
readBindingList(stateDesc, "exit", root, st->d->_entryBindings);
readBindingList(stateDesc, "update", root, st->d->_exitBindings);
addState(st);
} // of states iteration
BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
std::string nm = tDesc->getStringValue("name");
State_ptr target = findStateByName(tDesc->getStringValue("target"));
SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
Transition_ptr t(new Transition(nm, target));
t->setTriggerCondition(cond);
t->setExcludeTarget(tDesc->getBoolValue("exclude-target", true));
BOOST_FOREACH(SGPropertyNode* src, desc->getChildren("source")) {
State_ptr srcState = findStateByName(src->getStringValue());
t->addSourceState(srcState);
}
readBindingList(tDesc, "binding", root, t->d->_bindings);
addTransition(t);
} // of states iteration
init();
}
StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
{
StateMachine* sm = new StateMachine;
sm->initFromPlist(desc, root);
return sm;
}
void StateMachine::addState(State_ptr aState)
{
bool wasEmpty = d->_states.empty();
d->_states.push_back(aState);
if (wasEmpty) {
d->_currentState = aState;
}
}
void StateMachine::addTransition(Transition_ptr aTrans)
{
d->_transitions.push_back(aTrans);
}
} // of namespace simgear