1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*! \file ScXMLStateMachine.h */
34 #include <Inventor/scxml/ScXMLStateMachine.h>
35 
36 /*!
37   \class ScXMLStateMachine ScXMLStateMachine.h Inventor/scxml/ScXMLStateMachine.h
38   \brief Manager for processing events and setting states in SCXML structures.
39 
40   \since Coin 3.0
41   \ingroup scxml
42 */
43 
44 #ifdef _MSC_VER
45 #pragma warning(disable:4786) // symbol truncated
46 #endif // _MSC_VER
47 
48 #include <cassert>
49 #include <cstring>
50 #include <algorithm>
51 #include <list>
52 #include <map>
53 #include <vector>
54 
55 #include <boost/scoped_ptr.hpp>
56 
57 #include <Inventor/errors/SoDebugError.h>
58 
59 #include <Inventor/scxml/ScXML.h>
60 #include <Inventor/scxml/ScXMLEvent.h>
61 #include <Inventor/scxml/ScXMLDocument.h>
62 
63 #include <Inventor/scxml/ScXMLScxmlElt.h>
64 #include <Inventor/scxml/ScXMLInvokeElt.h>
65 #include <Inventor/scxml/ScXMLStateElt.h>
66 #include <Inventor/scxml/ScXMLParallelElt.h>
67 #include <Inventor/scxml/ScXMLInitialElt.h>
68 #include <Inventor/scxml/ScXMLFinalElt.h>
69 #include <Inventor/scxml/ScXMLOnEntryElt.h>
70 #include <Inventor/scxml/ScXMLOnExitElt.h>
71 #include <Inventor/scxml/ScXMLTransitionElt.h>
72 #include <Inventor/scxml/ScXMLAnchorElt.h>
73 #include <Inventor/scxml/ScXMLHistoryElt.h>
74 #include <Inventor/scxml/ScXMLLogElt.h>
75 #include <Inventor/scxml/ScXMLSendElt.h>
76 #include <Inventor/scxml/ScXMLMinimumEvaluator.h>
77 #include <Inventor/scxml/ScXMLCoinEvaluator.h>
78 #include "scxml/ScXMLP.h"
79 #include "misc/SbHash.h"
80 
81 // *************************************************************************
82 
83 /*!
84   \typedef void ScXMLStateMachineDeleteCB(void * userdata, ScXMLStateMachine * statemachine);
85 
86   This is the type definition for all callback functions to be invoked when a state machine
87   is deleted.
88 
89   \typedef void ScXMLParallelStateChangeCB(void * userdata,
90                                         ScXMLStateMachine * statemachine,
91                                         int numstates,
92                                         const char ** stateidentifiers,
93                                         SbBool enterstate,
94                                         SbBool success);
95 
96   This typedef is currently unused.
97 
98 */
99 
100 // *************************************************************************
101 
102 struct EventInfo {
103   const ScXMLEvent * eventptr;
104   SbBool deallocate;
105 };
106 
107 class ScXMLStateMachine::PImpl {
108 public:
PImpl(void)109   PImpl(void)
110     : pub(NULL),
111       active(FALSE), finished(FALSE),
112       name(SbName::empty()), sessionid(SbName::empty()),
113       loglevel(3),
114       description(NULL),
115       evaluator(NULL),
116       initializer(NULL)
117   {
118   }
119 
~PImpl(void)120   ~PImpl(void)
121   {
122     delete this->description;
123     this->description = NULL;
124   }
125 
126   ScXMLStateMachine * pub;
127 
128   SbBool active;
129   SbBool finished;
130 
131   SbName name;
132   SbName sessionid;
133   int loglevel;
134   ScXMLDocument * description;
135   ScXMLEvaluator * evaluator;
136 
137   SbList<const char *> modules;
138 
139   mutable SbString varstring;
140 
141   // delete callbacks:
142   typedef std::pair<ScXMLStateMachineDeleteCB *, void *> DeleteCBInfo;
143   typedef std::vector<DeleteCBInfo> DeleteCallbackList;
144   DeleteCallbackList deletecallbacklist;
145   void invokeDeleteCallbacks(void);
146 
147   // state change callbacks:
148   typedef std::pair<ScXMLStateChangeCB *, void *> StateChangeCBInfo;
149   typedef std::vector<StateChangeCBInfo> StateChangeCallbackList;
150   StateChangeCallbackList statechangecallbacklist;
151   void invokeStateChangeCallbacks(const char * identifier, SbBool enterstate);
152 
153   boost::scoped_ptr<ScXMLTransitionElt> initializer;
154 
155   std::vector<ScXMLElt *> activestatelist;
156 
157   typedef std::pair<ScXMLElt *, ScXMLTransitionElt *> StateTransition;
158   typedef std::vector<StateTransition> TransitionList;
159 
160   void findTransitions(TransitionList & transitions, ScXMLElt * stateobj, const ScXMLEvent * event);
161 
162   void exitState(ScXMLElt * object);
163   void enterState(ScXMLElt * object);
164 
165   static long nextsessionid;
166   static SbHash<const char *, ScXMLStateMachine *> * sessiondictionary;
167 }; // ScXMLStateMachine::PImpl
168 
169 SbHash<const char *, ScXMLStateMachine *> * ScXMLStateMachine::PImpl::sessiondictionary = NULL;
170 long ScXMLStateMachine::PImpl::nextsessionid = 0;
171 
172 // *************************************************************************
173 
174 #define PRIVATE(obj) ((obj)->pimpl)
175 #define PUBLIC(obj) ((obj)->pub)
176 
177 SCXML_OBJECT_SOURCE(ScXMLStateMachine);
178 
179 void
initClass(void)180 ScXMLStateMachine::initClass(void)
181 {
182   SCXML_OBJECT_INIT_CLASS(ScXMLStateMachine, ScXMLEventTarget, "ScXMLEventTarget");
183   ScXMLStateMachine::PImpl::nextsessionid = 1;
184   ScXMLStateMachine::PImpl::sessiondictionary =
185     new SbHash<const char *, ScXMLStateMachine *>;
186 }
187 
188 void
cleanClass(void)189 ScXMLStateMachine::cleanClass(void)
190 {
191   ScXMLStateMachine::PImpl::nextsessionid = 0;
192   delete ScXMLStateMachine::PImpl::sessiondictionary;
193   ScXMLStateMachine::PImpl::sessiondictionary = NULL;
194   ScXMLStateMachine::classTypeId = SoType::badType();
195 }
196 
197 ScXMLStateMachine *
getStateMachineForSessionId(const SbName & sessionid)198 ScXMLStateMachine::getStateMachineForSessionId(const SbName & sessionid)
199 {
200   if (sessionid == SbName::empty()) {
201     return NULL;
202   }
203   const char * id = sessionid.getString();
204   ScXMLStateMachine * statemachine = NULL;
205   if (!ScXMLStateMachine::PImpl::sessiondictionary->get(id, statemachine)) {
206     return NULL;
207   }
208   return statemachine;
209 }
210 
ScXMLStateMachine(void)211 ScXMLStateMachine::ScXMLStateMachine(void)
212 {
213   PRIVATE(this)->pub = this;
214   this->setEventTargetType("scxml");
215   ScXMLP::lock();
216   const long id = ScXMLStateMachine::PImpl::nextsessionid;
217   ScXMLStateMachine::PImpl::nextsessionid += 1;
218   ScXMLP::unlock();
219   char sessionidstr[32];
220   sprintf(sessionidstr, "x-coin-scxml-session%03ld", id);
221   this->setSessionId(SbName(sessionidstr));
222 }
223 
~ScXMLStateMachine(void)224 ScXMLStateMachine::~ScXMLStateMachine(void)
225 {
226   PRIVATE(this)->invokeDeleteCallbacks();
227   this->setSessionId(SbName::empty());
228   this->setEnabledModulesList(SbList<const char *>());
229 }
230 
231 // *************************************************************************
232 
233 void
setName(const SbName & nameobj)234 ScXMLStateMachine::setName(const SbName & nameobj)
235 {
236   PRIVATE(this)->name = nameobj;
237   this->setEventTargetName(nameobj.getString());
238 }
239 
240 const SbName &
getName(void) const241 ScXMLStateMachine::getName(void) const
242 {
243   return PRIVATE(this)->name;
244 }
245 
246 void
setDescription(ScXMLDocument * document)247 ScXMLStateMachine::setDescription(ScXMLDocument * document)
248 {
249   assert(!PRIVATE(this)->active);
250   PRIVATE(this)->description = document;
251   PRIVATE(this)->initializer.reset(NULL);
252   PRIVATE(this)->active = FALSE;
253   PRIVATE(this)->finished = FALSE;
254   PRIVATE(this)->activestatelist.clear();
255 
256   // set up the correct evalutor and identify the modules that are enabled
257   ScXMLElt * rootelt = document->getRoot();
258   if (rootelt->isOfType(ScXMLScxmlElt::getClassTypeId())) {
259     ScXMLScxmlElt * scxmlelt = static_cast<ScXMLScxmlElt *>(rootelt);
260     const char * profile = scxmlelt->getProfileAttribute();
261     SbName profilename = SbName::empty();
262     if (profile) {
263       profilename = profile;
264     }
265 
266    SoType evaluatortype = ScXML::getEvaluatorTypeForProfile(profilename);
267    if (evaluatortype != SoType::badType()) {
268      assert(evaluatortype.canCreateInstance());
269      ScXMLEvaluator * evaluator =
270        static_cast<ScXMLEvaluator *>(evaluatortype.createInstance());
271      evaluator->setStateMachine(this);
272      this->setEvaluator(evaluator);
273    }
274    else {
275      SoDebugError::post("ScXMLStateMachine::setDescription",
276                         "No available evaluator for profile '%s'.",
277                         profilename.getString());
278    }
279   }
280 }
281 
282 const ScXMLDocument *
getDescription(void) const283 ScXMLStateMachine::getDescription(void) const
284 {
285   return PRIVATE(this)->description;
286 }
287 
288 /*!
289   This sets the session identifier for the state machine.  Using this is
290   optional, since state machines are already assigned unique session ids at
291   construction-time.
292 */
293 void
setSessionId(const SbName & sessionidarg)294 ScXMLStateMachine::setSessionId(const SbName & sessionidarg)
295 {
296   if (PRIVATE(this)->sessionid != SbName::empty()) {
297     ScXMLStateMachine::PImpl::sessiondictionary->erase(PRIVATE(this)->sessionid.getString());
298     PRIVATE(this)->sessionid = SbName::empty();
299   }
300   if (sessionidarg != SbName::empty()) {
301     PRIVATE(this)->sessionid = sessionidarg;
302     ScXMLStateMachine::PImpl::sessiondictionary->put(PRIVATE(this)->sessionid.getString(), this);
303   }
304 }
305 
306 /*!
307   Returns the session identifier string for the state machine.
308 */
309 const SbName &
getSessionId(void) const310 ScXMLStateMachine::getSessionId(void) const
311 {
312   return PRIVATE(this)->sessionid;
313 }
314 
315 void
setLogLevel(int loglevel)316 ScXMLStateMachine::setLogLevel(int loglevel)
317 {
318   PRIVATE(this)->loglevel = loglevel;
319 }
320 
321 int
getLogLevel(void) const322 ScXMLStateMachine::getLogLevel(void) const
323 {
324   return PRIVATE(this)->loglevel;
325 }
326 
327 // *************************************************************************
328 
329 /*!
330   Fire up the engine.
331 */
332 void
initialize(void)333 ScXMLStateMachine::initialize(void)
334 {
335   assert(!PRIVATE(this)->active);
336   PRIVATE(this)->active = TRUE;
337   PRIVATE(this)->finished = FALSE;
338   PRIVATE(this)->activestatelist.clear();
339   this->processOneEvent(NULL); // process the 'initial' initializer
340   this->processEventQueue(); // process any pending events from the initial-processing
341 }
342 
343 // *************************************************************************
344 
345 /*!
346   Processes one event.
347   This is an internal inner event-loop utility function.
348 */
349 SbBool
processOneEvent(const ScXMLEvent * event)350 ScXMLStateMachine::processOneEvent(const ScXMLEvent * event)
351 {
352   // this function seriously needs more structuring
353   this->setCurrentEvent(event);
354 
355   if (0 /* debug */) {
356     if (event)
357       SoDebugError::postInfo("ScXMLStateMachine::processOneEvent",
358                              "event: %s",
359                              event->getEventName().getString());
360     else
361       SoDebugError::postInfo("ScXMLStateMachine::processOneEvent",
362                              "NULL event");
363   }
364 
365   if (0 /* debug */) {
366     std::vector<ScXMLElt *>::iterator it =
367       PRIVATE(this)->activestatelist.begin();
368     while (it != PRIVATE(this)->activestatelist.end()) {
369       SoDebugError::postInfo("ScXMLStateMachine::processOneEvent",
370                              "active state: %s", (*it)->getXMLAttribute("id"));
371       ++it;
372     }
373   }
374 
375   PImpl::TransitionList transitions;
376   if (PRIVATE(this)->activestatelist.size() == 0) {
377     if (PRIVATE(this)->initializer.get() == NULL) {
378       PRIVATE(this)->initializer.reset(new ScXMLTransitionElt);
379       // FIXME
380       if (PRIVATE(this)->description->getRoot()->getInitial()) {
381       } else {
382         PRIVATE(this)->initializer->setTargetAttribute(PRIVATE(this)->description->getRoot()->getInitialAttribute());
383       }
384     }
385     transitions.push_back(PImpl::StateTransition(static_cast<ScXMLElt*>(NULL), PRIVATE(this)->initializer.get()));
386   } else {
387     for (int c = 0; c < static_cast<int>(PRIVATE(this)->activestatelist.size()); ++c) {
388       // containers are also active states and must be checked
389       ScXMLElt * stateobj = PRIVATE(this)->activestatelist.at(c);
390       while (stateobj != NULL) {
391         PRIVATE(this)->findTransitions(transitions, stateobj, event);
392         stateobj = stateobj->getContainer();
393       }
394     }
395   }
396 
397   // no transitions means no changes, just return
398   if (transitions.size() == 0) {
399     if (this->getEvaluator())
400       this->getEvaluator()->clearTemporaryVariables();
401     this->setCurrentEvent(NULL);
402     return FALSE;
403   }
404 
405   // we handle all targetless transitions first
406   {
407     PImpl::TransitionList::iterator transit = transitions.begin();
408     while (transit != transitions.end()) {
409       if (transit->second->isTargetLess()) {
410         transit->second->execute(this);
411       }
412       ++transit;
413     }
414   }
415 
416   // handle self-targeting transitions next (not sure this is the right
417   // place, but it's not improbable either)...
418   {
419     PImpl::TransitionList::iterator transit = transitions.begin();
420     while (transit != transitions.end()) {
421       if (transit->second->isSelfReferencing()) {
422         ScXMLElt * containerobj = transit->second->getContainer();
423         /*ScXMLAbstractStateElt * targetobj = */PRIVATE(this)->description->getStateById(transit->second->getTargetAttribute());
424 
425         if (containerobj->isOfType(ScXMLStateElt::getClassTypeId())) {
426           ScXMLStateElt * state = static_cast<ScXMLStateElt *>(containerobj);
427           PRIVATE(this)->exitState(state);
428           transit->second->execute(this);
429           PRIVATE(this)->enterState(state);
430         } else {
431           transit->second->execute(this);
432         }
433       }
434       ++transit;
435     }
436   }
437 
438   std::vector<ScXMLElt *> newstateslist;
439 
440   // handle those with other targets next
441   PImpl::TransitionList::iterator transit = transitions.begin();
442   while (transit != transitions.end()) {
443     if (transit->second->isTargetLess() ||
444         transit->second->isSelfReferencing()) {
445       ++transit;
446       continue;
447     }
448 
449     const char * targetid = transit->second->getTargetAttribute();
450     ScXMLElt * targetstate = PRIVATE(this)->description->getStateById(targetid);
451     if (!targetstate) {
452       SoDebugError::post("ScXMLStateMachine::processOneEvent",
453                          "transition to unknown state '%s' failed.", targetid);
454       ++transit;
455       continue;
456     }
457 
458     std::vector<ScXMLElt *> sourcestates;
459 
460     ScXMLElt * sourcestate = transit->first;
461     if (sourcestate != NULL) { // ignore sourcestate NULL (initializer)
462       // find all activestate object contained within source state
463       std::vector<ScXMLElt *>::iterator activeit =
464         PRIVATE(this)->activestatelist.begin();
465       while (activeit != PRIVATE(this)->activestatelist.end()) {
466         if ((*activeit)->isContainedIn(sourcestate)) {
467           ScXMLElt * active = *activeit;
468           sourcestates.push_back(active); // remember, to remove from activelist
469           while (active != sourcestate) {
470             //SoDebugError::post("process",
471             //       "found activestate as substate of transition source");
472 
473             PRIVATE(this)->exitState(active); // exit substates of transition point
474             active = active->getContainer();
475             assert(active);
476           }
477         }
478         ++activeit;
479       }
480 
481       while (!targetstate->isContainedIn(sourcestate)) {
482         //SoDebugError::postInfo("process", "going up to find common ancestor");
483         PRIVATE(this)->exitState(sourcestate);
484         sourcestate = sourcestate->getContainer();
485       }
486     }
487 
488     // executable content in the transition
489     //SoDebugError::postInfo("process", "executing transition code");
490     transit->second->execute(this);
491 
492     {
493       std::vector<ScXMLElt *> path;
494       //SoDebugError::postInfo("process", "finding target-path from sourcestate %p",
495       //                       sourcestate);
496       while (sourcestate != targetstate) {
497         path.push_back(targetstate);
498         targetstate = targetstate->getContainer();
499       }
500       targetstate = PRIVATE(this)->description->getStateById(targetid); // restore
501 
502       //SoDebugError::postInfo("process", "reversing downward path");
503       std::reverse(path.begin(), path.end());
504 
505       std::vector<ScXMLElt *>::iterator pathit = path.begin();
506       while (pathit != path.end()) {
507         // SoDebugError::postInfo("process", "entering down towards target");
508         PRIVATE(this)->enterState(*pathit);
509         ++pathit;
510       }
511     }
512 
513     //SoDebugError::post("process", "list of source states to remove - %d",
514     //                   sourcestates.size());
515     // remove source states form activestates
516     std::vector<ScXMLElt *>::iterator it = sourcestates.begin();
517     while (it != sourcestates.end()) {
518       std::vector<ScXMLElt *>::iterator findit =
519         std::find(PRIVATE(this)->activestatelist.begin(),
520                   PRIVATE(this)->activestatelist.end(), *it);
521       if (findit != PRIVATE(this)->activestatelist.end()) {
522         //SoDebugError::post("process", "erasing old activestate");
523         PRIVATE(this)->activestatelist.erase(findit);
524       } else {
525         SoDebugError::post("ScXMLStateMachine::processOneEvent",
526                            "source state not found in activestate list");
527       }
528       ++it;
529     }
530 
531     // add targetstate to active states
532     if (std::find(PRIVATE(this)->activestatelist.begin(), PRIVATE(this)->activestatelist.end(), targetstate) == PRIVATE(this)->activestatelist.end()) {
533       newstateslist.push_back(targetstate);
534     }
535 
536     ++transit;
537   }
538 
539   //SoDebugError::postInfo("process", "new states to potentially append to activestates: %d",
540   //                       newstateslist.size());
541 
542 
543   // inspect target states for substates + <initial> children
544   std::vector<ScXMLElt *>::iterator appendit = newstateslist.begin();
545   while (appendit != newstateslist.end()) {
546     SbBool pushedsubstate = FALSE;
547     ScXMLElt * newstate = *appendit;
548 
549     SbBool settled = FALSE;
550     while (!settled) {
551       settled = TRUE;
552       if (newstate->isOfType(ScXMLStateElt::getClassTypeId())) {
553         ScXMLStateElt * state = static_cast<ScXMLStateElt *>(newstate);
554         if (state->getNumStates() > 0 || state->getNumParallels() > 0) {
555           do {
556             const ScXMLInitialElt * initial = state->getInitial();
557             if (!initial) {
558               SoDebugError::post("ScXMLStateMachine::processOneEvent",
559                                  "state '%s' has substates but no <initial>.",
560                                  state->getIdAttribute());
561               break;
562             }
563             ScXMLTransitionElt * transition = initial->getTransition();
564             if (!transition) {
565               SoDebugError::post("ScXMLStateMachine::processOneEvent",
566                                  "state '%s' has <initial> without a transition.",
567                                  state->getIdAttribute());
568               break;
569             }
570             const char * targetid = transition->getTargetAttribute();
571             if (!targetid) {
572               SoDebugError::post("ScXMLStateMachine::processOneEvent",
573                                  "state '%s' has <initial> with a targetless transition.",
574                                  state->getIdAttribute());
575               break;
576             }
577             ScXMLElt * targetobj = PRIVATE(this)->description->getStateById(targetid);
578             if (!targetobj) {
579               SoDebugError::post("ScXMLStateMachine::processOneEvent",
580                                  "could not find target of state \"%s\"'s <initial> transition.",
581                                  state->getIdAttribute());
582               break;
583             }
584 
585             if (targetobj->getContainer() != state) {
586               SoDebugError::post("ScXMLStateMachine::processOneEvent",
587                                  "target of state \"%s\"'s <initial> transition is not an immediate child of the state",
588                                  state->getIdAttribute());
589               break;
590             }
591 
592             // perform executable code
593             transition->execute(this);
594 
595             PRIVATE(this)->enterState(targetobj);
596 
597             newstate = targetobj;
598             settled = FALSE; // need to loop over on new state one more time
599           } while ( FALSE );
600 
601         } else {
602           // no substates in this state - can be marked as the deepest active state
603           if (state->getInitial()) { // just checking
604             SoDebugError::post("ScXMLStateMachine::processOneEvent",
605                                "state '%s' has <initial> but no sub-states.",
606                                state->getIdAttribute());
607           }
608 
609           PRIVATE(this)->activestatelist.push_back(state);
610           pushedsubstate = TRUE; // need to avoid adding parent state before doing outer loop
611           ++appendit;
612         }
613       } else {
614         // non-ScXMLStateElt object (ScXMLFinalElt for instance)
615         if (newstate != *appendit) {
616           PRIVATE(this)->activestatelist.push_back(newstate);
617           pushedsubstate = TRUE; // need to avoid adding parent state before doing outer loop
618           ++appendit;
619         }
620       }
621     }
622     if (!pushedsubstate) {
623       PRIVATE(this)->activestatelist.push_back(*appendit);
624       ++appendit;
625     }
626   }
627 
628   // if all active states are <final> states of the root scxml element,
629   // we should set 'finished' to true and stop/hinder event processing
630 
631   if (this->getEvaluator())
632     this->getEvaluator()->clearTemporaryVariables();
633   this->setCurrentEvent(NULL);
634   return TRUE; // transitions have been taken
635 }
636 
637 // *************************************************************************
638 
639 /*!
640   Returns whether the state machine is active or not.
641 */
642 SbBool
isActive(void) const643 ScXMLStateMachine::isActive(void) const
644 {
645   return PRIVATE(this)->active;
646 }
647 
648 /*!
649   Returns whether the state machine has run to completion or not.
650 */
651 SbBool
isFinished(void) const652 ScXMLStateMachine::isFinished(void) const
653 {
654   return PRIVATE(this)->finished;
655 }
656 
657 // *************************************************************************
658 
659 
660 /*!
661   This method returns the current event during event processing, and \c NULL
662   when not processing events.
663 
664   Event processing is in special cases done with \c NULL as the current event,
665   as for instance during state machine initialization.
666 */
667 
668 // *************************************************************************
669 
670 /*!
671   Returns the number of active states in the state machine.  This number
672   should currently be 1, but in the future, when &lt;parallel&gt; is implemented,
673   it can be more.
674 */
675 int
getNumActiveStates(void) const676 ScXMLStateMachine::getNumActiveStates(void) const
677 {
678   return static_cast<int>(PRIVATE(this)->activestatelist.size());
679 }
680 
681 /*!
682   Returns the Nth active state.
683 */
684 const ScXMLElt *
getActiveState(int idx) const685 ScXMLStateMachine::getActiveState(int idx) const
686 {
687   assert(idx >= 0 && idx < static_cast<int>(PRIVATE(this)->activestatelist.size()));
688   return PRIVATE(this)->activestatelist.at(idx);
689 }
690 
691 // *************************************************************************
692 
693 /*!
694   Registers a callback to be called when the state machine object is being
695   deleted.
696 */
697 void
addDeleteCallback(ScXMLStateMachineDeleteCB * cb,void * userdata)698 ScXMLStateMachine::addDeleteCallback(ScXMLStateMachineDeleteCB * cb, void * userdata)
699 {
700   PRIVATE(this)->deletecallbacklist.push_back(PImpl::DeleteCBInfo(cb, userdata));
701 }
702 
703 /*!
704   Unregisters a callback to be called when the state machine object is being
705   deleted.
706 */
707 void
removeDeleteCallback(ScXMLStateMachineDeleteCB * cb,void * userdata)708 ScXMLStateMachine::removeDeleteCallback(ScXMLStateMachineDeleteCB * cb, void * userdata)
709 {
710   PImpl::DeleteCallbackList::iterator it =
711     std::find(PRIVATE(this)->deletecallbacklist.begin(),
712               PRIVATE(this)->deletecallbacklist.end(),
713               PImpl::DeleteCBInfo(cb, userdata));
714   if (it != PRIVATE(this)->deletecallbacklist.end()) {
715     PRIVATE(this)->deletecallbacklist.erase(it);
716   }
717 }
718 
719 /*
720   Invoke all the delete callbacks.
721 */
722 
723 void
invokeDeleteCallbacks(void)724 ScXMLStateMachine::PImpl::invokeDeleteCallbacks(void)
725 {
726   DeleteCallbackList::const_iterator it = this->deletecallbacklist.begin();
727   while (it != this->deletecallbacklist.end()) {
728     (it->first)(it->second, PUBLIC(this));
729     ++it;
730   }
731 }
732 
733 // *************************************************************************
734 
735 /*!
736   \var ScXMLStateChangeCB
737 
738   This callback type is for notifying listeners on when the state machine
739   enters and exits states that are tagged as "tasks" for logging purposes.
740   This is what the Boolean "task" attribute in the state element sets up.
741 
742   The \a success argument is currently unsupported (will always be TRUE),
743   but has been preemptively added to avoid a signature change later.
744 
745   \sa addStateChangeCallback
746 */
747 
748 /*!
749   Registers a callback to be called when the state machine exits or enters
750   a state.
751 */
752 void
addStateChangeCallback(ScXMLStateChangeCB * callback,void * userdata)753 ScXMLStateMachine::addStateChangeCallback(ScXMLStateChangeCB * callback, void * userdata)
754 {
755   PRIVATE(this)->statechangecallbacklist.push_back(PImpl::StateChangeCBInfo(callback, userdata));
756 }
757 
758 /*!
759   Unregisters a callback to be called when the state machine exits or enters
760   a state.
761 */
762 void
removeStateChangeCallback(ScXMLStateChangeCB * callback,void * userdata)763 ScXMLStateMachine::removeStateChangeCallback(ScXMLStateChangeCB * callback, void * userdata)
764 {
765   PImpl::StateChangeCallbackList::iterator findit =
766     std::find(PRIVATE(this)->statechangecallbacklist.begin(),
767               PRIVATE(this)->statechangecallbacklist.end(),
768               PImpl::StateChangeCBInfo(callback, userdata));
769   if (findit != PRIVATE(this)->statechangecallbacklist.end()) {
770     PRIVATE(this)->statechangecallbacklist.erase(findit);
771   }
772 }
773 
774 /*
775   Invoke all the state change callbacks.
776 */
777 void
invokeStateChangeCallbacks(const char * identifier,SbBool enterstate)778 ScXMLStateMachine::PImpl::invokeStateChangeCallbacks(const char * identifier, SbBool enterstate)
779 {
780   StateChangeCallbackList::const_iterator it =
781     this->statechangecallbacklist.begin();
782   while (it != this->statechangecallbacklist.end()) {
783     (it->first)(it->second, PUBLIC(this), identifier, enterstate, TRUE);
784     ++it;
785   }
786 }
787 
788 // *************************************************************************
789 
790 void
setVariable(const char * name,const char * COIN_UNUSED_ARG (value))791 ScXMLStateMachine::setVariable(const char * name, const char * COIN_UNUSED_ARG(value))
792 {
793   assert(name);
794   if (name[0] == '_') { // reserved system variables
795     // core reserved names
796     if (strcmp(name, "_name") == 0) {
797       SoDebugError::post("ScXMLStateMachine::setVariable",
798                          "Name '%s' is a reserved system variable.", name);
799     }
800     else if (strcmp(name, "_sessionID") == 0) {
801       SoDebugError::post("ScXMLStateMachine::setVariable",
802                          "Name '%s' is a reserved system variable.", name);
803     }
804     else if (strcmp(name, "_event") == 0 ||
805              strncmp(name, "_event.", 7) == 0) {
806       SoDebugError::post("ScXMLStateMachine::setVariable",
807                          "Name '%s' is a reserved system variable.", name);
808     }
809     // data module
810     else if (strcmp(name, "_data") == 0) {
811       SoDebugError::post("ScXMLStateMachine::setVariable",
812                          "Name '%s' is a reserved system variable.", name);
813     }
814     // fallthrough
815     else {
816       SoDebugError::post("ScXMLStateMachine::setVariable",
817                          "Name '%s' violates the reserved '_'-prefix "
818                          "namespace for system variables.", name);
819     }
820   }
821 
822   else if (strncmp(name, "coin:", 5) == 0) {
823     // coin profile
824     if (strcmp(name, "coin:root") == 0) {
825       SoDebugError::post("ScXMLStateMachine::setVariable",
826                          "Name '%s' is a reserved system variable.", name);
827     }
828     else if (strcmp(name, "coin:camera") == 0) {
829       SoDebugError::post("ScXMLStateMachine::setVariable",
830                          "Name '%s' is a reserved system variable.", name);
831     }
832     // fallthrough
833     else {
834       SoDebugError::post("ScXMLStateMachine::setVariable",
835                          "Name '%s' violates the reserved 'coin:'-prefix "
836                          "namespace for system variables.", name);
837     }
838   }
839   else {
840     // FIXME
841   }
842 }
843 
844 const char *
getVariable(const char * name) const845 ScXMLStateMachine::getVariable(const char * name) const
846 {
847   if (strcmp(name, "_sessionid") == 0) {
848     PRIVATE(this)->varstring.sprintf("'%s'", PRIVATE(this)->sessionid.getString());
849     return PRIVATE(this)->varstring.getString();
850     // return PRIVATE(this)->sessionid.getString();
851   }
852   if (strcmp(name, "_name") == 0) {
853     PRIVATE(this)->varstring.sprintf("'%s'", PRIVATE(this)->name.getString());
854     return PRIVATE(this)->varstring.getString();
855     // return PRIVATE(this)->name.getString();
856   }
857   return NULL;
858 }
859 
860 // *************************************************************************
861 
862 void
findTransitions(TransitionList & transitions,ScXMLElt * stateobj,const ScXMLEvent * event)863 ScXMLStateMachine::PImpl::findTransitions(TransitionList & transitions, ScXMLElt * stateobj, const ScXMLEvent * event)
864 {
865   assert(stateobj);
866 
867   if (stateobj->isOfType(ScXMLHistoryElt::getClassTypeId())) {
868     ScXMLHistoryElt * history = static_cast<ScXMLHistoryElt *>(stateobj);
869     if (history->getTransition() &&
870         history->getTransition()->isEventMatch(event) &&
871         history->getTransition()->evaluateCondition(PUBLIC(this))) {
872       StateTransition transition(stateobj, history->getTransition());
873       TransitionList::iterator findit =
874         std::find(transitions.begin(), transitions.end(), transition);
875       if (findit == transitions.end()) {
876         transitions.push_back(transition);
877       }
878     }
879   }
880   else if (stateobj->isOfType(ScXMLInitialElt::getClassTypeId())) {
881     ScXMLInitialElt * initial = static_cast<ScXMLInitialElt *>(stateobj);
882     if (initial->getTransition() &&
883         initial->getTransition()->isEventMatch(event) &&
884         initial->getTransition()->evaluateCondition(PUBLIC(this))) {
885       StateTransition transition(stateobj, initial->getTransition());
886       TransitionList::iterator findit =
887         std::find(transitions.begin(), transitions.end(), transition);
888       if (findit == transitions.end()) {
889         transitions.push_back(transition);
890       }
891     }
892   }
893   else if (stateobj->isOfType(ScXMLStateElt::getClassTypeId())) {
894     ScXMLStateElt * state = static_cast<ScXMLStateElt *>(stateobj);
895     for (int j = 0; j < state->getNumTransitions(); ++j) {
896       if (state->getTransition(j)->isEventMatch(event) &&
897           state->getTransition(j)->evaluateCondition(PUBLIC(this))) {
898         StateTransition transition(stateobj, state->getTransition(j));
899         TransitionList::iterator findit =
900           std::find(transitions.begin(), transitions.end(), transition);
901         if (findit == transitions.end()) {
902           transitions.push_back(transition);
903         }
904       }
905     }
906   }
907 }
908 
909 // *************************************************************************
910 
911 void
exitState(ScXMLElt * object)912 ScXMLStateMachine::PImpl::exitState(ScXMLElt * object)
913 {
914   assert(object);
915   if (object->isOfType(ScXMLStateElt::getClassTypeId())) {
916     ScXMLStateElt * state = static_cast<ScXMLStateElt *>(object);
917     const char * id = state->getIdAttribute();
918     this->invokeStateChangeCallbacks(id, FALSE);
919     ScXMLOnExitElt * onexit = state->getOnExit();
920     if (onexit) {
921       onexit->execute(PUBLIC(this));
922     }
923   }
924 }
925 
926 void
enterState(ScXMLElt * object)927 ScXMLStateMachine::PImpl::enterState(ScXMLElt * object)
928 {
929   assert(object);
930 
931   if (object->isOfType(ScXMLFinalElt::getClassTypeId())) {
932     // When entering a <final>, ParentID.done should be posted
933     ScXMLFinalElt * final = static_cast<ScXMLFinalElt *>(object);
934     const ScXMLElt * container = final->getContainer();
935     assert(container);
936     const char * id = container->getXMLAttribute("id");
937     if (!id || strlen(id) == 0) {
938       if (container->isOfType(ScXMLDocument::getClassTypeId())) {
939         // there is not ParentID to post a ParentID.done event in
940         // this case. study SCXML state to see what to do?
941         this->finished = TRUE;
942         this->active = FALSE;
943       } else {
944         SoDebugError::post("ScXMLStateMachine::PImpl::enterState",
945                            "<final> container has no id - can't post done-event");
946       }
947       return;
948     }
949     SbString eventstr;
950     eventstr.sprintf("%s.done", id);
951     PUBLIC(this)->queueInternalEvent(eventstr.getString());
952   }
953   else if (object->isOfType(ScXMLStateElt::getClassTypeId())) {
954     ScXMLStateElt * state = static_cast<ScXMLStateElt *>(object);
955     const char * id = state->getIdAttribute();
956     this->invokeStateChangeCallbacks(id, TRUE);
957     ScXMLOnEntryElt * onentry = state->getOnEntry();
958     if (onentry) {
959       onentry->execute(PUBLIC(this));
960     }
961   }
962 }
963 
964 void
setEvaluator(ScXMLEvaluator * evaluator)965 ScXMLStateMachine::setEvaluator(ScXMLEvaluator * evaluator)
966 {
967   PRIVATE(this)->evaluator = evaluator;
968 }
969 
970 ScXMLEvaluator *
getEvaluator(void) const971 ScXMLStateMachine::getEvaluator(void) const
972 {
973   return PRIVATE(this)->evaluator;
974 }
975 
976 SbBool
isModuleEnabled(const char * modulename) const977 ScXMLStateMachine::isModuleEnabled(const char * modulename) const
978 {
979   for (int i = 0; i < PRIVATE(this)->modules.getLength(); ++i) {
980     if (strcmp(modulename, PRIVATE(this)->modules[i]) == 0) {
981       return TRUE;
982     }
983   }
984   return FALSE;
985 }
986 
987 int
getNumEnabledModules(void) const988 ScXMLStateMachine::getNumEnabledModules(void) const
989 {
990   return PRIVATE(this)->modules.getLength();
991 }
992 
993 const char *
getEnabledModuleName(int idx) const994 ScXMLStateMachine::getEnabledModuleName(int idx) const
995 {
996   assert(idx >= 0 && idx < PRIVATE(this)->modules.getLength());
997   return PRIVATE(this)->modules[idx];
998 }
999 
1000 void
setEnabledModulesList(const SbList<const char * > & modulenames)1001 ScXMLStateMachine::setEnabledModulesList(const SbList<const char *> & modulenames)
1002 {
1003   int i;
1004   for (i = 0; i < PRIVATE(this)->modules.getLength(); ++i) {
1005     delete [] PRIVATE(this)->modules[i];
1006   }
1007   PRIVATE(this)->modules.truncate(0);
1008   for (i = 0; i < modulenames.getLength(); ++i) {
1009     char * dup = new char [ strlen(modulenames[i]) + 1 ];
1010     strcpy(dup, modulenames[i]);
1011     PRIVATE(this)->modules.append(dup);
1012   }
1013 }
1014 
1015 #undef PUBLIC
1016 #undef PRIVATE
1017