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"
30 #include <boost/foreach.hpp>
32 #include <simgear/debug/logstream.hxx>
33 #include <simgear/structure/SGBinding.hxx>
34 #include <simgear/props/condition.hxx>
35 #include <simgear/timing/timestamp.hxx>
36 #include <simgear/structure/exception.hxx>
41 typedef std::vector<StateMachine::State_ptr> StatePtrVec;
43 static void readBindingList(SGPropertyNode* desc, const std::string& name,
44 SGPropertyNode* root, SGBindingList& result)
46 BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) {
47 SGBinding* bind = new SGBinding;
49 result.push_back(bind);
53 ///////////////////////////////////////////////////////////////////////////
55 class StateMachine::State::StatePrivate
59 SGBindingList _updateBindings,
64 ///////////////////////////////////////////////////////////////////////////
66 class StateMachine::Transition::TransitionPrivate
70 SGBindingList _bindings;
71 std::set<State*> _sourceStates; ///< weak refs to source states
73 SGSharedPtr<SGCondition> _condition;
76 ///////////////////////////////////////////////////////////////////////////
78 class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
81 StateMachinePrivate(StateMachine* p) : _p(p) { }
83 void computeEligibleTransitions()
86 BOOST_FOREACH(Transition_ptr t, _transitions) {
87 if (t->applicableForState(_currentState)) {
88 _eligible.push_back(t.ptr());
94 State_ptr _currentState;
96 std::vector<Transition_ptr> _transitions;
97 std::vector<Transition*> _eligible;
98 SGTimeStamp _timeInState;
100 bool _listenerLockout; ///< block our listener when self-updating props
101 virtual void valueChanged(SGPropertyNode* changed)
103 if (_listenerLockout) {
107 if (changed == _currentStateIndex) {
108 State_ptr s = _p->stateByIndex(changed->getIntValue());
109 _p->changeToState(s);
110 } else if (changed == _currentStateName) {
111 _p->changeToStateName(changed->getStringValue());
115 // exposed properties
116 SGPropertyNode_ptr _root;
117 SGPropertyNode_ptr _currentStateIndex;
118 SGPropertyNode_ptr _currentStateName;
119 SGPropertyNode_ptr _timeInStateProp;
122 ///////////////////////////////////////////////////////////////////////////
124 StateMachine::State::State(const std::string& aName) :
130 StateMachine::State::~State()
134 std::string StateMachine::State::name() const
139 void StateMachine::State::update()
141 fireBindingList(d->_updateBindings);
144 void StateMachine::State::fireEntryBindings()
146 fireBindingList(d->_entryBindings);
149 void StateMachine::State::fireExitBindings()
151 fireBindingList(d->_exitBindings);
154 void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
156 d->_updateBindings.push_back(aBinding);
159 void StateMachine::State::addEntryBinding(SGBinding* aBinding)
161 d->_entryBindings.push_back(aBinding);
164 void StateMachine::State::addExitBinding(SGBinding* aBinding)
166 d->_exitBindings.push_back(aBinding);
169 ///////////////////////////////////////////////////////////////////////////
171 StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
172 d(new TransitionPrivate)
176 d->_target = aTarget;
179 StateMachine::Transition::~Transition()
183 StateMachine::State* StateMachine::Transition::target() const
188 void StateMachine::Transition::addSourceState(State* aSource)
190 if (aSource == d->_target) { // should this be disallowed outright?
191 SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
194 d->_sourceStates.insert(aSource);
197 bool StateMachine::Transition::applicableForState(State* aCurrent) const
199 return d->_sourceStates.count(aCurrent);
202 bool StateMachine::Transition::evaluate() const
204 return d->_condition->test();
207 void StateMachine::Transition::fireBindings()
209 fireBindingList(d->_bindings);
212 std::string StateMachine::Transition::name() const
217 void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
219 d->_condition = aCondition;
222 void StateMachine::Transition::addBinding(SGBinding* aBinding)
224 d->_bindings.push_back(aBinding);
227 ///////////////////////////////////////////////////////////////////////////
229 StateMachine::StateMachine() :
230 d(new StateMachinePrivate(this))
232 d->_root = new SGPropertyNode();
235 StateMachine::~StateMachine()
240 void StateMachine::init()
244 d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
245 d->_currentStateIndex->setIntValue(0);
247 d->_currentStateName = d->_root->getChild("current-name", 0, true);
248 d->_currentStateName->setStringValue("");
250 d->_currentStateIndex->addChangeListener(d.get());
251 d->_currentStateName->addChangeListener(d.get());
253 d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
254 d->_timeInStateProp->setIntValue(0);
256 // TODO go to default state if found
257 d->computeEligibleTransitions();
261 void StateMachine::shutdown()
263 d->_currentStateIndex->removeChangeListener(d.get());
264 d->_currentStateName->removeChangeListener(d.get());
268 void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
270 d->_currentState->fireExitBindings();
272 // fire bindings before we change the state, hmmmm
274 aTrans->fireBindings();
277 // update our private state and properties
278 d->_listenerLockout = true;
279 d->_currentState = aState;
280 d->_timeInState.stamp();
281 d->_currentStateName->setStringValue(d->_currentState->name());
282 d->_currentStateIndex->setIntValue(indexOfState(aState));
283 d->_timeInStateProp->setIntValue(0);
284 d->_listenerLockout = false;
287 d->_currentState->fireEntryBindings();
288 d->_currentState->update();
290 d->computeEligibleTransitions();
293 void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
295 assert(aState != NULL);
296 if (std::find(d->_states.begin(), d->_states.end(), aState) == d->_states.end()) {
297 throw sg_exception("Requested change to state not in machine");
300 if (aOnlyIfDifferent && (aState == d->_currentState)) {
304 innerChangeState(aState, NULL);
307 void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
309 State_ptr st = findStateByName(aName);
311 throw sg_range_exception("unknown state:" + aName);
314 changeToState(st, aOnlyIfDifferent);
317 StateMachine::State_ptr StateMachine::state() const
319 return d->_currentState;
322 SGPropertyNode* StateMachine::root()
327 void StateMachine::update(double aDt)
329 // do this first, for triggers which depend on time in current state
330 // (spring-loaded transitions)
331 d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
333 Transition_ptr trigger;
335 BOOST_FOREACH(Transition* trans, d->_eligible) {
336 if (trans->evaluate()) {
337 if (trigger != Transition_ptr()) {
338 SG_LOG(SG_GENERAL, SG_WARN, "ambiguous transitions! "
339 << trans->name() << " or " << trigger->name());
346 if (trigger != Transition_ptr()) {
347 SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
348 innerChangeState(trigger->target(), trigger);
351 d->_currentState->update();
354 StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
356 BOOST_FOREACH(State_ptr sp, d->_states) {
357 if (sp->name() == aName) {
362 SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
366 StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
368 if (aIndex >= d->_states.size()) {
369 throw sg_range_exception("invalid state index, out of bounds");
372 return d->_states[aIndex];
375 int StateMachine::indexOfState(State_ptr aState) const
377 StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
378 if (it == d->_states.end()) {
382 return it - d->_states.begin();
385 StateMachine::State_ptr StateMachine::createState(const std::string& aName)
387 if (findStateByName(aName) != NULL) {
388 throw sg_range_exception("duplicate state name");
391 State_ptr st = new State(aName);
396 StateMachine::Transition_ptr
397 StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
399 Transition_ptr t = new Transition(aName, aTarget);
404 StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
406 StateMachine* sm = new StateMachine;
409 BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
410 std::string nm = stateDesc->getStringValue("name");
411 State_ptr st(new State(nm));
413 readBindingList(stateDesc, "enter", root, st->d->_updateBindings);
414 readBindingList(stateDesc, "exit", root, st->d->_entryBindings);
415 readBindingList(stateDesc, "update", root, st->d->_exitBindings);
418 } // of states iteration
420 BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
421 std::string nm = tDesc->getStringValue("name");
422 State_ptr target = sm->findStateByName(tDesc->getStringValue("target"));
424 SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
426 Transition_ptr t(new Transition(nm, target));
427 t->setTriggerCondition(cond);
429 BOOST_FOREACH(SGPropertyNode* src, desc->getChildren("source")) {
430 State_ptr srcState = sm->findStateByName(src->getStringValue());
431 t->addSourceState(srcState);
434 readBindingList(tDesc, "binding", root, t->d->_bindings);
436 sm->addTransition(t);
437 } // of states iteration
442 void StateMachine::addState(State_ptr aState)
444 bool wasEmpty = d->_states.empty();
445 d->_states.push_back(aState);
447 d->_currentState = aState;
451 void StateMachine::addTransition(Transition_ptr aTrans)
453 d->_transitions.push_back(aTrans);
456 } // of namespace simgear