1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qscxmlstatemachine_p.h"
5#include "qscxmlexecutablecontent_p.h"
6#include "qscxmlevent_p.h"
7#include "qscxmlinvokableservice.h"
8#include "qscxmldatamodel_p.h"
9
10#include <qfile.h>
11#include <qhash.h>
12#include <qloggingcategory.h>
13#include <qstring.h>
14#include <qtimer.h>
15#include <qthread.h>
16
17#include <functional>
18
19QT_BEGIN_NAMESPACE
20
21Q_LOGGING_CATEGORY(qscxmlLog, "qt.scxml.statemachine")
22Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine")
23
24/*!
25 * \class QScxmlStateMachine
26 * \brief The QScxmlStateMachine class provides an interface to the state machines
27 * created from SCXML files.
28 * \since 5.7
29 * \inmodule QtScxml
30 *
31 * QScxmlStateMachine is an implementation of the
32 * \l{SCXML Specification}{State Chart XML (SCXML)}.
33 *
34 * All states that are defined in the SCXML file
35 * are accessible as properties of QScxmlStateMachine.
36 * These properties are boolean values and indicate
37 * whether the state is active or inactive.
38 *
39 * \note The QScxmlStateMachine needs a QEventLoop to work correctly. The event loop is used to
40 * implement the \c delay attribute for events and to schedule the processing of a state
41 * machine when events are received from nested (or parent) state machines.
42 */
43
44/*!
45 \qmltype ScxmlStateMachine
46 \instantiates QScxmlStateMachine
47 \inqmlmodule QtScxml
48 \since 5.7
49
50 \brief Provides an interface to the state machines created from SCXML files.
51
52 The ScxmlStateMachine type is an implementation of the
53 \l{SCXML Specification}{State Chart XML (SCXML)}.
54
55 All states that are defined in the SCXML file are accessible as properties
56 of this type. These properties are boolean values and indicate whether the
57 state is active or inactive.
58*/
59
60/*!
61 \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToEvent(
62 const QString &scxmlEventSpec,
63 const QObject *context,
64 Functor &&functor,
65 Qt::ConnectionType type)
66 \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToEvent(
67 const QString &scxmlEventSpec,
68 Functor &&functor,
69 Qt::ConnectionType type)
70
71 Creates a connection of the given \a type from the event specified by
72 \a scxmlEventSpec to \a functor, which can be a functor or a member function of
73 the optional \a context object.
74
75 The receiver's \a functor must take a QScxmlEvent as a parameter.
76
77 In contrast to event specifications in SCXML documents, spaces are not
78 allowed in the \a scxmlEventSpec here. In order to connect to multiple
79 events with different prefixes, connectToEvent() has to be called multiple
80 times.
81
82 Returns a handle to the connection, which can be used later to disconnect.
83*/
84
85/*!
86 \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToState(
87 const QString &scxmlStateName,
88 const QObject *context,
89 Functor &&functor,
90 Qt::ConnectionType type)
91 \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToState(
92 const QString &scxmlStateName,
93 Functor &&functor,
94 Qt::ConnectionType type)
95
96 Creates a connection of the given \a type from the state specified by
97 \a scxmlStateName to \a functor, which can be a functor or a member function of
98 the optional \a context object.
99
100 The receiver's \a functor must take a boolean argument that indicates
101 whether the state connected became active or inactive.
102
103 Returns a handle to the connection, which can be used later to disconnect.
104*/
105
106/*!
107 \fn [onentry] std::function<void(bool)> QScxmlStateMachine::onEntry(
108 const QObject *receiver, const char *method)
109
110 Returns a functor that accepts a boolean argument and calls the given
111 \a method on \a receiver using QMetaObject::invokeMethod() if that argument
112 is \c true and \a receiver has not been deleted, yet.
113
114 The given \a method must not accept any arguments. \a method is the plain
115 method name, not enclosed in \c SIGNAL() or \c SLOT().
116
117 This is useful to wrap handlers for connectToState() that should only
118 be executed when the state is entered.
119 */
120
121/*!
122 \fn [onexit] std::function<void(bool)> QScxmlStateMachine::onExit(
123 const QObject *receiver, const char *method)
124
125 Returns a functor that accepts a boolean argument and calls the given
126 \a method on \a receiver using QMetaObject::invokeMethod() if that argument
127 is \c false and \a receiver has not been deleted, yet.
128
129 The given \a method must not accept any arguments. \a method is the plain
130 method name, not enclosed in SIGNAL(...) or SLOT(...).
131
132 This is useful to wrap handlers for connectToState() that should only
133 be executed when the state is left.
134 */
135
136/*!
137 \fn [onentry-functor] template<typename Functor> std::function<void(bool)> QScxmlStateMachine::onEntry(
138 Functor functor)
139
140 Returns a functor that accepts a boolean argument and calls the given
141 \a functor if that argument is \c true. The given \a functor must not
142 accept any arguments.
143
144 This is useful to wrap handlers for connectToState() that should only
145 be executed when the state is entered.
146 */
147
148/*!
149 \fn [onexit-functor] template<typename Functor> std::function<void(bool)> QScxmlStateMachine::onExit(Functor functor)
150
151 Returns a functor that accepts a boolean argument and calls the given
152 \a functor if that argument is \c false. The given \a functor must not
153 accept any arguments.
154
155 This is useful to wrap handlers for connectToState() that should only
156 be executed when the state is left.
157 */
158
159/*!
160 \fn [onentry-template] template<typename PointerToMemberFunction> std::function<void(bool)> QScxmlStateMachine::onEntry(
161 const typename QtPrivate::FunctionPointer<PointerToMemberFunction>::Object *receiver,
162 PointerToMemberFunction method)
163
164 Returns a functor that accepts a boolean argument and calls the given
165 \a method on \a receiver if that argument is \c true and the \a receiver
166 has not been deleted, yet. The given \a method must not accept any
167 arguments.
168
169 This is useful to wrap handlers for connectToState() that should only
170 be executed when the state is entered.
171 */
172
173/*!
174 \fn [onexit-template] template<typename PointerToMemberFunction> std::function<void(bool)> QScxmlStateMachine::onExit(
175 const typename QtPrivate::FunctionPointer<PointerToMemberFunction>::Object *receiver,
176 PointerToMemberFunction method)
177
178 Returns a functor that accepts a boolean argument and calls the given
179 \a method on \a receiver if that argument is \c false and the \a receiver
180 has not been deleted, yet. The given \a method must not accept any
181 arguments.
182
183 This is useful to wrap handlers for connectToState() that should only
184 be executed when the state is left.
185 */
186
187namespace QScxmlInternal {
188
189static int signalIndex(const QMetaObject *meta, const QByteArray &signalName)
190{
191 Q_ASSERT(meta);
192
193 int signalIndex = meta->indexOfSignal(signal: signalName.constData());
194
195 // If signal doesn't exist, return negative value
196 if (signalIndex < 0)
197 return signalIndex;
198
199 // signal belongs to class whose meta object was passed, not some derived class.
200 Q_ASSERT(meta->methodOffset() <= signalIndex);
201
202 // Duplicate of computeOffsets in qobject.cpp
203 const QMetaObject *m = meta->d.superdata;
204 while (m) {
205 const QMetaObjectPrivate *d = QMetaObjectPrivate::get(metaobject: m);
206 signalIndex = signalIndex - d->methodCount + d->signalCount;
207 m = m->d.superdata;
208 }
209
210 // Asserting about the signal not being cloned would be nice, too, but not practical.
211
212 return signalIndex;
213}
214
215void EventLoopHook::queueProcessEvents()
216{
217 if (smp->m_isProcessingEvents)
218 return;
219
220 QMetaObject::invokeMethod(obj: this, member: "doProcessEvents", c: Qt::QueuedConnection);
221}
222
223void EventLoopHook::doProcessEvents()
224{
225 smp->processEvents();
226}
227
228void EventLoopHook::timerEvent(QTimerEvent *timerEvent)
229{
230 const int timerId = timerEvent->timerId();
231 for (auto it = smp->m_delayedEvents.begin(), eit = smp->m_delayedEvents.end(); it != eit; ++it) {
232 if (it->first == timerId) {
233 QScxmlEvent *scxmlEvent = it->second;
234 smp->m_delayedEvents.erase(position: it);
235 smp->routeEvent(event: scxmlEvent);
236 killTimer(id: timerId);
237 return;
238 }
239 }
240}
241
242void ScxmlEventRouter::route(const QStringList &segments, QScxmlEvent *event)
243{
244 emit eventOccurred(event: *event);
245 if (!segments.isEmpty()) {
246 auto it = children.find(key: segments.first());
247 if (it != children.end())
248 it.value()->route(segments: segments.mid(pos: 1), event);
249 }
250}
251
252static QString nextSegment(const QStringList &segments)
253{
254 if (segments.isEmpty())
255 return QString();
256
257 const QString &segment = segments.first();
258 return segment == QLatin1String("*") ? QString() : segment;
259}
260
261ScxmlEventRouter *ScxmlEventRouter::child(const QString &segment)
262{
263 ScxmlEventRouter *&child = children[segment];
264 if (child == nullptr)
265 child = new ScxmlEventRouter(this);
266 return child;
267}
268
269void ScxmlEventRouter::disconnectNotify(const QMetaMethod &signal)
270{
271 Q_UNUSED(signal);
272
273 // Defer the actual work, as this may be called from a destructor, or the signal may not
274 // actually be disconnected, yet.
275 QTimer::singleShot(interval: 0, receiver: this, slot: [this] {
276 if (!children.isEmpty() || receivers(SIGNAL(eventOccurred(QScxmlEvent))) > 0)
277 return;
278
279 ScxmlEventRouter *parentRouter = qobject_cast<ScxmlEventRouter *>(object: parent());
280 if (!parentRouter) // root node
281 return;
282
283 QHash<QString, ScxmlEventRouter *>::Iterator it = parentRouter->children.begin(),
284 end = parentRouter->children.end();
285 for (; it != end; ++it) {
286 if (it.value() == this) {
287 parentRouter->children.erase(it);
288 parentRouter->disconnectNotify(signal: QMetaMethod());
289 break;
290 }
291 }
292
293 deleteLater(); // The parent might delete itself, triggering QObject delete cascades.
294 });
295}
296
297QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments,
298 const QObject *receiver,
299 const char *method,
300 Qt::ConnectionType type)
301{
302 QString segment = nextSegment(segments);
303 return segment.isEmpty() ?
304 connect(sender: this, SIGNAL(eventOccurred(QScxmlEvent)), receiver, member: method, type) :
305 child(segment)->connectToEvent(segments: segments.mid(pos: 1), receiver, method, type);
306}
307
308QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments,
309 const QObject *receiver, void **slot,
310 QtPrivate::QSlotObjectBase *method,
311 Qt::ConnectionType type)
312{
313 QString segment = nextSegment(segments);
314 if (segment.isEmpty()) {
315 const int *types = nullptr;
316 if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
317 types = QtPrivate::ConnectionTypes<QtPrivate::List<QScxmlEvent> >::types();
318
319 const QMetaObject *meta = metaObject();
320 static const int eventOccurredIndex = signalIndex(meta, signalName: "eventOccurred(QScxmlEvent)");
321 return QObjectPrivate::connectImpl(sender: this, signal_index: eventOccurredIndex, receiver, slot, slotObj: method, type,
322 types, senderMetaObject: meta);
323 } else {
324 return child(segment)->connectToEvent(segments: segments.mid(pos: 1), receiver, slot, method, type);
325 }
326}
327
328} // namespace QScxmlInternal
329
330QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0);
331
332QScxmlStateMachinePrivate::QScxmlStateMachinePrivate(const QMetaObject *metaObject)
333 : QObjectPrivate()
334 , m_sessionId(QScxmlStateMachinePrivate::generateSessionId(QStringLiteral("session-")))
335 , m_isInvoked(false)
336 , m_isProcessingEvents(false)
337 , m_executionEngine(nullptr)
338 , m_parentStateMachine(nullptr)
339 , m_eventLoopHook(this)
340 , m_metaObject(metaObject)
341 , m_infoSignalProxy(nullptr)
342{
343 static int metaType = qRegisterMetaType<QScxmlStateMachine *>();
344 Q_UNUSED(metaType);
345 m_loader.setValueBypassingBindings(&m_defaultLoader);
346}
347
348QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate()
349{
350 for (const InvokedService &invokedService : m_invokedServices)
351 delete invokedService.service;
352 qDeleteAll(c: m_cachedFactories);
353 delete m_executionEngine;
354}
355
356QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData()
357{
358 if (m_parserData.isNull())
359 m_parserData.reset(other: new ParserData);
360 return m_parserData.data();
361}
362
363void QScxmlStateMachinePrivate::addService(int invokingState)
364{
365 Q_Q(QScxmlStateMachine);
366
367 const int arrayId = m_stateTable->state(idx: invokingState).serviceFactoryIds;
368 if (arrayId == StateTable::InvalidIndex)
369 return;
370
371 const auto &ids = m_stateTable->array(idx: arrayId);
372 for (int id : ids) {
373 auto factory = serviceFactory(id);
374 auto service = factory->invoke(parentStateMachine: q);
375 if (service == nullptr)
376 continue; // service failed to start
377 const QString serviceName = service->name();
378 m_invokedServices[size_t(id)] = { .invokingState: invokingState, .service: service, .serviceName: serviceName };
379 service->start();
380 }
381 emitInvokedServicesChanged();
382}
383
384void QScxmlStateMachinePrivate::removeService(int invokingState)
385{
386 const int arrayId = m_stateTable->state(idx: invokingState).serviceFactoryIds;
387 if (arrayId == StateTable::InvalidIndex)
388 return;
389
390 for (size_t i = 0, ei = m_invokedServices.size(); i != ei; ++i) {
391 auto &it = m_invokedServices[i];
392 QScxmlInvokableService *service = it.service;
393 if (it.invokingState == invokingState && service != nullptr) {
394 it.service = nullptr;
395 delete service;
396 }
397 }
398 emitInvokedServicesChanged();
399}
400
401QScxmlInvokableServiceFactory *QScxmlStateMachinePrivate::serviceFactory(int id)
402{
403 Q_ASSERT(id <= m_stateTable->maxServiceId && id >= 0);
404 QScxmlInvokableServiceFactory *& factory = m_cachedFactories[size_t(id)];
405 if (factory == nullptr)
406 factory = m_tableData.value()->serviceFactory(id);
407 return factory;
408}
409
410bool QScxmlStateMachinePrivate::executeInitialSetup()
411{
412 return m_executionEngine->execute(ip: m_tableData.value()->initialSetup());
413}
414
415void QScxmlStateMachinePrivate::routeEvent(QScxmlEvent *event)
416{
417 Q_Q(QScxmlStateMachine);
418
419 if (!event)
420 return;
421
422 QString origin = event->origin();
423 if (origin == QStringLiteral("#_parent")) {
424 if (auto psm = m_parentStateMachine) {
425 qCDebug(qscxmlLog) << q << "routing event" << event->name() << "from" << q->name() << "to parent" << psm->name();
426 QScxmlStateMachinePrivate::get(t: psm)->postEvent(event);
427 } else {
428 qCDebug(qscxmlLog) << this << "is not invoked, so it cannot route a message to #_parent";
429 delete event;
430 }
431 } else if (origin.startsWith(QStringLiteral("#_")) && origin != QStringLiteral("#_internal")) {
432 // route to children
433 auto originId = QStringView{origin}.mid(pos: 2);
434 for (const auto &invokedService : m_invokedServices) {
435 auto service = invokedService.service;
436 if (service == nullptr)
437 continue;
438 if (service->id() == originId) {
439 qCDebug(qscxmlLog) << q << "routing event" << event->name()
440 << "from" << q->name()
441 << "to child" << service->id();
442 service->postEvent(event: new QScxmlEvent(*event));
443 }
444 }
445 delete event;
446 } else {
447 postEvent(event);
448 }
449}
450
451void QScxmlStateMachinePrivate::postEvent(QScxmlEvent *event)
452{
453 Q_Q(QScxmlStateMachine);
454
455 if (!event->name().startsWith(QStringLiteral("done.invoke."))) {
456 for (int id = 0, end = static_cast<int>(m_invokedServices.size()); id != end; ++id) {
457 auto service = m_invokedServices[id].service;
458 if (service == nullptr)
459 continue;
460 auto factory = serviceFactory(id);
461 if (event->invokeId() == service->id()) {
462 setEvent(event);
463
464 const QScxmlExecutableContent::ContainerId finalize
465 = factory->invokeInfo().finalize;
466 if (finalize != QScxmlExecutableContent::NoContainer) {
467 auto psm = service->parentStateMachine();
468 qCDebug(qscxmlLog) << psm << "running finalize on event";
469 auto smp = QScxmlStateMachinePrivate::get(t: psm);
470 smp->m_executionEngine->execute(ip: finalize);
471 }
472
473 resetEvent();
474 }
475 if (factory->invokeInfo().autoforward) {
476 qCDebug(qscxmlLog) << q << "auto-forwarding event" << event->name()
477 << "from" << q->name()
478 << "to child" << service->id();
479 service->postEvent(event: new QScxmlEvent(*event));
480 }
481 }
482 }
483
484 if (event->eventType() == QScxmlEvent::ExternalEvent)
485 m_router.route(segments: event->name().split(sep: QLatin1Char('.')), event);
486
487 if (event->eventType() == QScxmlEvent::ExternalEvent) {
488 qCDebug(qscxmlLog) << q << "posting external event" << event->name();
489 m_externalQueue.enqueue(e: event);
490 } else {
491 qCDebug(qscxmlLog) << q << "posting internal event" << event->name();
492 m_internalQueue.enqueue(e: event);
493 }
494
495 m_eventLoopHook.queueProcessEvents();
496}
497
498void QScxmlStateMachinePrivate::submitDelayedEvent(QScxmlEvent *event)
499{
500 Q_ASSERT(event);
501 Q_ASSERT(event->delay() > 0);
502
503 const int timerId = m_eventLoopHook.startTimer(interval: event->delay());
504 if (timerId == 0) {
505 qWarning(msg: "QScxmlStateMachinePrivate::submitDelayedEvent: "
506 "failed to start timer for event '%s' (%p)",
507 qPrintable(event->name()), event);
508 delete event;
509 return;
510 }
511 m_delayedEvents.push_back(x: std::make_pair(x: timerId, y&: event));
512
513 qCDebug(qscxmlLog) << q_func()
514 << ": delayed event" << event->name()
515 << "(" << event << ") got id:" << timerId;
516}
517
518/*!
519 * Submits an error event to the external event queue of this state machine.
520 *
521 * The type of the error is specified by \a type. The value of type has to begin
522 * with the string \e error. For example \c {error.execution}. The message,
523 * \a message, decribes the error and is passed to the event as the
524 * \c errorMessage property. The \a sendId of the message causing the error is specified, if it has
525 * one.
526 */
527void QScxmlStateMachinePrivate::submitError(const QString &type, const QString &message,
528 const QString &sendId)
529{
530 Q_Q(QScxmlStateMachine);
531 qCDebug(qscxmlLog) << q << "had error" << type << ":" << message;
532 if (!type.startsWith(QStringLiteral("error.")))
533 qCWarning(qscxmlLog) << q << "Message type of error message does not start with 'error.'!";
534 q->submitEvent(event: QScxmlEventBuilder::errorEvent(stateMachine: q, name: type, message, sendid: sendId));
535}
536
537void QScxmlStateMachinePrivate::start()
538{
539 Q_Q(QScxmlStateMachine);
540
541 if (m_stateTable->binding == StateTable::LateBinding)
542 m_isFirstStateEntry.resize(new_size: m_stateTable->stateCount, x: true);
543
544 bool running = isRunnable() && !isPaused();
545 m_runningState = Starting;
546 Q_ASSERT(m_stateTable->initialTransition != StateTable::InvalidIndex);
547
548 if (!running)
549 emit q->runningChanged(running: true);
550}
551
552void QScxmlStateMachinePrivate::pause()
553{
554 Q_Q(QScxmlStateMachine);
555
556 if (isRunnable() && !isPaused()) {
557 m_runningState = Paused;
558 emit q->runningChanged(running: false);
559 }
560}
561
562void QScxmlStateMachinePrivate::processEvents()
563{
564 if (m_isProcessingEvents || (!isRunnable() && !isPaused()))
565 return;
566
567 m_isProcessingEvents = true;
568
569 Q_Q(QScxmlStateMachine);
570 qCDebug(qscxmlLog) << q_func() << "starting macrostep";
571
572 while (isRunnable() && !isPaused()) {
573 if (m_runningState == Starting) {
574 enterStates(enabledTransitions: {m_stateTable->initialTransition});
575 if (m_runningState == Starting)
576 m_runningState = Running;
577 continue;
578 }
579
580 OrderedSet enabledTransitions;
581 std::vector<int> configurationInDocumentOrder = m_configuration.list();
582 std::sort(first: configurationInDocumentOrder.begin(), last: configurationInDocumentOrder.end());
583 selectTransitions(enabledTransitions, configInDocumentOrder: configurationInDocumentOrder, event: nullptr);
584 if (!enabledTransitions.isEmpty()) {
585 microstep(enabledTransitions);
586 } else if (!m_internalQueue.isEmpty()) {
587 auto event = m_internalQueue.dequeue();
588 setEvent(event);
589 selectTransitions(enabledTransitions, configInDocumentOrder: configurationInDocumentOrder, event);
590 if (!enabledTransitions.isEmpty()) {
591 microstep(enabledTransitions);
592 }
593 resetEvent();
594 delete event;
595 } else if (!m_externalQueue.isEmpty()) {
596 auto event = m_externalQueue.dequeue();
597 setEvent(event);
598 selectTransitions(enabledTransitions, configInDocumentOrder: configurationInDocumentOrder, event);
599 if (!enabledTransitions.isEmpty()) {
600 microstep(enabledTransitions);
601 }
602 resetEvent();
603 delete event;
604 } else {
605 // nothing to do, so:
606 break;
607 }
608 }
609
610 if (!m_statesToInvoke.empty()) {
611 for (int stateId : m_statesToInvoke)
612 addService(invokingState: stateId);
613 m_statesToInvoke.clear();
614 }
615
616 qCDebug(qscxmlLog) << q_func()
617 << "finished macrostep, runnable:" << isRunnable()
618 << "paused:" << isPaused();
619 emit q->reachedStableState();
620 if (!isRunnable() && !isPaused()) {
621 exitInterpreter();
622 emit q->finished();
623 }
624
625 m_isProcessingEvents = false;
626}
627
628void QScxmlStateMachinePrivate::setEvent(QScxmlEvent *event)
629{
630 Q_ASSERT(event);
631 m_dataModel.value()->setScxmlEvent(*event);
632}
633
634void QScxmlStateMachinePrivate::resetEvent()
635{
636 m_dataModel.value()->setScxmlEvent(QScxmlEvent());
637}
638
639void QScxmlStateMachinePrivate::emitStateActive(int stateIndex, bool active)
640{
641 Q_Q(QScxmlStateMachine);
642 void *args[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&active)) };
643 const int signalIndex = m_stateIndexToSignalIndex.value(key: stateIndex, defaultValue: -1);
644 if (signalIndex >= 0)
645 QMetaObject::activate(sender: q, m_metaObject, local_signal_index: signalIndex, argv: args);
646}
647
648void QScxmlStateMachinePrivate::emitInvokedServicesChanged()
649{
650 Q_Q(QScxmlStateMachine);
651 m_invokedServicesComputedProperty.notify();
652 emit q->invokedServicesChanged(invokedServices: q->invokedServices());
653}
654
655void QScxmlStateMachinePrivate::attach(QScxmlStateMachineInfo *info)
656{
657 Q_Q(QScxmlStateMachine);
658
659 if (!m_infoSignalProxy)
660 m_infoSignalProxy = new QScxmlInternal::StateMachineInfoProxy(q);
661
662 QObject::connect(sender: m_infoSignalProxy, signal: &QScxmlInternal::StateMachineInfoProxy::statesEntered,
663 context: info, slot: &QScxmlStateMachineInfo::statesEntered);
664 QObject::connect(sender: m_infoSignalProxy, signal: &QScxmlInternal::StateMachineInfoProxy::statesExited,
665 context: info, slot: &QScxmlStateMachineInfo::statesExited);
666 QObject::connect(sender: m_infoSignalProxy,signal: &QScxmlInternal::StateMachineInfoProxy::transitionsTriggered,
667 context: info, slot: &QScxmlStateMachineInfo::transitionsTriggered);
668}
669
670void QScxmlStateMachinePrivate::updateMetaCache()
671{
672 // This function creates a mapping from state index/name to their signal indexes.
673 // The state index may differ from its signal index as we don't generate history
674 // and invalid states, effectively skipping them
675 m_stateIndexToSignalIndex.clear();
676 m_stateNameToSignalIndex.clear();
677
678 if (!m_tableData.value())
679 return;
680
681 if (!m_stateTable)
682 return;
683
684 int signalIndex = 0;
685 const int methodOffset = QMetaObjectPrivate::signalOffset(m: m_metaObject);
686 for (int i = 0; i < m_stateTable->stateCount; ++i) {
687 const auto &s = m_stateTable->state(idx: i);
688 if (!s.isHistoryState() && s.type != StateTable::State::Invalid) {
689 m_stateIndexToSignalIndex.insert(key: i, value: signalIndex);
690 m_stateNameToSignalIndex.insert(key: m_tableData.value()->string(id: s.name),
691 value: signalIndex + methodOffset);
692
693 ++signalIndex;
694 }
695 }
696}
697
698QStringList QScxmlStateMachinePrivate::stateNames(const std::vector<int> &stateIndexes) const
699{
700 QStringList names;
701 for (int idx : stateIndexes)
702 names.append(t: m_tableData.value()->string(id: m_stateTable->state(idx).name));
703 return names;
704}
705
706std::vector<int> QScxmlStateMachinePrivate::historyStates(int stateIdx) const {
707 const StateTable::Array kids = m_stateTable->array(idx: m_stateTable->state(idx: stateIdx).childStates);
708 std::vector<int> res;
709 if (!kids.isValid()) return res;
710 for (int k : kids) {
711 if (m_stateTable->state(idx: k).isHistoryState())
712 res.push_back(x: k);
713 }
714 return res;
715}
716
717void QScxmlStateMachinePrivate::exitInterpreter()
718{
719 qCDebug(qscxmlLog) << q_func() << "exiting SCXML processing";
720
721 for (auto it : m_delayedEvents) {
722 m_eventLoopHook.killTimer(id: it.first);
723 delete it.second;
724 }
725 m_delayedEvents.clear();
726
727 auto statesToExitSorted = m_configuration.list();
728 std::sort(first: statesToExitSorted.begin(), last: statesToExitSorted.end(), comp: std::greater<int>());
729 for (int stateIndex : statesToExitSorted) {
730 const auto &state = m_stateTable->state(idx: stateIndex);
731 if (state.exitInstructions != StateTable::InvalidIndex) {
732 m_executionEngine->execute(ip: state.exitInstructions);
733 }
734 removeService(invokingState: stateIndex);
735 if (state.type == StateTable::State::Final && state.parentIsScxmlElement()) {
736 returnDoneEvent(doneData: state.doneData);
737 }
738 }
739}
740
741void QScxmlStateMachinePrivate::returnDoneEvent(QScxmlExecutableContent::ContainerId doneData)
742{
743 Q_Q(QScxmlStateMachine);
744
745 m_executionEngine->execute(ip: doneData, extraData: QVariant());
746 if (m_isInvoked) {
747 auto e = new QScxmlEvent;
748 e->setName(QStringLiteral("done.invoke.") + q->sessionId());
749 e->setInvokeId(q->sessionId());
750 QScxmlStateMachinePrivate::get(t: m_parentStateMachine)->postEvent(event: e);
751 }
752}
753
754bool QScxmlStateMachinePrivate::nameMatch(const StateTable::Array &patterns,
755 QScxmlEvent *event) const
756{
757 const QString eventName = event->name();
758 bool selected = false;
759 for (int eventSelectorIter = 0; eventSelectorIter < patterns.size(); ++eventSelectorIter) {
760 QString eventStr = m_tableData.value()->string(id: patterns[eventSelectorIter]);
761 if (eventStr == QStringLiteral("*")) {
762 selected = true;
763 break;
764 }
765 if (eventStr.endsWith(QStringLiteral(".*")))
766 eventStr.chop(n: 2);
767 if (eventName.startsWith(s: eventStr)) {
768 QChar nextC = QLatin1Char('.');
769 if (eventName.size() > eventStr.size())
770 nextC = eventName.at(i: eventStr.size());
771 if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) {
772 selected = true;
773 break;
774 }
775 }
776 }
777 return selected;
778}
779
780void QScxmlStateMachinePrivate::selectTransitions(OrderedSet &enabledTransitions,
781 const std::vector<int> &configInDocumentOrder,
782 QScxmlEvent *event) const
783{
784 if (event == nullptr) {
785 qCDebug(qscxmlLog) << q_func() << "selectEventlessTransitions";
786 } else {
787 qCDebug(qscxmlLog) << q_func() << "selectTransitions with event"
788 << QScxmlEventPrivate::debugString(event).constData();
789 }
790
791 std::vector<int> states;
792 states.reserve(n: 16);
793 for (int configStateIdx : configInDocumentOrder) {
794 if (m_stateTable->state(idx: configStateIdx).isAtomic()) {
795 states.clear();
796 states.push_back(x: configStateIdx);
797 getProperAncestors(ancestors: &states, state1: configStateIdx, state2: -1);
798 for (int stateIdx : states) {
799 bool finishedWithThisConfigState = false;
800
801 if (stateIdx == -1) {
802 // the state machine has no transitions (other than the initial one, which has
803 // already been taken at this point)
804 continue;
805 }
806 const auto &state = m_stateTable->state(idx: stateIdx);
807 const StateTable::Array transitions = m_stateTable->array(idx: state.transitions);
808 if (!transitions.isValid())
809 continue;
810 std::vector<int> sortedTransitions(transitions.size(), -1);
811 std::copy(first: transitions.begin(), last: transitions.end(), result: sortedTransitions.begin());
812 for (int transitionIndex : sortedTransitions) {
813 const StateTable::Transition &t = m_stateTable->transition(idx: transitionIndex);
814 bool enabled = false;
815 if (event == nullptr) {
816 if (t.events == -1) {
817 if (t.condition == -1) {
818 enabled = true;
819 } else {
820 bool ok = false;
821 enabled = m_dataModel.value()->evaluateToBool(id: t.condition, ok: &ok) && ok;
822 }
823 }
824 } else {
825 if (t.events != -1 && nameMatch(patterns: m_stateTable->array(idx: t.events), event)) {
826 if (t.condition == -1) {
827 enabled = true;
828 } else {
829 bool ok = false;
830 enabled = m_dataModel.value()->evaluateToBool(id: t.condition, ok: &ok) && ok;
831 }
832 }
833 }
834 if (enabled) {
835 enabledTransitions.add(i: transitionIndex);
836 finishedWithThisConfigState = true;
837 break; // stop iterating over transitions
838 }
839 }
840
841 if (finishedWithThisConfigState)
842 break; // stop iterating over ancestors
843 }
844 }
845 }
846 if (!enabledTransitions.isEmpty())
847 removeConflictingTransitions(enabledTransitions: &enabledTransitions);
848}
849
850void QScxmlStateMachinePrivate::removeConflictingTransitions(OrderedSet *enabledTransitions) const
851{
852 Q_ASSERT(enabledTransitions);
853
854 auto sortedTransitions = enabledTransitions->takeList();
855 std::sort(first: sortedTransitions.begin(), last: sortedTransitions.end(), comp: [this](int t1, int t2) -> bool {
856 auto descendantDepth = [this](int state, int ancestor)->int {
857 int depth = 0;
858 for (int it = state; it != -1; it = m_stateTable->state(idx: it).parent) {
859 if (it == ancestor)
860 break;
861 ++depth;
862 }
863 return depth;
864 };
865
866 const auto &s1 = m_stateTable->transition(idx: t1).source;
867 const auto &s2 = m_stateTable->transition(idx: t2).source;
868 if (s1 == s2) {
869 return t1 < t2;
870 } else if (isDescendant(state1: s1, state2: s2)) {
871 return true;
872 } else if (isDescendant(state1: s2, state2: s1)) {
873 return false;
874 } else {
875 const int lcca = findLCCA(states: { s1, s2 });
876 const int s1Depth = descendantDepth(s1, lcca);
877 const int s2Depth = descendantDepth(s2, lcca);
878 if (s1Depth == s2Depth)
879 return s1 < s2;
880 else
881 return s1Depth > s2Depth;
882 }
883 });
884
885 OrderedSet filteredTransitions;
886 for (int t1 : sortedTransitions) {
887 OrderedSet transitionsToRemove;
888 bool t1Preempted = false;
889 OrderedSet exitSetT1;
890 computeExitSet(enabledTransitions: {t1}, statesToExit&: exitSetT1);
891 const int source1 = m_stateTable->transition(idx: t1).source;
892 for (int t2 : filteredTransitions) {
893 OrderedSet exitSetT2;
894 computeExitSet(enabledTransitions: {t2}, statesToExit&: exitSetT2);
895 if (exitSetT1.intersectsWith(other: exitSetT2)) {
896 const int source2 = m_stateTable->transition(idx: t2).source;
897 if (isDescendant(state1: source1, state2: source2)) {
898 transitionsToRemove.add(i: t2);
899 } else {
900 t1Preempted = true;
901 break;
902 }
903 }
904 }
905 if (!t1Preempted) {
906 for (int t3 : transitionsToRemove) {
907 filteredTransitions.remove(i: t3);
908 }
909 filteredTransitions.add(i: t1);
910 }
911 }
912 *enabledTransitions = filteredTransitions;
913}
914
915void QScxmlStateMachinePrivate::getProperAncestors(std::vector<int> *ancestors, int state1,
916 int state2) const
917{
918 Q_ASSERT(ancestors);
919
920 if (state1 == -1) {
921 return;
922 }
923
924 int parent = state1;
925 do {
926 parent = m_stateTable->state(idx: parent).parent;
927 if (parent == state2) {
928 break;
929 }
930 ancestors->push_back(x: parent);
931 } while (parent != -1);
932}
933
934void QScxmlStateMachinePrivate::microstep(const OrderedSet &enabledTransitions)
935{
936 if (qscxmlLog().isDebugEnabled()) {
937 qCDebug(qscxmlLog) << q_func()
938 << "starting microstep, configuration:"
939 << stateNames(stateIndexes: m_configuration.list());
940 qCDebug(qscxmlLog) << q_func() << "enabled transitions:";
941 for (int t : enabledTransitions) {
942 const auto &transition = m_stateTable->transition(idx: t);
943 QString from = QStringLiteral("(none)");
944 if (transition.source != StateTable::InvalidIndex)
945 from = m_tableData.value()->string(id: m_stateTable->state(idx: transition.source).name);
946 QStringList to;
947 if (transition.targets == StateTable::InvalidIndex) {
948 to.append(QStringLiteral("(none)"));
949 } else {
950 for (int t : m_stateTable->array(idx: transition.targets))
951 to.append(t: m_tableData.value()->string(id: m_stateTable->state(idx: t).name));
952 }
953 qCDebug(qscxmlLog) << q_func() << "\t" << t << ":" << from << "->"
954 << to.join(sep: QLatin1Char(','));
955 }
956 }
957
958 exitStates(enabledTransitions);
959 executeTransitionContent(enabledTransitions);
960 enterStates(enabledTransitions);
961
962 qCDebug(qscxmlLog) << q_func() << "finished microstep, configuration:"
963 << stateNames(stateIndexes: m_configuration.list());
964}
965
966void QScxmlStateMachinePrivate::exitStates(const OrderedSet &enabledTransitions)
967{
968 OrderedSet statesToExit;
969 computeExitSet(enabledTransitions, statesToExit);
970 auto statesToExitSorted = statesToExit.takeList();
971 std::sort(first: statesToExitSorted.begin(), last: statesToExitSorted.end(), comp: std::greater<int>());
972 qCDebug(qscxmlLog) << q_func() << "exiting states" << stateNames(stateIndexes: statesToExitSorted);
973 for (int s : statesToExitSorted) {
974 const auto &state = m_stateTable->state(idx: s);
975 if (state.serviceFactoryIds != StateTable::InvalidIndex)
976 m_statesToInvoke.remove(value: s);
977 }
978 for (int s : statesToExitSorted) {
979 for (int h : historyStates(stateIdx: s)) {
980 const auto &hState = m_stateTable->state(idx: h);
981 QList<int> history;
982
983 for (int s0 : m_configuration) {
984 const auto &s0State = m_stateTable->state(idx: s0);
985 if (hState.type == StateTable::State::DeepHistory) {
986 if (s0State.isAtomic() && isDescendant(state1: s0, state2: s))
987 history.append(t: s0);
988 } else {
989 if (s0State.parent == s)
990 history.append(t: s0);
991 }
992 }
993
994 m_historyValue[h] = history;
995 }
996 }
997 for (int s : statesToExitSorted) {
998 const auto &state = m_stateTable->state(idx: s);
999 if (state.exitInstructions != StateTable::InvalidIndex)
1000 m_executionEngine->execute(ip: state.exitInstructions);
1001 m_configuration.remove(i: s);
1002 emitStateActive(stateIndex: s, active: false);
1003 removeService(invokingState: s);
1004 }
1005
1006 if (m_infoSignalProxy) {
1007 emit m_infoSignalProxy->statesExited(
1008 states: QList<QScxmlStateMachineInfo::StateId>(statesToExitSorted.begin(),
1009 statesToExitSorted.end()));
1010 }
1011}
1012
1013void QScxmlStateMachinePrivate::computeExitSet(const OrderedSet &enabledTransitions,
1014 OrderedSet &statesToExit) const
1015{
1016 for (int t : enabledTransitions) {
1017 const auto &transition = m_stateTable->transition(idx: t);
1018 if (transition.targets == StateTable::InvalidIndex) {
1019 // nothing to do here: there is no exit set
1020 } else {
1021 const int domain = getTransitionDomain(transitionIndex: t);
1022 for (int s : m_configuration) {
1023 if (isDescendant(state1: s, state2: domain))
1024 statesToExit.add(i: s);
1025 }
1026 }
1027 }
1028}
1029
1030void QScxmlStateMachinePrivate::executeTransitionContent(const OrderedSet &enabledTransitions)
1031{
1032 for (int t : enabledTransitions) {
1033 const auto &transition = m_stateTable->transition(idx: t);
1034 if (transition.transitionInstructions != StateTable::InvalidIndex)
1035 m_executionEngine->execute(ip: transition.transitionInstructions);
1036 }
1037
1038 if (m_infoSignalProxy) {
1039 emit m_infoSignalProxy->transitionsTriggered(
1040 transitions: QList<QScxmlStateMachineInfo::TransitionId>(enabledTransitions.list().begin(),
1041 enabledTransitions.list().end()));
1042 }
1043}
1044
1045void QScxmlStateMachinePrivate::enterStates(const OrderedSet &enabledTransitions)
1046{
1047 Q_Q(QScxmlStateMachine);
1048
1049 OrderedSet statesToEnter, statesForDefaultEntry;
1050 HistoryContent defaultHistoryContent;
1051 computeEntrySet(enabledTransitions, statesToEnter: &statesToEnter, statesForDefaultEntry: &statesForDefaultEntry,
1052 defaultHistoryContent: &defaultHistoryContent);
1053 auto sortedStates = statesToEnter.takeList();
1054 std::sort(first: sortedStates.begin(), last: sortedStates.end());
1055 qCDebug(qscxmlLog) << q_func() << "entering states" << stateNames(stateIndexes: sortedStates);
1056 for (int s : sortedStates) {
1057 const auto &state = m_stateTable->state(idx: s);
1058 m_configuration.add(i: s);
1059 if (state.serviceFactoryIds != StateTable::InvalidIndex)
1060 m_statesToInvoke.insert(value: s);
1061 if (m_stateTable->binding == StateTable::LateBinding && m_isFirstStateEntry[s]) {
1062 if (state.initInstructions != StateTable::InvalidIndex)
1063 m_executionEngine->execute(ip: state.initInstructions);
1064 m_isFirstStateEntry[s] = false;
1065 }
1066 if (state.entryInstructions != StateTable::InvalidIndex)
1067 m_executionEngine->execute(ip: state.entryInstructions);
1068 if (statesForDefaultEntry.contains(i: s)) {
1069 const auto &initialTransition = m_stateTable->transition(idx: state.initialTransition);
1070 if (initialTransition.transitionInstructions != StateTable::InvalidIndex)
1071 m_executionEngine->execute(ip: initialTransition.transitionInstructions);
1072 }
1073 const int dhc = defaultHistoryContent.value(idx: s);
1074 if (dhc != StateTable::InvalidIndex)
1075 m_executionEngine->execute(ip: dhc);
1076 if (state.type == StateTable::State::Final) {
1077 if (state.parentIsScxmlElement()) {
1078 bool running = isRunnable() && !isPaused();
1079 m_runningState = Finished;
1080 if (running)
1081 emit q->runningChanged(running: false);
1082 } else {
1083 const auto &parent = m_stateTable->state(idx: state.parent);
1084 m_executionEngine->execute(ip: state.doneData, extraData: m_tableData.value()->string(id: parent.name));
1085 if (parent.parent != StateTable::InvalidIndex) {
1086 const auto &grandParent = m_stateTable->state(idx: parent.parent);
1087 if (grandParent.isParallel()) {
1088 if (allInFinalStates(states: getChildStates(state: grandParent))) {
1089 auto e = new QScxmlEvent;
1090 e->setEventType(QScxmlEvent::InternalEvent);
1091 e->setName(QStringLiteral("done.state.")
1092 + m_tableData.value()->string(id: grandParent.name));
1093 q->submitEvent(event: e);
1094 }
1095 }
1096 }
1097 }
1098 }
1099 }
1100 for (int s : sortedStates)
1101 emitStateActive(stateIndex: s, active: true);
1102 if (m_infoSignalProxy) {
1103 emit m_infoSignalProxy->statesEntered(
1104 states: QList<QScxmlStateMachineInfo::StateId>(sortedStates.begin(),
1105 sortedStates.end()));
1106 }
1107}
1108
1109void QScxmlStateMachinePrivate::computeEntrySet(const OrderedSet &enabledTransitions,
1110 OrderedSet *statesToEnter,
1111 OrderedSet *statesForDefaultEntry,
1112 HistoryContent *defaultHistoryContent) const
1113{
1114 Q_ASSERT(statesToEnter);
1115 Q_ASSERT(statesForDefaultEntry);
1116 Q_ASSERT(defaultHistoryContent);
1117
1118 for (int t : enabledTransitions) {
1119 const auto &transition = m_stateTable->transition(idx: t);
1120 if (transition.targets == StateTable::InvalidIndex)
1121 // targetless transition, so nothing to do
1122 continue;
1123 for (int s : m_stateTable->array(idx: transition.targets))
1124 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1125 defaultHistoryContent);
1126 auto ancestor = getTransitionDomain(transitionIndex: t);
1127 OrderedSet targets;
1128 getEffectiveTargetStates(targets: &targets, transitionIndex: t);
1129 for (auto s : targets)
1130 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: ancestor, statesToEnter, statesForDefaultEntry,
1131 defaultHistoryContent);
1132 }
1133}
1134
1135void QScxmlStateMachinePrivate::addDescendantStatesToEnter(
1136 int stateIndex, OrderedSet *statesToEnter, OrderedSet *statesForDefaultEntry,
1137 HistoryContent *defaultHistoryContent) const
1138{
1139 Q_ASSERT(statesToEnter);
1140 Q_ASSERT(statesForDefaultEntry);
1141 Q_ASSERT(defaultHistoryContent);
1142
1143 const auto &state = m_stateTable->state(idx: stateIndex);
1144 if (state.isHistoryState()) {
1145 HistoryValues::const_iterator historyValueIter = m_historyValue.find(key: stateIndex);
1146 if (historyValueIter != m_historyValue.end()) {
1147 auto historyValue = historyValueIter.value();
1148 for (int s : historyValue)
1149 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1150 defaultHistoryContent);
1151 for (int s : historyValue)
1152 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: state.parent, statesToEnter, statesForDefaultEntry,
1153 defaultHistoryContent);
1154 } else {
1155 int transitionIdx = StateTable::InvalidIndex;
1156 if (state.transitions == StateTable::InvalidIndex) {
1157 int parentInitialTransition = m_stateTable->state(idx: state.parent).initialTransition;
1158 if (parentInitialTransition == StateTable::InvalidIndex)
1159 return;
1160 transitionIdx = parentInitialTransition;
1161 } else {
1162 transitionIdx = m_stateTable->array(idx: state.transitions)[0];
1163 }
1164 const auto &defaultHistoryTransition = m_stateTable->transition(idx: transitionIdx);
1165 defaultHistoryContent->operator[](idx: state.parent) =
1166 defaultHistoryTransition.transitionInstructions;
1167 StateTable::Array targetStates = m_stateTable->array(idx: defaultHistoryTransition.targets);
1168 for (int s : targetStates)
1169 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1170 defaultHistoryContent);
1171 for (int s : targetStates)
1172 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: state.parent, statesToEnter, statesForDefaultEntry,
1173 defaultHistoryContent);
1174 }
1175 } else {
1176 statesToEnter->add(i: stateIndex);
1177 if (state.isCompound()) {
1178 statesForDefaultEntry->add(i: stateIndex);
1179 if (state.initialTransition != StateTable::InvalidIndex) {
1180 auto initialTransition = m_stateTable->transition(idx: state.initialTransition);
1181 auto initialTransitionTargets = m_stateTable->array(idx: initialTransition.targets);
1182 for (int targetStateIndex : initialTransitionTargets)
1183 addDescendantStatesToEnter(stateIndex: targetStateIndex, statesToEnter,
1184 statesForDefaultEntry, defaultHistoryContent);
1185 for (int targetStateIndex : initialTransitionTargets)
1186 addAncestorStatesToEnter(stateIndex: targetStateIndex, ancestorIndex: stateIndex, statesToEnter,
1187 statesForDefaultEntry, defaultHistoryContent);
1188 }
1189 } else {
1190 if (state.isParallel()) {
1191 for (int child : getChildStates(state)) {
1192 if (!hasDescendant(statesToEnter: *statesToEnter, childIdx: child))
1193 addDescendantStatesToEnter(stateIndex: child, statesToEnter, statesForDefaultEntry,
1194 defaultHistoryContent);
1195 }
1196 }
1197 }
1198 }
1199}
1200
1201void QScxmlStateMachinePrivate::addAncestorStatesToEnter(
1202 int stateIndex, int ancestorIndex, OrderedSet *statesToEnter,
1203 OrderedSet *statesForDefaultEntry, HistoryContent *defaultHistoryContent) const
1204{
1205 Q_ASSERT(statesToEnter);
1206 Q_ASSERT(statesForDefaultEntry);
1207 Q_ASSERT(defaultHistoryContent);
1208
1209 std::vector<int> ancestors;
1210 getProperAncestors(ancestors: &ancestors, state1: stateIndex, state2: ancestorIndex);
1211 for (int anc : ancestors) {
1212 if (anc == -1) {
1213 // we can't enter the state machine itself, so:
1214 continue;
1215 }
1216 statesToEnter->add(i: anc);
1217 const auto &ancState = m_stateTable->state(idx: anc);
1218 if (ancState.isParallel()) {
1219 for (int child : getChildStates(state: ancState)) {
1220 if (!hasDescendant(statesToEnter: *statesToEnter, childIdx: child))
1221 addDescendantStatesToEnter(stateIndex: child, statesToEnter, statesForDefaultEntry,
1222 defaultHistoryContent);
1223 }
1224 }
1225 }
1226}
1227
1228std::vector<int> QScxmlStateMachinePrivate::getChildStates(
1229 const QScxmlExecutableContent::StateTable::State &state) const
1230{
1231 std::vector<int> childStates;
1232 auto kids = m_stateTable->array(idx: state.childStates);
1233 if (kids.isValid()) {
1234 childStates.reserve(n: kids.size());
1235 for (int kiddo : kids) {
1236 switch (m_stateTable->state(idx: kiddo).type) {
1237 case StateTable::State::Normal:
1238 case StateTable::State::Final:
1239 case StateTable::State::Parallel:
1240 childStates.push_back(x: kiddo);
1241 break;
1242 default:
1243 break;
1244 }
1245 }
1246 }
1247 return childStates;
1248}
1249
1250bool QScxmlStateMachinePrivate::hasDescendant(const OrderedSet &statesToEnter, int childIdx) const
1251{
1252 for (int s : statesToEnter) {
1253 if (isDescendant(state1: s, state2: childIdx))
1254 return true;
1255 }
1256 return false;
1257}
1258
1259bool QScxmlStateMachinePrivate::allDescendants(const OrderedSet &statesToEnter, int childdx) const
1260{
1261 for (int s : statesToEnter) {
1262 if (!isDescendant(state1: s, state2: childdx))
1263 return false;
1264 }
1265 return true;
1266}
1267
1268bool QScxmlStateMachinePrivate::isDescendant(int state1, int state2) const
1269{
1270 int parent = state1;
1271 do {
1272 parent = m_stateTable->state(idx: parent).parent;
1273 if (parent == state2)
1274 return true;
1275 } while (parent != -1);
1276 return false;
1277}
1278
1279bool QScxmlStateMachinePrivate::allInFinalStates(const std::vector<int> &states) const
1280{
1281 if (states.empty())
1282 return false;
1283
1284 for (int idx : states) {
1285 if (!isInFinalState(stateIndex: idx))
1286 return false;
1287 }
1288
1289 return true;
1290}
1291
1292bool QScxmlStateMachinePrivate::someInFinalStates(const std::vector<int> &states) const
1293{
1294 for (int stateIndex : states) {
1295 const auto &state = m_stateTable->state(idx: stateIndex);
1296 if (state.type == StateTable::State::Final && m_configuration.contains(i: stateIndex))
1297 return true;
1298 }
1299 return false;
1300}
1301
1302bool QScxmlStateMachinePrivate::isInFinalState(int stateIndex) const
1303{
1304 const auto &state = m_stateTable->state(idx: stateIndex);
1305 if (state.isCompound())
1306 return someInFinalStates(states: getChildStates(state)) && m_configuration.contains(i: stateIndex);
1307 else if (state.isParallel())
1308 return allInFinalStates(states: getChildStates(state));
1309 else
1310 return false;
1311}
1312
1313int QScxmlStateMachinePrivate::getTransitionDomain(int transitionIndex) const
1314{
1315 const auto &transition = m_stateTable->transition(idx: transitionIndex);
1316 if (transition.source == -1)
1317 //oooh, we have the initial transition of the state machine.
1318 return -1;
1319
1320 OrderedSet tstates;
1321 getEffectiveTargetStates(targets: &tstates, transitionIndex);
1322 if (tstates.isEmpty()) {
1323 return StateTable::InvalidIndex;
1324 } else {
1325 const auto &sourceState = m_stateTable->state(idx: transition.source);
1326 if (transition.type == StateTable::Transition::Internal
1327 && sourceState.isCompound()
1328 && allDescendants(statesToEnter: tstates, childdx: transition.source)) {
1329 return transition.source;
1330 } else {
1331 tstates.add(i: transition.source);
1332 return findLCCA(states: std::move(tstates));
1333 }
1334 }
1335}
1336
1337int QScxmlStateMachinePrivate::findLCCA(OrderedSet &&states) const
1338{
1339 std::vector<int> ancestors;
1340 const int head = *states.begin();
1341 OrderedSet tail(std::move(states));
1342 tail.removeHead();
1343
1344 getProperAncestors(ancestors: &ancestors, state1: head, state2: StateTable::InvalidIndex);
1345 for (int anc : ancestors) {
1346 if (anc != -1) { // the state machine itself is always compound
1347 const auto &ancState = m_stateTable->state(idx: anc);
1348 if (!ancState.isCompound())
1349 continue;
1350 }
1351
1352 if (allDescendants(statesToEnter: tail, childdx: anc))
1353 return anc;
1354 }
1355
1356 return StateTable::InvalidIndex;
1357}
1358
1359void QScxmlStateMachinePrivate::getEffectiveTargetStates(OrderedSet *targets,
1360 int transitionIndex) const
1361{
1362 Q_ASSERT(targets);
1363
1364 const auto &transition = m_stateTable->transition(idx: transitionIndex);
1365 for (int s : m_stateTable->array(idx: transition.targets)) {
1366 const auto &state = m_stateTable->state(idx: s);
1367 if (state.isHistoryState()) {
1368 HistoryValues::const_iterator historyValueIter = m_historyValue.find(key: s);
1369 if (historyValueIter != m_historyValue.end()) {
1370 for (int historyState : historyValueIter.value()) {
1371 targets->add(i: historyState);
1372 }
1373 } else if (state.transitions != StateTable::InvalidIndex) {
1374 getEffectiveTargetStates(targets, transitionIndex: m_stateTable->array(idx: state.transitions)[0]);
1375 }
1376 } else {
1377 targets->add(i: s);
1378 }
1379 }
1380}
1381
1382/*!
1383 * Creates a state machine from the SCXML file specified by \a fileName.
1384 *
1385 * This method will always return a state machine. If errors occur while reading the SCXML file,
1386 * the state machine cannot be started. The errors can be retrieved by calling the parseErrors()
1387 * method.
1388 *
1389 * \sa parseErrors()
1390 */
1391QScxmlStateMachine *QScxmlStateMachine::fromFile(const QString &fileName)
1392{
1393 QFile scxmlFile(fileName);
1394 if (!scxmlFile.open(flags: QIODevice::ReadOnly)) {
1395 auto stateMachine = new QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject);
1396 QScxmlError err(scxmlFile.fileName(), 0, 0, QStringLiteral("cannot open for reading"));
1397 QScxmlStateMachinePrivate::get(t: stateMachine)->parserData()->m_errors.append(t: err);
1398 return stateMachine;
1399 }
1400
1401 QScxmlStateMachine *stateMachine = fromData(data: &scxmlFile, fileName);
1402 scxmlFile.close();
1403 return stateMachine;
1404}
1405
1406/*!
1407 * Creates a state machine by reading from the QIODevice specified by \a data.
1408 *
1409 * This method will always return a state machine. If errors occur while reading the SCXML file,
1410 * \a fileName, the state machine cannot be started. The errors can be retrieved by calling the
1411 * parseErrors() method.
1412 *
1413 * \sa parseErrors()
1414 */
1415QScxmlStateMachine *QScxmlStateMachine::fromData(QIODevice *data, const QString &fileName)
1416{
1417 QXmlStreamReader xmlReader(data);
1418 QScxmlCompiler compiler(&xmlReader);
1419 compiler.setFileName(fileName);
1420 return compiler.compile();
1421}
1422
1423QList<QScxmlError> QScxmlStateMachine::parseErrors() const
1424{
1425 Q_D(const QScxmlStateMachine);
1426 return d->m_parserData ? d->m_parserData->m_errors : QList<QScxmlError>();
1427}
1428
1429QScxmlStateMachine::QScxmlStateMachine(const QMetaObject *metaObject, QObject *parent)
1430 : QObject(*new QScxmlStateMachinePrivate(metaObject), parent)
1431{
1432 Q_D(QScxmlStateMachine);
1433 d->m_executionEngine = new QScxmlExecutionEngine(this);
1434}
1435
1436QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent)
1437 : QObject(dd, parent)
1438{
1439 Q_D(QScxmlStateMachine);
1440 d->m_executionEngine = new QScxmlExecutionEngine(this);
1441}
1442
1443/*!
1444 \property QScxmlStateMachine::running
1445
1446 \brief the running state of this state machine
1447
1448 \sa start()
1449 */
1450
1451/*!
1452 \qmlproperty bool ScxmlStateMachine::running
1453
1454 The running state of this state machine.
1455*/
1456
1457/*!
1458 \property QScxmlStateMachine::dataModel
1459
1460 \brief The data model to be used for this state machine.
1461
1462 SCXML data models are described in
1463 \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
1464 information about supported data models, see \l {SCXML Compliance}.
1465
1466 Changing the data model when the state machine has been \c initialized is
1467 not specified in the SCXML standard and leads to undefined behavior.
1468
1469 \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
1470*/
1471
1472/*!
1473 \qmlproperty ScxmlDataModel ScxmlStateMachine::dataModel
1474
1475 The data model to be used for this state machine.
1476
1477 SCXML data models are described in
1478 \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
1479 information about supported data models, see \l {SCXML Compliance}.
1480
1481 Changing the data model when the state machine has been \l initialized is
1482 not specified in the SCXML standard and leads to undefined behavior.
1483
1484 \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
1485*/
1486
1487/*!
1488 \property QScxmlStateMachine::initialized
1489
1490 \brief Whether the state machine has been initialized.
1491
1492 It is \c true if the state machine has been initialized, \c false otherwise.
1493
1494 \sa QScxmlStateMachine::init(), QScxmlDataModel
1495*/
1496
1497/*!
1498 \qmlproperty bool ScxmlStateMachine::initialized
1499
1500 This read-only property is set to \c true if the state machine has been
1501 initialized, \c false otherwise.
1502*/
1503
1504/*!
1505 \property QScxmlStateMachine::initialValues
1506
1507 \brief The initial values to be used for setting up the data model.
1508
1509 \sa QScxmlStateMachine::init(), QScxmlDataModel
1510*/
1511
1512/*!
1513 \qmlproperty var ScxmlStateMachine::initialValues
1514
1515 The initial values to be used for setting up the data model.
1516*/
1517
1518/*!
1519 \property QScxmlStateMachine::sessionId
1520
1521 \brief The session ID of the current state machine.
1522
1523 The session ID is used for message routing between parent and child state machines. If a state
1524 machine is started by an \c <invoke> element, any event it sends will have the \c invokeid field
1525 set to the session ID. The state machine will use the origin of an event (which is set by the
1526 \e target or \e targetexpr attribute in a \c <send> element) to dispatch messages to the correct
1527 child state machine.
1528
1529 \sa QScxmlEvent::invokeId()
1530 */
1531
1532/*!
1533 \qmlproperty string ScxmlStateMachine::sessionId
1534
1535 The session ID of the current state machine.
1536
1537 The session ID is used for message routing between parent and child state
1538 machines. If a state machine is started by an \c <invoke> element, any event
1539 it sends will have the \c invokeid field set to the session ID. The state
1540 machine will use the origin of an event (which is set by the \e target or
1541 \e targetexpr attribute in a \c <send> element) to dispatch messages to the
1542 correct child state machine.
1543*/
1544
1545/*!
1546 \property QScxmlStateMachine::name
1547
1548 \brief The name of the state machine as set by the \e name attribute of the \c <scxml> tag.
1549 */
1550
1551/*!
1552 \qmlproperty string ScxmlStateMachine::name
1553
1554 The name of the state machine as set by the \e name attribute of the
1555 \c <scxml> tag.
1556*/
1557
1558/*!
1559 \property QScxmlStateMachine::invoked
1560
1561 \brief Whether the state machine was invoked from an outer state machine.
1562
1563 \c true when the state machine was started as a service with the \c <invoke> element,
1564 \c false otherwise.
1565 */
1566
1567/*!
1568 \qmlproperty bool ScxmlStateMachine::invoked
1569
1570 Whether the state machine was invoked from an outer state machine.
1571
1572 This read-only property is set to \c true when the state machine was started
1573 as a service with the \c <invoke> element, \c false otherwise.
1574 */
1575
1576/*!
1577 \property QScxmlStateMachine::parseErrors
1578
1579 \brief The list of parse errors that occurred while creating a state machine from an SCXML file.
1580 */
1581
1582/*!
1583 \qmlproperty var ScxmlStateMachine::parseErrors
1584
1585 The list of parse errors that occurred while creating a state machine from
1586 an SCXML file.
1587 */
1588
1589/*!
1590 \property QScxmlStateMachine::loader
1591
1592 \brief The loader that is currently used to resolve and load URIs for the state machine.
1593 */
1594
1595/*!
1596 \qmlproperty Loader ScxmlStateMachine::loader
1597
1598 The loader that is currently used to resolve and load URIs for the state
1599 machine.
1600 */
1601
1602/*!
1603 \property QScxmlStateMachine::tableData
1604
1605 \brief The table data that is used when generating C++ from an SCXML file.
1606
1607 The class implementing
1608 the state machine will use this property to assign the generated table
1609 data. The state machine does not assume ownership of the table data.
1610 */
1611
1612QString QScxmlStateMachine::sessionId() const
1613{
1614 Q_D(const QScxmlStateMachine);
1615
1616 return d->m_sessionId;
1617}
1618
1619QString QScxmlStateMachinePrivate::generateSessionId(const QString &prefix)
1620{
1621 int id = ++QScxmlStateMachinePrivate::m_sessionIdCounter;
1622 return prefix + QString::number(id);
1623}
1624
1625bool QScxmlStateMachine::isInvoked() const
1626{
1627 Q_D(const QScxmlStateMachine);
1628 return d->m_isInvoked;
1629}
1630
1631bool QScxmlStateMachine::isInitialized() const
1632{
1633 Q_D(const QScxmlStateMachine);
1634 return d->m_isInitialized;
1635}
1636
1637QBindable<bool> QScxmlStateMachine::bindableInitialized() const
1638{
1639 Q_D(const QScxmlStateMachine);
1640 return &d->m_isInitialized;
1641}
1642
1643/*!
1644 * Sets the data model for this state machine to \a model. There is a 1:1
1645 * relation between state machines and models. After setting the model once you
1646 * cannot change it anymore. Any further attempts to set the model using this
1647 * method will be ignored.
1648 */
1649void QScxmlStateMachine::setDataModel(QScxmlDataModel *model)
1650{
1651 Q_D(QScxmlStateMachine);
1652
1653 if (d->m_dataModel.value() == nullptr && model != nullptr) {
1654 // the binding is removed only on the first valid set
1655 // as the later attempts are ignored (removed when value is set below)
1656 d->m_dataModel = model;
1657 model->setStateMachine(this);
1658 d->m_dataModel.notify();
1659 emit dataModelChanged(model);
1660 }
1661}
1662
1663/*!
1664 * Returns the data model used by the state machine.
1665 */
1666QScxmlDataModel *QScxmlStateMachine::dataModel() const
1667{
1668 Q_D(const QScxmlStateMachine);
1669
1670 return d->m_dataModel;
1671}
1672
1673QBindable<QScxmlDataModel*> QScxmlStateMachine::bindableDataModel()
1674{
1675 Q_D(QScxmlStateMachine);
1676 return &d->m_dataModel;
1677}
1678
1679void QScxmlStateMachine::setLoader(QScxmlCompiler::Loader *loader)
1680{
1681 Q_D(QScxmlStateMachine);
1682 d->m_loader.setValue(loader);
1683}
1684
1685QScxmlCompiler::Loader *QScxmlStateMachine::loader() const
1686{
1687 Q_D(const QScxmlStateMachine);
1688
1689 return d->m_loader;
1690}
1691
1692QBindable<QScxmlCompiler::Loader*> QScxmlStateMachine::bindableLoader()
1693{
1694 Q_D(QScxmlStateMachine);
1695 return &d->m_loader;
1696}
1697
1698QScxmlTableData *QScxmlStateMachine::tableData() const
1699{
1700 Q_D(const QScxmlStateMachine);
1701
1702 return d->m_tableData;
1703}
1704
1705void QScxmlStateMachine::setTableData(QScxmlTableData *tableData)
1706{
1707 Q_D(QScxmlStateMachine);
1708
1709 if (d->m_tableData.value() == tableData) {
1710 d->m_tableData.removeBindingUnlessInWrapper();
1711 return;
1712 }
1713
1714 d->m_tableData = tableData;
1715 if (tableData) {
1716 d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>(
1717 tableData->stateMachineTable());
1718 if (objectName().isEmpty()) {
1719 setObjectName(tableData->name());
1720 }
1721 if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) {
1722 const size_t serviceCount = size_t(d->m_stateTable->maxServiceId + 1);
1723 d->m_invokedServices.resize(new_size: serviceCount, x: { .invokingState: -1, .service: nullptr, .serviceName: QString() });
1724 d->m_cachedFactories.resize(new_size: serviceCount, x: nullptr);
1725 }
1726
1727 if (d->m_stateTable->version != Q_QSCXMLC_OUTPUT_REVISION) {
1728 qFatal(msg: "Cannot mix incompatible state table (version 0x%x) with this library "
1729 "(version 0x%x)", d->m_stateTable->version, Q_QSCXMLC_OUTPUT_REVISION);
1730 }
1731 Q_ASSERT(tableData->stateMachineTable()[d->m_stateTable->arrayOffset +
1732 d->m_stateTable->arraySize]
1733 == QScxmlExecutableContent::StateTable::terminator);
1734 }
1735
1736 d->updateMetaCache();
1737
1738 d->m_tableData.notify();
1739 emit tableDataChanged(tableData);
1740}
1741
1742QBindable<QScxmlTableData*> QScxmlStateMachine::bindableTableData()
1743{
1744 Q_D(QScxmlStateMachine);
1745 return &d->m_tableData;
1746}
1747
1748/*!
1749 \qmlmethod ScxmlStateMachine::stateNames(bool compress)
1750
1751 Retrieves a list of state names of all states.
1752
1753 When \a compress is \c true (the default), the states that contain child
1754 states is filtered out and only the \e {leaf states} is returned. When it
1755 is \c false, the full list of all states is returned.
1756
1757 The returned list does not contain the states of possible nested state
1758 machines.
1759
1760 \note The order of the state names in the list is the order in which the
1761 states occurred in the SCXML document.
1762*/
1763
1764/*!
1765 * Retrieves a list of state names of all states.
1766 *
1767 * When \a compress is \c true (the default), the states that contain child states
1768 * will be filtered out and only the \e {leaf states} will be returned.
1769 * When it is \c false, the full list of all states will be returned.
1770 *
1771 * The returned list does not contain the states of possible nested state machines.
1772 *
1773 * \note The order of the state names in the list is the order in which the states occurred in
1774 * the SCXML document.
1775 */
1776QStringList QScxmlStateMachine::stateNames(bool compress) const
1777{
1778 Q_D(const QScxmlStateMachine);
1779
1780 QStringList names;
1781 for (int i = 0; i < d->m_stateTable->stateCount; ++i) {
1782 const auto &state = d->m_stateTable->state(idx: i);
1783 if (!compress || state.isAtomic())
1784 names.append(t: d->m_tableData.value()->string(id: state.name));
1785 }
1786 return names;
1787}
1788
1789/*!
1790 \qmlmethod ScxmlStateMachine::activeStateNames(bool compress)
1791
1792 Retrieves a list of state names of all active states.
1793
1794 When a state is active, all its parent states are active by definition. When
1795 \a compress is \c true (the default), the parent states are filtered out and
1796 only the \e {leaf states} are returned. When it is \c false, the full list
1797 of active states is returned.
1798*/
1799
1800/*!
1801 * Retrieves a list of state names of all active states.
1802 *
1803 * When a state is active, all its parent states are active by definition. When \a compress
1804 * is \c true (the default), the parent states will be filtered out and only the \e {leaf states}
1805 * will be returned. When it is \c false, the full list of active states will be returned.
1806 */
1807QStringList QScxmlStateMachine::activeStateNames(bool compress) const
1808{
1809 Q_D(const QScxmlStateMachine);
1810
1811 QStringList result;
1812 for (int stateIdx : d->m_configuration) {
1813 const auto &state = d->m_stateTable->state(idx: stateIdx);
1814 if (state.isAtomic() || !compress)
1815 result.append(t: d->m_tableData.value()->string(id: state.name));
1816 }
1817 return result;
1818}
1819
1820/*!
1821 \qmlmethod ScxmlStateMachine::isActive(string scxmlStateName)
1822
1823 Returns \c true if the state specified by \a scxmlStateName is active,
1824 \c false otherwise.
1825*/
1826
1827/*!
1828 * Returns \c true if the state specified by \a scxmlStateName is active, \c false otherwise.
1829 */
1830bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const
1831{
1832 Q_D(const QScxmlStateMachine);
1833
1834 for (int stateIndex : d->m_configuration) {
1835 const auto &state = d->m_stateTable->state(idx: stateIndex);
1836 if (d->m_tableData.value()->string(id: state.name) == scxmlStateName)
1837 return true;
1838 }
1839
1840 return false;
1841}
1842
1843QMetaObject::Connection QScxmlStateMachine::connectToStateImpl(const QString &scxmlStateName,
1844 const QObject *receiver, void **slot,
1845 QtPrivate::QSlotObjectBase *slotObj,
1846 Qt::ConnectionType type)
1847{
1848 const int *types = nullptr;
1849 if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
1850 types = QtPrivate::ConnectionTypes<QtPrivate::List<bool> >::types();
1851
1852 Q_D(QScxmlStateMachine);
1853 const int signalIndex = d->m_stateNameToSignalIndex.value(key: scxmlStateName, defaultValue: -1);
1854 return signalIndex < 0 ? QMetaObject::Connection()
1855 : QObjectPrivate::connectImpl(sender: this, signal_index: signalIndex, receiver, slot, slotObj,
1856 type, types, senderMetaObject: d->m_metaObject);
1857}
1858
1859/*!
1860 Creates a connection of the given \a type from the state identified by \a scxmlStateName
1861 to the \a method in the \a receiver object. The receiver's \a method
1862 may take a boolean argument that indicates whether the state connected
1863 became active or inactive. For example:
1864
1865 \code
1866 void mySlot(bool active);
1867 \endcode
1868
1869 Returns a handle to the connection, which can be used later to disconnect.
1870 */
1871QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlStateName,
1872 const QObject *receiver,
1873 const char *method,
1874 Qt::ConnectionType type)
1875{
1876 QByteArray signalName = QByteArray::number(QSIGNAL_CODE) + scxmlStateName.toUtf8()
1877 + "Changed(bool)";
1878 return QObject::connect(sender: this, signal: signalName.constData(), receiver, member: method, type);
1879}
1880
1881/*!
1882 Creates a connection of the specified \a type from the event specified by
1883 \a scxmlEventSpec to the \a method in the \a receiver object. The receiver's
1884 \a method may take a QScxmlEvent as a parameter. For example:
1885
1886 \code
1887 void mySlot(const QScxmlEvent &event);
1888 \endcode
1889
1890 In contrast to event specifications in SCXML documents, spaces are not
1891 allowed in the \a scxmlEventSpec here. In order to connect to multiple
1892 events with different prefixes, connectToEvent() has to be called multiple
1893 times.
1894
1895 Returns a handle to the connection, which can be used later to disconnect.
1896*/
1897QMetaObject::Connection QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec,
1898 const QObject *receiver,
1899 const char *method,
1900 Qt::ConnectionType type)
1901{
1902 Q_D(QScxmlStateMachine);
1903 return d->m_router.connectToEvent(segments: scxmlEventSpec.split(sep: QLatin1Char('.')), receiver, method,
1904 type);
1905}
1906
1907QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &scxmlEventSpec,
1908 const QObject *receiver, void **slot,
1909 QtPrivate::QSlotObjectBase *slotObj,
1910 Qt::ConnectionType type)
1911{
1912 Q_D(QScxmlStateMachine);
1913 return d->m_router.connectToEvent(segments: scxmlEventSpec.split(sep: QLatin1Char('.')), receiver, slot,
1914 method: slotObj, type);
1915}
1916
1917/*!
1918 \qmlmethod ScxmlStateMachine::init()
1919
1920 Initializes the state machine by setting the initial values for \c <data>
1921 elements and executing any \c <script> tags of the \c <scxml> tag. The
1922 initial data values are taken from the \l initialValues property.
1923
1924 Returns \c false if parse errors occur or if any of the initialization steps
1925 fail. Returns \c true otherwise.
1926*/
1927
1928/*!
1929 * Initializes the state machine.
1930 *
1931 * State machine initialization consists of calling QScxmlDataModel::setup(), setting the initial
1932 * values for \c <data> elements, and executing any \c <script> tags of the \c <scxml> tag. The
1933 * initial data values are taken from the \c initialValues property.
1934 *
1935 * Returns \c false if parse errors occur or if any of the initialization steps fail.
1936 * Returns \c true otherwise.
1937 */
1938bool QScxmlStateMachine::init()
1939{
1940 Q_D(QScxmlStateMachine);
1941
1942 if (d->m_isInitialized.value())
1943 return false;
1944
1945 if (!parseErrors().isEmpty())
1946 return false;
1947
1948 if (!dataModel() || !dataModel()->setup(d->m_initialValues.value()))
1949 return false;
1950
1951 if (!d->executeInitialSetup())
1952 return false;
1953
1954 d->m_isInitialized.setValue(true);
1955 return true;
1956}
1957
1958/*!
1959 * Returns \c true if the state machine is running, \c false otherwise.
1960 *
1961 * \sa setRunning(), runningChanged()
1962 */
1963bool QScxmlStateMachine::isRunning() const
1964{
1965 Q_D(const QScxmlStateMachine);
1966
1967 return d->isRunnable() && !d->isPaused();
1968}
1969
1970/*!
1971 * Starts the state machine if \a running is \c true, or stops it otherwise.
1972 *
1973 * \sa start(), stop(), isRunning(), runningChanged()
1974 */
1975void QScxmlStateMachine::setRunning(bool running)
1976{
1977 if (running)
1978 start();
1979 else
1980 stop();
1981}
1982
1983QVariantMap QScxmlStateMachine::initialValues()
1984{
1985 Q_D(const QScxmlStateMachine);
1986 return d->m_initialValues;
1987}
1988
1989void QScxmlStateMachine::setInitialValues(const QVariantMap &initialValues)
1990{
1991 Q_D(QScxmlStateMachine);
1992 d->m_initialValues.setValue(initialValues);
1993}
1994
1995QBindable<QVariantMap> QScxmlStateMachine::bindableInitialValues()
1996{
1997 Q_D(QScxmlStateMachine);
1998 return &d->m_initialValues;
1999}
2000
2001QString QScxmlStateMachine::name() const
2002{
2003 return tableData()->name();
2004}
2005
2006/*!
2007 \qmlmethod ScxmlStateMachine::submitEvent(event)
2008
2009 Submits the SCXML event \a event to the internal or external event queue
2010 depending on the priority of the event.
2011
2012 When a delay is set, the event will be queued for delivery after the timeout
2013 has passed. The state machine takes ownership of the event and deletes it
2014 after processing.
2015
2016 \sa QScxmlEvent
2017 */
2018
2019/*!
2020 * Submits the SCXML event \a event to the internal or external event queue depending on the
2021 * priority of the event.
2022 *
2023 * When a delay is set, the event will be queued for delivery after the timeout has passed.
2024 * The state machine takes ownership of \a event and deletes it after processing.
2025 */
2026void QScxmlStateMachine::submitEvent(QScxmlEvent *event)
2027{
2028 Q_D(QScxmlStateMachine);
2029
2030 if (!event)
2031 return;
2032
2033 if (event->delay() > 0) {
2034 qCDebug(qscxmlLog) << this << "submitting event" << event->name()
2035 << "with delay" << event->delay() << "ms:"
2036 << QScxmlEventPrivate::debugString(event).constData();
2037
2038 Q_ASSERT(event->eventType() == QScxmlEvent::ExternalEvent);
2039 d->submitDelayedEvent(event);
2040 } else {
2041 qCDebug(qscxmlLog) << this << "submitting event" << event->name()
2042 << ":" << QScxmlEventPrivate::debugString(event).constData();
2043
2044 d->routeEvent(event);
2045 }
2046}
2047
2048/*!
2049 * A utility method to create and submit an external event with the specified
2050 * \a eventName as the name.
2051 */
2052void QScxmlStateMachine::submitEvent(const QString &eventName)
2053{
2054 QScxmlEvent *e = new QScxmlEvent;
2055 e->setName(eventName);
2056 e->setEventType(QScxmlEvent::ExternalEvent);
2057 submitEvent(event: e);
2058}
2059/*!
2060 \qmlmethod ScxmlStateMachine::submitEvent(string eventName, var data)
2061
2062 A utility method to create and submit an external event with the specified
2063 \a eventName as the name and \a data as the payload data (optional).
2064*/
2065
2066/*!
2067 * A utility method to create and submit an external event with the specified
2068 * \a eventName as the name and \a data as the payload data.
2069 */
2070void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &data)
2071{
2072 QScxmlEvent *e = new QScxmlEvent;
2073 e->setName(eventName);
2074 e->setEventType(QScxmlEvent::ExternalEvent);
2075 e->setData(data);
2076 submitEvent(event: e);
2077}
2078
2079/*!
2080 \qmlmethod ScxmlStateMachine::cancelDelayedEvent(string sendId)
2081
2082 Cancels a delayed event with the specified \a sendId.
2083*/
2084
2085/*!
2086 * Cancels a delayed event with the specified \a sendId.
2087 */
2088void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId)
2089{
2090 Q_D(QScxmlStateMachine);
2091
2092 for (auto it = d->m_delayedEvents.begin(), eit = d->m_delayedEvents.end(); it != eit; ++it) {
2093 if (it->second->sendId() == sendId) {
2094 qCDebug(qscxmlLog) << this
2095 << "canceling event" << sendId
2096 << "with timer id" << it->first;
2097 d->m_eventLoopHook.killTimer(id: it->first);
2098 delete it->second;
2099 d->m_delayedEvents.erase(position: it);
2100 return;
2101 }
2102 }
2103}
2104
2105/*!
2106 \qmlmethod ScxmlStateMachine::isDispatchableTarget(string target)
2107
2108 Returns \c true if a message to \a target can be dispatched by this state
2109 machine.
2110
2111 Valid targets are:
2112 \list
2113 \li \c #_parent for the parent state machine if the current state machine
2114 is started by \c <invoke>
2115 \li \c #_internal for the current state machine
2116 \li \c #_scxml_sessionid, where \c sessionid is the session ID of the
2117 current state machine
2118 \li \c #_servicename, where \c servicename is the ID or name of a service
2119 started with \c <invoke> by this state machine
2120 \endlist
2121 */
2122
2123/*!
2124 * Returns \c true if a message to \a target can be dispatched by this state machine.
2125 *
2126 * Valid targets are:
2127 * \list
2128 * \li \c #_parent for the parent state machine if the current state machine is started by
2129 * \c <invoke>
2130 * \li \c #_internal for the current state machine
2131 * \li \c #_scxml_sessionid, where \c sessionid is the session ID of the current state machine
2132 * \li \c #_servicename, where \c servicename is the ID or name of a service started with
2133 * \c <invoke> by this state machine
2134 * \endlist
2135 */
2136bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const
2137{
2138 Q_D(const QScxmlStateMachine);
2139
2140 if (isInvoked() && target == QStringLiteral("#_parent"))
2141 return true; // parent state machine, if we're <invoke>d.
2142 if (target == QStringLiteral("#_internal")
2143 || target == QStringLiteral("#_scxml_%1").arg(a: sessionId()))
2144 return true; // that's the current state machine
2145
2146 if (target.startsWith(QStringLiteral("#_"))) {
2147 QStringView targetId = QStringView{target}.mid(pos: 2);
2148 for (auto invokedService : d->m_invokedServices) {
2149 if (invokedService.service && invokedService.service->id() == targetId)
2150 return true;
2151 }
2152 }
2153
2154 return false;
2155}
2156
2157/*!
2158 \qmlproperty list ScxmlStateMachine::invokedServices
2159
2160 A list of SCXML services that were invoked from the main state machine
2161 (possibly recursively).
2162*/
2163
2164/*!
2165 \property QScxmlStateMachine::invokedServices
2166 \brief A list of SCXML services that were invoked from the main
2167 state machine (possibly recursively).
2168*/
2169
2170QList<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const
2171{
2172 Q_D(const QScxmlStateMachine);
2173 return d->m_invokedServicesComputedProperty;
2174}
2175
2176QBindable<QList<QScxmlInvokableService*>> QScxmlStateMachine::bindableInvokedServices()
2177{
2178 Q_D(QScxmlStateMachine);
2179 return &d->m_invokedServicesComputedProperty;
2180}
2181
2182/*!
2183 \fn QScxmlStateMachine::runningChanged(bool running)
2184
2185 This signal is emitted when the \c running property is changed with \a running as argument.
2186*/
2187
2188/*!
2189 \fn QScxmlStateMachine::log(const QString &label, const QString &msg)
2190
2191 This signal is emitted if a \c <log> tag is used in the SCXML. \a label is the value of the
2192 \e label attribute of the \c <log> tag. \a msg is the value of the evaluated \e expr attribute
2193 of the \c <log> tag. If there is no \e expr attribute, a null string will be returned.
2194*/
2195
2196/*!
2197 \qmlsignal ScxmlStateMachine::log(string label, string msg)
2198
2199 This signal is emitted if a \c <log> tag is used in the SCXML. \a label is
2200 the value of the \e label attribute of the \c <log> tag. \a msg is the value
2201 of the evaluated \e expr attribute of the \c <log> tag. If there is no
2202 \e expr attribute, a null string will be returned.
2203
2204 The corresponding signal handler is \c onLog().
2205*/
2206
2207/*!
2208 \fn QScxmlStateMachine::reachedStableState()
2209
2210 This signal is emitted when the event queue is empty at the end of a macro step or when a final
2211 state is reached.
2212*/
2213
2214/*!
2215 \qmlsignal ScxmlStateMachine::reachedStableState()
2216
2217 This signal is emitted when the event queue is empty at the end of a macro
2218 step or when a final state is reached.
2219
2220 The corresponding signal handler is \c onreachedStableState().
2221*/
2222
2223/*!
2224 \fn QScxmlStateMachine::finished()
2225
2226 This signal is emitted when the state machine reaches a top-level final state.
2227
2228 \sa running
2229*/
2230
2231/*!
2232 \qmlsignal ScxmlStateMachine::finished()
2233
2234 This signal is emitted when the state machine reaches a top-level final
2235 state.
2236
2237 The corresponding signal handler is \c onFinished().
2238*/
2239
2240/*!
2241 \qmlmethod ScxmlStateMachine::start()
2242
2243 Starts this state machine. The machine resets its configuration and
2244 transitions to the initial state. When a final top-level state
2245 is entered, the machine emits the finished() signal.
2246
2247 \sa stop(), finished()
2248*/
2249
2250/*!
2251 Starts this state machine. The machine will reset its configuration and
2252 transition to the initial state. When a final top-level state
2253 is entered, the machine will emit the finished() signal.
2254
2255 \note A state machine will not run without a running event loop, such as
2256 the main application event loop started with QCoreApplication::exec() or
2257 QApplication::exec().
2258
2259 \sa runningChanged(), setRunning(), stop(), finished()
2260*/
2261void QScxmlStateMachine::start()
2262{
2263 Q_D(QScxmlStateMachine);
2264
2265 if (!parseErrors().isEmpty())
2266 return;
2267
2268 // Failure to initialize doesn't prevent start(). See w3c-ecma/test487 in the scion test suite.
2269 if (!isInitialized() && !init())
2270 qCDebug(qscxmlLog) << this << "cannot be initialized on start(). Starting anyway ...";
2271
2272 d->start();
2273 d->m_eventLoopHook.queueProcessEvents();
2274}
2275
2276/*!
2277 \qmlmethod ScxmlStateMachine::stop()
2278
2279 Stops this state machine. The machine will not execute any further state
2280 transitions. Its \l running property is set to \c false.
2281
2282 \sa start(), finished()
2283*/
2284
2285/*!
2286 Stops this state machine. The machine will not execute any further state
2287 transitions. Its \c running property is set to \c false.
2288
2289 \sa runningChanged(), start(), setRunning()
2290 */
2291void QScxmlStateMachine::stop()
2292{
2293 Q_D(QScxmlStateMachine);
2294 d->pause();
2295}
2296
2297/*!
2298 Returns \c true if the state with the ID \a stateIndex is active.
2299
2300 This method is part of the interface to the compiled representation of SCXML
2301 state machines. It should only be used internally and by state machines
2302 compiled from SCXML documents.
2303 */
2304bool QScxmlStateMachine::isActive(int stateIndex) const
2305{
2306 Q_D(const QScxmlStateMachine);
2307 // Here we need to find the actual internal state index that corresponds with the
2308 // index of the compiled metaobject (which is same as its mapped signal index).
2309 // See updateMetaCache()
2310 const int mappedStateIndex = d->m_stateIndexToSignalIndex.key(value: stateIndex, defaultKey: -1);
2311 return d->m_configuration.contains(i: mappedStateIndex);
2312}
2313
2314QT_END_NAMESPACE
2315

source code of qtscxml/src/scxml/qscxmlstatemachine.cpp