]> git.mxchange.org Git - simgear.git/blob - simgear/structure/StateMachine.cxx
76a00b291910a1de973cd0b85bd5529f522e9c6d
[simgear.git] / simgear / structure / StateMachine.cxx
1 /* -*-c++-*-
2  *
3  * Copyright (C) 2013 James Turner
4  *
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.
9  *
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.
14  *
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,
18  * MA 02110-1301, USA.
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include <simgear_config.h>
24 #endif
25      
26 #include "StateMachine.hxx"
27
28 #include <cassert>
29 #include <set>
30 #include <boost/foreach.hpp>
31
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>
37                     
38 namespace simgear
39 {
40
41 typedef std::vector<StateMachine::State_ptr> StatePtrVec;
42
43 static void readBindingList(SGPropertyNode* desc, const std::string& name, 
44     SGPropertyNode* root, SGBindingList& result)
45 {
46     BOOST_FOREACH(SGPropertyNode* b, desc->getChildren(name)) {
47         SGBinding* bind = new SGBinding;
48         bind->read(b, root);
49         result.push_back(bind);
50     }
51 }
52
53 ///////////////////////////////////////////////////////////////////////////
54
55 class StateMachine::State::StatePrivate
56 {
57 public:
58     std::string _name;
59     SGBindingList _updateBindings, 
60         _entryBindings, 
61         _exitBindings;
62 };
63
64 ///////////////////////////////////////////////////////////////////////////
65
66 class StateMachine::Transition::TransitionPrivate
67 {
68 public:
69     std::string _name;
70     SGBindingList _bindings;
71     std::set<State*> _sourceStates; ///< weak refs to source states
72     State* _target;
73     SGSharedPtr<SGCondition> _condition;
74 };
75     
76 ///////////////////////////////////////////////////////////////////////////
77
78 class StateMachine::StateMachinePrivate : public SGPropertyChangeListener
79 {
80 public:
81     StateMachinePrivate(StateMachine* p) : _p(p) { }
82     
83     void computeEligibleTransitions()
84     {
85         _eligible.clear();
86         BOOST_FOREACH(Transition_ptr t, _transitions) {
87             if (t->applicableForState(_currentState)) {
88                 _eligible.push_back(t.ptr());
89             }
90         }
91     }
92     
93     StateMachine* _p;
94     State_ptr _currentState;
95     StatePtrVec _states;
96     std::vector<Transition_ptr> _transitions;
97     std::vector<Transition*> _eligible;
98     SGTimeStamp _timeInState;
99     
100     bool _listenerLockout; ///< block our listener when self-updating props
101     virtual void valueChanged(SGPropertyNode* changed)
102     {
103         if (_listenerLockout) {
104             return;
105         }
106         
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());
112         }
113     }
114     
115 // exposed properties
116     SGPropertyNode_ptr _root;
117     SGPropertyNode_ptr _currentStateIndex;
118     SGPropertyNode_ptr _currentStateName;
119     SGPropertyNode_ptr _timeInStateProp;
120 };
121
122 ///////////////////////////////////////////////////////////////////////////
123
124 StateMachine::State::State(const std::string& aName) :
125     d(new StatePrivate)
126 {
127     d->_name = aName;
128 }
129
130 StateMachine::State::~State()
131 {
132 }
133
134 std::string StateMachine::State::name() const
135 {
136     return d->_name;
137 }
138
139 void StateMachine::State::update()
140 {
141     fireBindingList(d->_updateBindings);
142 }
143
144 void StateMachine::State::fireEntryBindings()
145 {
146     fireBindingList(d->_entryBindings);
147 }   
148
149 void StateMachine::State::fireExitBindings()
150 {
151     fireBindingList(d->_exitBindings);
152 }   
153
154 void StateMachine::State::addUpdateBinding(SGBinding* aBinding)
155 {
156     d->_updateBindings.push_back(aBinding);
157 }
158
159 void StateMachine::State::addEntryBinding(SGBinding* aBinding)
160 {
161     d->_entryBindings.push_back(aBinding);
162 }
163
164 void StateMachine::State::addExitBinding(SGBinding* aBinding)
165 {
166     d->_exitBindings.push_back(aBinding);
167 }
168
169 ///////////////////////////////////////////////////////////////////////////
170
171 StateMachine::Transition::Transition(const std::string& aName, State* aTarget) :
172     d(new TransitionPrivate)
173 {
174     assert(aTarget);
175     d->_name = aName;
176     d->_target = aTarget;
177 }
178
179 StateMachine::Transition::~Transition()
180 {
181 }
182
183 StateMachine::State* StateMachine::Transition::target() const
184 {
185     return d->_target;
186 }    
187
188 void StateMachine::Transition::addSourceState(State* aSource)
189 {
190     if (aSource == d->_target) { // should this be disallowed outright?
191         SG_LOG(SG_GENERAL, SG_WARN, d->_name << ": adding target state as source");
192     }
193     
194     d->_sourceStates.insert(aSource);
195 }    
196
197 bool StateMachine::Transition::applicableForState(State* aCurrent) const
198 {
199     return d->_sourceStates.count(aCurrent);
200
201     
202 bool StateMachine::Transition::evaluate() const
203 {
204     return d->_condition->test();
205 }    
206
207 void StateMachine::Transition::fireBindings()
208 {
209     fireBindingList(d->_bindings);
210 }   
211
212 std::string StateMachine::Transition::name() const
213 {
214     return d->_name;
215 }
216
217 void StateMachine::Transition::setTriggerCondition(SGCondition* aCondition)
218 {
219     d->_condition = aCondition;
220 }
221
222 void StateMachine::Transition::addBinding(SGBinding* aBinding)
223 {
224     d->_bindings.push_back(aBinding);
225 }
226     
227 ///////////////////////////////////////////////////////////////////////////
228
229 StateMachine::StateMachine() :
230     d(new StateMachinePrivate(this))
231 {
232     d->_root = new SGPropertyNode();
233 }
234
235 StateMachine::~StateMachine()
236 {
237     
238 }
239
240 void StateMachine::init()
241 {
242     
243     
244     d->_currentStateIndex = d->_root->getChild("current-index", 0, true);
245     d->_currentStateIndex->setIntValue(0);
246     
247     d->_currentStateName = d->_root->getChild("current-name", 0, true);
248     d->_currentStateName->setStringValue("");
249     
250     d->_currentStateIndex->addChangeListener(d.get());
251     d->_currentStateName->addChangeListener(d.get());
252     
253     d->_timeInStateProp = d->_root->getChild("elapsed-time-msec", 0, true);
254     d->_timeInStateProp->setIntValue(0);
255     
256     // TODO go to default state if found
257     d->computeEligibleTransitions();
258     
259 }
260
261 void StateMachine::shutdown()
262 {
263     d->_currentStateIndex->removeChangeListener(d.get());
264     d->_currentStateName->removeChangeListener(d.get());
265     
266 }
267
268 void StateMachine::innerChangeState(State_ptr aState, Transition_ptr aTrans)
269 {
270     d->_currentState->fireExitBindings();
271         
272 // fire bindings before we change the state, hmmmm    
273     if (aTrans) {
274         aTrans->fireBindings();
275     }
276     
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;
285     
286     // fire bindings
287     d->_currentState->fireEntryBindings();
288     d->_currentState->update();
289         
290     d->computeEligibleTransitions();
291 }
292
293 void StateMachine::changeToState(State_ptr aState, bool aOnlyIfDifferent)
294 {
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");
298     }
299     
300     if (aOnlyIfDifferent && (aState == d->_currentState)) {
301         return;
302     }
303     
304     innerChangeState(aState, NULL);
305 }
306
307 void StateMachine::changeToStateName(const std::string& aName, bool aOnlyIfDifferent)
308 {
309     State_ptr st = findStateByName(aName);
310     if (!st) {
311         throw sg_range_exception("unknown state:" + aName);
312     }
313     
314     changeToState(st, aOnlyIfDifferent);
315 }
316
317 StateMachine::State_ptr StateMachine::state() const
318 {
319     return d->_currentState;
320 }
321     
322 SGPropertyNode* StateMachine::root()
323 {
324     return d->_root;
325 }
326
327 void StateMachine::update(double aDt)
328 {
329     // do this first, for triggers which depend on time in current state
330     // (spring-loaded transitions)
331     d->_timeInStateProp->setIntValue(d->_timeInState.elapsedMSec());
332     
333     Transition_ptr trigger;
334     
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());
340             }
341             
342             trigger = trans;
343         }
344     }
345     
346     if (trigger != Transition_ptr()) {
347         SG_LOG(SG_GENERAL, SG_DEBUG, "firing transition:" << trigger->name());
348         innerChangeState(trigger->target(), trigger);
349     }
350     
351     d->_currentState->update();
352 }    
353
354 StateMachine::State_ptr StateMachine::findStateByName(const std::string& aName) const
355 {
356     BOOST_FOREACH(State_ptr sp, d->_states) {
357         if (sp->name() == aName) {
358             return sp;
359         }
360     }
361     
362     SG_LOG(SG_GENERAL, SG_WARN, "unknown state:" << aName);
363     return State_ptr();
364 }
365
366 StateMachine::State_ptr StateMachine::stateByIndex(unsigned int aIndex) const
367 {
368     if (aIndex >= d->_states.size()) {
369         throw sg_range_exception("invalid state index, out of bounds");
370     }
371     
372     return d->_states[aIndex];
373 }
374     
375 int StateMachine::indexOfState(State_ptr aState) const
376 {
377     StatePtrVec::const_iterator it = std::find(d->_states.begin(), d->_states.end(), aState);
378     if (it == d->_states.end()) {
379         return -1;
380     }
381     
382     return it - d->_states.begin();
383 }
384
385 StateMachine::State_ptr StateMachine::createState(const std::string& aName)
386 {
387     if (findStateByName(aName) != NULL) {
388         throw sg_range_exception("duplicate state name");
389     }
390     
391     State_ptr st = new State(aName);
392     addState(st);
393     return st;
394 }
395
396 StateMachine::Transition_ptr
397 StateMachine::createTransition(const std::string& aName, State_ptr aTarget)
398 {
399     Transition_ptr t = new Transition(aName, aTarget);
400     addTransition(t);
401     return t;
402 }
403
404 StateMachine* StateMachine::createFromPlist(SGPropertyNode* desc, SGPropertyNode* root)
405 {
406     StateMachine* sm = new StateMachine;
407     
408     
409     BOOST_FOREACH(SGPropertyNode* stateDesc, desc->getChildren("state")) {
410         std::string nm = stateDesc->getStringValue("name");
411         State_ptr st(new State(nm));
412         
413         readBindingList(stateDesc, "enter", root, st->d->_updateBindings);
414         readBindingList(stateDesc, "exit", root, st->d->_entryBindings);
415         readBindingList(stateDesc, "update", root, st->d->_exitBindings);
416         
417         sm->addState(st);
418     } // of states iteration
419     
420     BOOST_FOREACH(SGPropertyNode* tDesc, desc->getChildren("transition")) {
421         std::string nm = tDesc->getStringValue("name");
422         State_ptr target = sm->findStateByName(tDesc->getStringValue("target"));
423         
424         SGCondition* cond = sgReadCondition(root, tDesc->getChild("condition"));
425         
426         Transition_ptr t(new Transition(nm, target));
427         t->setTriggerCondition(cond);
428         
429         BOOST_FOREACH(SGPropertyNode* src, desc->getChildren("source")) {
430             State_ptr srcState = sm->findStateByName(src->getStringValue());
431             t->addSourceState(srcState);
432         }
433         
434         readBindingList(tDesc, "binding", root, t->d->_bindings);
435         
436         sm->addTransition(t);
437     } // of states iteration
438     
439     return sm;
440 }
441
442 void StateMachine::addState(State_ptr aState)
443 {
444     bool wasEmpty = d->_states.empty();
445     d->_states.push_back(aState);
446     if (wasEmpty) {
447         d->_currentState = aState;
448     }
449 }
450
451 void StateMachine::addTransition(Transition_ptr aTrans)
452 {
453     d->_transitions.push_back(aTrans);
454 }
455
456 } // of namespace simgear