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