3 * Copyright (C) 2013 James Turner
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
23 # include <simgear_config.h>
26 #include "StateMachine.hxx"
31 #include <boost/foreach.hpp>
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/structure/SGBinding.hxx>
35 #include <simgear/props/condition.hxx>
36 #include <simgear/timing/timestamp.hxx>
37 #include <simgear/structure/exception.hxx>
42 typedef std::vector<StateMachine::State_ptr> StatePtrVec;
44 static void readBindingList(SGPropertyNode* desc, const std::string& name,
45 SGPropertyNode* root, SGBindingList& result)
47 BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) {
48 SGBinding* bind = new SGBinding;
50 result.push_back(bind);
54 ///////////////////////////////////////////////////////////////////////////
56 class StateMachine::State::StatePrivate
60 SGBindingList _updateBindings,
65 ///////////////////////////////////////////////////////////////////////////
67 class StateMachine::Transition::TransitionPrivate
71 SGBindingList _bindings;
72 std::set<State*> _sourceStates; ///< weak refs to source states
75 SGSharedPtr<SGCondition> _condition;
78 ///////////////////////////////////////////////////////////////////////////
80 class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
83 StateMachinePrivate(StateMachine* p) : _p(p) { }
85 void computeEligibleTransitions()
88 BOOST_FOREACH(Transition_ptr t, _transitions) {
89 if (t->applicableForState(_currentState)) {
90 _eligible.push_back(t.ptr());
97 State_ptr _currentState;
99 std::vector<Transition_ptr> _transitions;
100 std::vector<Transition*> _eligible;
101 SGTimeStamp _timeInState;
103 bool _listenerLockout; ///< block our listener when self-updating props
104 virtual void valueChanged(SGPropertyNode* changed)
106 if (_listenerLockout) {
110 if (changed == _currentStateIndex) {
111 State_ptr s = _p->stateByIndex(changed->getIntValue());
112 _p->changeToState(s);
113 } else if (changed == _currentStateName) {
114 _p->changeToStateName(changed->getStringValue());
118 // exposed properties
119 SGPropertyNode_ptr _root;
120 SGPropertyNode_ptr _currentStateIndex;
121 SGPropertyNode_ptr _currentStateName;
122 SGPropertyNode_ptr _timeInStateProp;
125 ///////////////////////////////////////////////////////////////////////////
127 StateMachine::State::State(const std::string& aName) :
133 StateMachine::State::~State()
137 std::string StateMachine::State::name() const
142 void StateMachine::State::update()
144 fireBindingList(d->_updateBindings);
147 void StateMachine::State::fireEntryBindings()
149 fireBindingList(d->_entryBindings);
152 void StateMachine::State::fireExitBindings()
154 fireBindingList(d->_exitBindings);
157 void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
159 d->_updateBindings.push_back(aBinding);
162 void StateMachine::State::addEntryBinding(SGBinding* aBinding)
164 d->_entryBindings.push_back(aBinding);
167 void StateMachine::State::addExitBinding(SGBinding* aBinding)
169 d->_exitBindings.push_back(aBinding);
172 ///////////////////////////////////////////////////////////////////////////
174 StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
175 d(new TransitionPrivate)
179 d->_target = aTarget;
180 d->_excludeTarget = true;
183 StateMachine::Transition::~Transition()
187 StateMachine::State* StateMachine::Transition::target() const
192 void StateMachine::Transition::addSourceState(State* aSource)
194 if (aSource == d->_target) { // should this be disallowed outright?
195 SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
198 d->_sourceStates.insert(aSource);
201 bool StateMachine::Transition::applicableForState(State* aCurrent) const
203 if (d->_excludeTarget && (aCurrent == d->_target)) {
207 if (d->_sourceStates.empty()) {
210 return d->_sourceStates.count(aCurrent);
213 bool StateMachine::Transition::evaluate() const
215 return d->_condition->test();
218 void StateMachine::Transition::fireBindings()
220 fireBindingList(d->_bindings);
223 std::string StateMachine::Transition::name() const
228 void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
230 d->_condition = aCondition;
233 void StateMachine::Transition::addBinding(SGBinding* aBinding)
235 d->_bindings.push_back(aBinding);
238 void StateMachine::Transition::setExcludeTarget(bool aExclude)
240 d->_excludeTarget = aExclude;
243 ///////////////////////////////////////////////////////////////////////////
245 StateMachine::StateMachine() :
246 d(new StateMachinePrivate(this))
248 d->_root = new SGPropertyNode();
249 d->_listenerLockout = false;
250 d->_initialised = false;
253 StateMachine::~StateMachine()
258 void StateMachine::init()
260 if (d->_initialised) {
264 if (d->_states.empty()) {
265 throw sg_range_exception("StateMachine::init: no states defined");
268 d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
269 d->_currentStateIndex->setIntValue(0);
271 d->_currentStateName = d->_root->getChild("current-name", 0, true);
272 d->_currentStateName->setStringValue("");
274 d->_currentStateIndex->addChangeListener(d.get());
275 d->_currentStateName->addChangeListener(d.get());
277 d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
278 d->_timeInStateProp->setIntValue(0);
280 // TODO go to default state if found
281 innerChangeState(d->_states[0], NULL);
282 d->_initialised = true;
285 void StateMachine::shutdown()
287 d->_currentStateIndex->removeChangeListener(d.get());
288 d->_currentStateName->removeChangeListener(d.get());
292 void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
294 if (d->_currentState) {
295 d->_currentState->fireExitBindings();
298 // fire bindings before we change the state, hmmmm
300 aTrans->fireBindings();
303 // update our private state and properties
304 d->_listenerLockout = true;
305 d->_currentState = aState;
306 d->_timeInState.stamp();
307 d->_currentStateName->setStringValue(d->_currentState->name());
308 d->_currentStateIndex->setIntValue(indexOfState(aState));
309 d->_timeInStateProp->setIntValue(0);
310 d->_listenerLockout = false;
313 d->_currentState->fireEntryBindings();
314 d->_currentState->update();
316 d->computeEligibleTransitions();
319 void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
321 assert(aState != NULL);
322 if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) {
323 throw sg_exception("Requested change to state not in machine");
326 if (aOnlyIfDifferent && (aState == d->_currentState)) {
330 innerChangeState(aState, NULL);
333 void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
335 State_ptr st = findStateByName(aName);
337 throw sg_range_exception("unknown state:" + aName);
340 changeToState(st, aOnlyIfDifferent);
343 StateMachine::State_ptr StateMachine::state() const
345 return d->_currentState;
348 SGPropertyNode* StateMachine::root()
353 void StateMachine::update(double aDt)
355 // do this first, for triggers which depend on time in current state
356 // (spring-loaded transitions)
357 d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
359 Transition_ptr trigger;
361 BOOST_FOREACH(Transition* trans, d->_eligible) {
362 if (trans->evaluate()) {
363 if (trigger != Transition_ptr()) {
364 SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! "
365 << trans->name() << " or " << trigger->name());
372 if (trigger != Transition_ptr()) {
373 SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
374 innerChangeState(trigger->target(), trigger);
377 d->_currentState->update();
380 StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
382 BOOST_FOREACH(State_ptr sp, d->_states) {
383 if (sp->name() == aName) {
388 SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
392 StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
394 if (aIndex >= d->_states.size()) {
395 throw sg_range_exception("invalid state index, out of bounds");
398 return d->_states[aIndex];
401 int StateMachine::indexOfState(State_ptr aState) const
403 StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
404 if (it == d->_states.end()) {
408 return it - d->_states.begin();
411 StateMachine::State_ptr StateMachine::createState(const std::string& aName)
413 if (findStateByName(aName) != NULL) {
414 throw sg_range_exception("duplicate state name");
417 State_ptr st = new State(aName);
422 StateMachine::Transition_ptr
423 StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
425 Transition_ptr t = new Transition(aName, aTarget);
430 void StateMachine::initFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
432 std::string path = desc->getStringValue("branch");
434 d->_root = root->getNode(path, 0, true);
438 BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
439 std::string nm = stateDesc->getStringValue("name");
440 State_ptr st(new State(nm));
442 readBindingList(stateDesc, "enter", root, st->d->_entryBindings);
443 readBindingList(stateDesc, "update", root, st->d->_updateBindings);
444 readBindingList(stateDesc, "exit", root, st->d->_exitBindings);
447 } // of states iteration
449 BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
450 std::string nm = tDesc->getStringValue("name");
451 State_ptr target = findStateByName(tDesc->getStringValue("target"));
453 SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
455 Transition_ptr t(new Transition(nm, target));
456 t->setTriggerCondition(cond);
458 t->setExcludeTarget(tDesc->getBoolValue("exclude-target", true));
459 BOOST_FOREACH(SGPropertyNode* src, tDesc->getChildren("source")) {
460 State_ptr srcState = findStateByName(src->getStringValue());
461 t->addSourceState(srcState);
464 readBindingList(tDesc, "binding", root, t->d->_bindings);
467 } // of states iteration
472 StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
474 StateMachine* sm = new StateMachine;
475 sm->initFromPlist(desc, root);
479 void StateMachine::addState(State_ptr aState)
481 bool wasEmpty = d->_states.empty();
482 d->_states.push_back(aState);
484 d->_currentState = aState;
488 void StateMachine::addTransition(Transition_ptr aTrans)
490 d->_transitions.push_back(aTrans);
493 } // of namespace simgear