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
74 SGSharedPtr<SGCondition> _condition;
77 ///////////////////////////////////////////////////////////////////////////
79 class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
82 StateMachinePrivate(StateMachine* p) : _p(p) { }
84 void computeEligibleTransitions()
87 BOOST_FOREACH(Transition_ptr t, _transitions) {
88 if (t->applicableForState(_currentState)) {
89 _eligible.push_back(t.ptr());
95 State_ptr _currentState;
97 std::vector<Transition_ptr> _transitions;
98 std::vector<Transition*> _eligible;
99 SGTimeStamp _timeInState;
101 bool _listenerLockout; ///< block our listener when self-updating props
102 virtual void valueChanged(SGPropertyNode* changed)
104 if (_listenerLockout) {
108 if (changed == _currentStateIndex) {
109 State_ptr s = _p->stateByIndex(changed->getIntValue());
110 _p->changeToState(s);
111 } else if (changed == _currentStateName) {
112 _p->changeToStateName(changed->getStringValue());
116 // exposed properties
117 SGPropertyNode_ptr _root;
118 SGPropertyNode_ptr _currentStateIndex;
119 SGPropertyNode_ptr _currentStateName;
120 SGPropertyNode_ptr _timeInStateProp;
123 ///////////////////////////////////////////////////////////////////////////
125 StateMachine::State::State(const std::string& aName) :
131 StateMachine::State::~State()
135 std::string StateMachine::State::name() const
140 void StateMachine::State::update()
142 fireBindingList(d->_updateBindings);
145 void StateMachine::State::fireEntryBindings()
147 fireBindingList(d->_entryBindings);
150 void StateMachine::State::fireExitBindings()
152 fireBindingList(d->_exitBindings);
155 void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
157 d->_updateBindings.push_back(aBinding);
160 void StateMachine::State::addEntryBinding(SGBinding* aBinding)
162 d->_entryBindings.push_back(aBinding);
165 void StateMachine::State::addExitBinding(SGBinding* aBinding)
167 d->_exitBindings.push_back(aBinding);
170 ///////////////////////////////////////////////////////////////////////////
172 StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
173 d(new TransitionPrivate)
177 d->_target = aTarget;
180 StateMachine::Transition::~Transition()
184 StateMachine::State* StateMachine::Transition::target() const
189 void StateMachine::Transition::addSourceState(State* aSource)
191 if (aSource == d->_target) { // should this be disallowed outright?
192 SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
195 d->_sourceStates.insert(aSource);
198 bool StateMachine::Transition::applicableForState(State* aCurrent) const
200 return d->_sourceStates.count(aCurrent);
203 bool StateMachine::Transition::evaluate() const
205 return d->_condition->test();
208 void StateMachine::Transition::fireBindings()
210 fireBindingList(d->_bindings);
213 std::string StateMachine::Transition::name() const
218 void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
220 d->_condition = aCondition;
223 void StateMachine::Transition::addBinding(SGBinding* aBinding)
225 d->_bindings.push_back(aBinding);
228 ///////////////////////////////////////////////////////////////////////////
230 StateMachine::StateMachine() :
231 d(new StateMachinePrivate(this))
233 d->_root = new SGPropertyNode();
236 StateMachine::~StateMachine()
241 void StateMachine::init()
245 d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
246 d->_currentStateIndex->setIntValue(0);
248 d->_currentStateName = d->_root->getChild("current-name", 0, true);
249 d->_currentStateName->setStringValue("");
251 d->_currentStateIndex->addChangeListener(d.get());
252 d->_currentStateName->addChangeListener(d.get());
254 d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
255 d->_timeInStateProp->setIntValue(0);
257 // TODO go to default state if found
258 d->computeEligibleTransitions();
262 void StateMachine::shutdown()
264 d->_currentStateIndex->removeChangeListener(d.get());
265 d->_currentStateName->removeChangeListener(d.get());
269 void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
271 d->_currentState->fireExitBindings();
273 // fire bindings before we change the state, hmmmm
275 aTrans->fireBindings();
278 // update our private state and properties
279 d->_listenerLockout = true;
280 d->_currentState = aState;
281 d->_timeInState.stamp();
282 d->_currentStateName->setStringValue(d->_currentState->name());
283 d->_currentStateIndex->setIntValue(indexOfState(aState));
284 d->_timeInStateProp->setIntValue(0);
285 d->_listenerLockout = false;
288 d->_currentState->fireEntryBindings();
289 d->_currentState->update();
291 d->computeEligibleTransitions();
294 void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
296 assert(aState != NULL);
297 if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) {
298 throw sg_exception("Requested change to state not in machine");
301 if (aOnlyIfDifferent && (aState == d->_currentState)) {
305 innerChangeState(aState, NULL);
308 void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
310 State_ptr st = findStateByName(aName);
312 throw sg_range_exception("unknown state:" + aName);
315 changeToState(st, aOnlyIfDifferent);
318 StateMachine::State_ptr StateMachine::state() const
320 return d->_currentState;
323 SGPropertyNode* StateMachine::root()
328 void StateMachine::update(double aDt)
330 // do this first, for triggers which depend on time in current state
331 // (spring-loaded transitions)
332 d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
334 Transition_ptr trigger;
336 BOOST_FOREACH(Transition* trans, d->_eligible) {
337 if (trans->evaluate()) {
338 if (trigger != Transition_ptr()) {
339 SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! "
340 << trans->name() << " or " << trigger->name());
347 if (trigger != Transition_ptr()) {
348 SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
349 innerChangeState(trigger->target(), trigger);
352 d->_currentState->update();
355 StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
357 BOOST_FOREACH(State_ptr sp, d->_states) {
358 if (sp->name() == aName) {
363 SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
367 StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
369 if (aIndex >= d->_states.size()) {
370 throw sg_range_exception("invalid state index, out of bounds");
373 return d->_states[aIndex];
376 int StateMachine::indexOfState(State_ptr aState) const
378 StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
379 if (it == d->_states.end()) {
383 return it - d->_states.begin();
386 StateMachine::State_ptr StateMachine::createState(const std::string& aName)
388 if (findStateByName(aName) != NULL) {
389 throw sg_range_exception("duplicate state name");
392 State_ptr st = new State(aName);
397 StateMachine::Transition_ptr
398 StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
400 Transition_ptr t = new Transition(aName, aTarget);
405 StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
407 StateMachine* sm = new StateMachine;
410 BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
411 std::string nm = stateDesc->getStringValue("name");
412 State_ptr st(new State(nm));
414 readBindingList(stateDesc, "enter", root, st->d->_updateBindings);
415 readBindingList(stateDesc, "exit", root, st->d->_entryBindings);
416 readBindingList(stateDesc, "update", root, st->d->_exitBindings);
419 } // of states iteration
421 BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
422 std::string nm = tDesc->getStringValue("name");
423 State_ptr target = sm->findStateByName(tDesc->getStringValue("target"));
425 SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
427 Transition_ptr t(new Transition(nm, target));
428 t->setTriggerCondition(cond);
430 BOOST_FOREACH(SGPropertyNode* src, desc->getChildren("source")) {
431 State_ptr srcState = sm->findStateByName(src->getStringValue());
432 t->addSourceState(srcState);
435 readBindingList(tDesc, "binding", root, t->d->_bindings);
437 sm->addTransition(t);
438 } // of states iteration
443 void StateMachine::addState(State_ptr aState)
445 bool wasEmpty = d->_states.empty();
446 d->_states.push_back(aState);
448 d->_currentState = aState;
452 void StateMachine::addTransition(Transition_ptr aTrans)
454 d->_transitions.push_back(aTrans);
457 } // of namespace simgear