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

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