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 <parallel> 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