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 \nativetype 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 const QScxmlTableData *tableData = m_tableData.valueBypassingBindings();
679 if (!tableData)
680 return;
681
682 if (!m_stateTable)
683 return;
684
685 int signalIndex = 0;
686 const int methodOffset = QMetaObjectPrivate::signalOffset(m: m_metaObject);
687 for (int i = 0; i < m_stateTable->stateCount; ++i) {
688 const auto &s = m_stateTable->state(idx: i);
689 if (!s.isHistoryState() && s.type != StateTable::State::Invalid) {
690 m_stateIndexToSignalIndex.insert(key: i, value: signalIndex);
691 m_stateNameToSignalIndex.insert(key: tableData->string(id: s.name),
692 value: signalIndex + methodOffset);
693
694 ++signalIndex;
695 }
696 }
697}
698
699QStringList QScxmlStateMachinePrivate::stateNames(const std::vector<int> &stateIndexes) const
700{
701 QStringList names;
702 for (int idx : stateIndexes)
703 names.append(t: m_tableData.value()->string(id: m_stateTable->state(idx).name));
704 return names;
705}
706
707std::vector<int> QScxmlStateMachinePrivate::historyStates(int stateIdx) const {
708 const StateTable::Array kids = m_stateTable->array(idx: m_stateTable->state(idx: stateIdx).childStates);
709 std::vector<int> res;
710 if (!kids.isValid()) return res;
711 for (int k : kids) {
712 if (m_stateTable->state(idx: k).isHistoryState())
713 res.push_back(x: k);
714 }
715 return res;
716}
717
718void QScxmlStateMachinePrivate::exitInterpreter()
719{
720 qCDebug(qscxmlLog) << q_func() << "exiting SCXML processing";
721
722 for (auto it : m_delayedEvents) {
723 m_eventLoopHook.killTimer(id: it.first);
724 delete it.second;
725 }
726 m_delayedEvents.clear();
727
728 auto statesToExitSorted = m_configuration.list();
729 std::sort(first: statesToExitSorted.begin(), last: statesToExitSorted.end(), comp: std::greater<int>());
730 for (int stateIndex : statesToExitSorted) {
731 const auto &state = m_stateTable->state(idx: stateIndex);
732 if (state.exitInstructions != StateTable::InvalidIndex) {
733 m_executionEngine->execute(ip: state.exitInstructions);
734 }
735 removeService(invokingState: stateIndex);
736 if (state.type == StateTable::State::Final && state.parentIsScxmlElement()) {
737 returnDoneEvent(doneData: state.doneData);
738 }
739 }
740}
741
742void QScxmlStateMachinePrivate::returnDoneEvent(QScxmlExecutableContent::ContainerId doneData)
743{
744 Q_Q(QScxmlStateMachine);
745
746 m_executionEngine->execute(ip: doneData, extraData: QVariant());
747 if (m_isInvoked) {
748 auto e = new QScxmlEvent;
749 e->setName(QStringLiteral("done.invoke.") + q->sessionId());
750 e->setInvokeId(q->sessionId());
751 QScxmlStateMachinePrivate::get(t: m_parentStateMachine)->postEvent(event: e);
752 }
753}
754
755bool QScxmlStateMachinePrivate::nameMatch(const StateTable::Array &patterns,
756 QScxmlEvent *event) const
757{
758 const QString eventName = event->name();
759 bool selected = false;
760 for (int eventSelectorIter = 0; eventSelectorIter < patterns.size(); ++eventSelectorIter) {
761 QString eventStr = m_tableData.value()->string(id: patterns[eventSelectorIter]);
762 if (eventStr == QStringLiteral("*")) {
763 selected = true;
764 break;
765 }
766 if (eventStr.endsWith(QStringLiteral(".*")))
767 eventStr.chop(n: 2);
768 if (eventName.startsWith(s: eventStr)) {
769 QChar nextC = QLatin1Char('.');
770 if (eventName.size() > eventStr.size())
771 nextC = eventName.at(i: eventStr.size());
772 if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) {
773 selected = true;
774 break;
775 }
776 }
777 }
778 return selected;
779}
780
781void QScxmlStateMachinePrivate::selectTransitions(OrderedSet &enabledTransitions,
782 const std::vector<int> &configInDocumentOrder,
783 QScxmlEvent *event) const
784{
785 if (event == nullptr) {
786 qCDebug(qscxmlLog) << q_func() << "selectEventlessTransitions";
787 } else {
788 qCDebug(qscxmlLog) << q_func() << "selectTransitions with event"
789 << QScxmlEventPrivate::debugString(event).constData();
790 }
791
792 std::vector<int> states;
793 states.reserve(n: 16);
794 for (int configStateIdx : configInDocumentOrder) {
795 if (m_stateTable->state(idx: configStateIdx).isAtomic()) {
796 states.clear();
797 states.push_back(x: configStateIdx);
798 getProperAncestors(ancestors: &states, state1: configStateIdx, state2: -1);
799 for (int stateIdx : states) {
800 bool finishedWithThisConfigState = false;
801
802 if (stateIdx == -1) {
803 // the state machine has no transitions (other than the initial one, which has
804 // already been taken at this point)
805 continue;
806 }
807 const auto &state = m_stateTable->state(idx: stateIdx);
808 const StateTable::Array transitions = m_stateTable->array(idx: state.transitions);
809 if (!transitions.isValid())
810 continue;
811 std::vector<int> sortedTransitions(transitions.size(), -1);
812 std::copy(first: transitions.begin(), last: transitions.end(), result: sortedTransitions.begin());
813 for (int transitionIndex : sortedTransitions) {
814 const StateTable::Transition &t = m_stateTable->transition(idx: transitionIndex);
815 bool enabled = false;
816 if (event == nullptr) {
817 if (t.events == -1) {
818 if (t.condition == -1) {
819 enabled = true;
820 } else {
821 bool ok = false;
822 enabled = m_dataModel.value()->evaluateToBool(id: t.condition, ok: &ok) && ok;
823 }
824 }
825 } else {
826 if (t.events != -1 && nameMatch(patterns: m_stateTable->array(idx: t.events), event)) {
827 if (t.condition == -1) {
828 enabled = true;
829 } else {
830 bool ok = false;
831 enabled = m_dataModel.value()->evaluateToBool(id: t.condition, ok: &ok) && ok;
832 }
833 }
834 }
835 if (enabled) {
836 enabledTransitions.add(i: transitionIndex);
837 finishedWithThisConfigState = true;
838 break; // stop iterating over transitions
839 }
840 }
841
842 if (finishedWithThisConfigState)
843 break; // stop iterating over ancestors
844 }
845 }
846 }
847 if (!enabledTransitions.isEmpty())
848 removeConflictingTransitions(enabledTransitions: &enabledTransitions);
849}
850
851void QScxmlStateMachinePrivate::removeConflictingTransitions(OrderedSet *enabledTransitions) const
852{
853 Q_ASSERT(enabledTransitions);
854
855 auto sortedTransitions = enabledTransitions->takeList();
856 std::sort(first: sortedTransitions.begin(), last: sortedTransitions.end(), comp: [this](int t1, int t2) -> bool {
857 auto descendantDepth = [this](int state, int ancestor)->int {
858 int depth = 0;
859 for (int it = state; it != -1; it = m_stateTable->state(idx: it).parent) {
860 if (it == ancestor)
861 break;
862 ++depth;
863 }
864 return depth;
865 };
866
867 const auto &s1 = m_stateTable->transition(idx: t1).source;
868 const auto &s2 = m_stateTable->transition(idx: t2).source;
869 if (s1 == s2) {
870 return t1 < t2;
871 } else if (isDescendant(state1: s1, state2: s2)) {
872 return true;
873 } else if (isDescendant(state1: s2, state2: s1)) {
874 return false;
875 } else {
876 const int lcca = findLCCA(states: { s1, s2 });
877 const int s1Depth = descendantDepth(s1, lcca);
878 const int s2Depth = descendantDepth(s2, lcca);
879 if (s1Depth == s2Depth)
880 return s1 < s2;
881 else
882 return s1Depth > s2Depth;
883 }
884 });
885
886 OrderedSet filteredTransitions;
887 for (int t1 : sortedTransitions) {
888 OrderedSet transitionsToRemove;
889 bool t1Preempted = false;
890 OrderedSet exitSetT1;
891 computeExitSet(enabledTransitions: {t1}, statesToExit&: exitSetT1);
892 const int source1 = m_stateTable->transition(idx: t1).source;
893 for (int t2 : filteredTransitions) {
894 OrderedSet exitSetT2;
895 computeExitSet(enabledTransitions: {t2}, statesToExit&: exitSetT2);
896 if (exitSetT1.intersectsWith(other: exitSetT2)) {
897 const int source2 = m_stateTable->transition(idx: t2).source;
898 if (isDescendant(state1: source1, state2: source2)) {
899 transitionsToRemove.add(i: t2);
900 } else {
901 t1Preempted = true;
902 break;
903 }
904 }
905 }
906 if (!t1Preempted) {
907 for (int t3 : transitionsToRemove) {
908 filteredTransitions.remove(i: t3);
909 }
910 filteredTransitions.add(i: t1);
911 }
912 }
913 *enabledTransitions = filteredTransitions;
914}
915
916void QScxmlStateMachinePrivate::getProperAncestors(std::vector<int> *ancestors, int state1,
917 int state2) const
918{
919 Q_ASSERT(ancestors);
920
921 if (state1 == -1) {
922 return;
923 }
924
925 int parent = state1;
926 do {
927 parent = m_stateTable->state(idx: parent).parent;
928 if (parent == state2) {
929 break;
930 }
931 ancestors->push_back(x: parent);
932 } while (parent != -1);
933}
934
935void QScxmlStateMachinePrivate::microstep(const OrderedSet &enabledTransitions)
936{
937 if (qscxmlLog().isDebugEnabled()) {
938 qCDebug(qscxmlLog) << q_func()
939 << "starting microstep, configuration:"
940 << stateNames(stateIndexes: m_configuration.list());
941 qCDebug(qscxmlLog) << q_func() << "enabled transitions:";
942 for (int t : enabledTransitions) {
943 const auto &transition = m_stateTable->transition(idx: t);
944 QString from = QStringLiteral("(none)");
945 if (transition.source != StateTable::InvalidIndex)
946 from = m_tableData.value()->string(id: m_stateTable->state(idx: transition.source).name);
947 QStringList to;
948 if (transition.targets == StateTable::InvalidIndex) {
949 to.append(QStringLiteral("(none)"));
950 } else {
951 for (int t : m_stateTable->array(idx: transition.targets))
952 to.append(t: m_tableData.value()->string(id: m_stateTable->state(idx: t).name));
953 }
954 qCDebug(qscxmlLog) << q_func() << "\t" << t << ":" << from << "->"
955 << to.join(sep: QLatin1Char(','));
956 }
957 }
958
959 exitStates(enabledTransitions);
960 executeTransitionContent(enabledTransitions);
961 enterStates(enabledTransitions);
962
963 qCDebug(qscxmlLog) << q_func() << "finished microstep, configuration:"
964 << stateNames(stateIndexes: m_configuration.list());
965}
966
967void QScxmlStateMachinePrivate::exitStates(const OrderedSet &enabledTransitions)
968{
969 OrderedSet statesToExit;
970 computeExitSet(enabledTransitions, statesToExit);
971 auto statesToExitSorted = statesToExit.takeList();
972 std::sort(first: statesToExitSorted.begin(), last: statesToExitSorted.end(), comp: std::greater<int>());
973 qCDebug(qscxmlLog) << q_func() << "exiting states" << stateNames(stateIndexes: statesToExitSorted);
974 for (int s : statesToExitSorted) {
975 const auto &state = m_stateTable->state(idx: s);
976 if (state.serviceFactoryIds != StateTable::InvalidIndex)
977 m_statesToInvoke.remove(value: s);
978 }
979 for (int s : statesToExitSorted) {
980 for (int h : historyStates(stateIdx: s)) {
981 const auto &hState = m_stateTable->state(idx: h);
982 QList<int> history;
983
984 for (int s0 : m_configuration) {
985 const auto &s0State = m_stateTable->state(idx: s0);
986 if (hState.type == StateTable::State::DeepHistory) {
987 if (s0State.isAtomic() && isDescendant(state1: s0, state2: s))
988 history.append(t: s0);
989 } else {
990 if (s0State.parent == s)
991 history.append(t: s0);
992 }
993 }
994
995 m_historyValue[h] = history;
996 }
997 }
998 for (int s : statesToExitSorted) {
999 const auto &state = m_stateTable->state(idx: s);
1000 if (state.exitInstructions != StateTable::InvalidIndex)
1001 m_executionEngine->execute(ip: state.exitInstructions);
1002 m_configuration.remove(i: s);
1003 emitStateActive(stateIndex: s, active: false);
1004 removeService(invokingState: s);
1005 }
1006
1007 if (m_infoSignalProxy) {
1008 emit m_infoSignalProxy->statesExited(
1009 states: QList<QScxmlStateMachineInfo::StateId>(statesToExitSorted.begin(),
1010 statesToExitSorted.end()));
1011 }
1012}
1013
1014void QScxmlStateMachinePrivate::computeExitSet(const OrderedSet &enabledTransitions,
1015 OrderedSet &statesToExit) const
1016{
1017 for (int t : enabledTransitions) {
1018 const auto &transition = m_stateTable->transition(idx: t);
1019 if (transition.targets == StateTable::InvalidIndex) {
1020 // nothing to do here: there is no exit set
1021 } else {
1022 const int domain = getTransitionDomain(transitionIndex: t);
1023 for (int s : m_configuration) {
1024 if (isDescendant(state1: s, state2: domain))
1025 statesToExit.add(i: s);
1026 }
1027 }
1028 }
1029}
1030
1031void QScxmlStateMachinePrivate::executeTransitionContent(const OrderedSet &enabledTransitions)
1032{
1033 for (int t : enabledTransitions) {
1034 const auto &transition = m_stateTable->transition(idx: t);
1035 if (transition.transitionInstructions != StateTable::InvalidIndex)
1036 m_executionEngine->execute(ip: transition.transitionInstructions);
1037 }
1038
1039 if (m_infoSignalProxy) {
1040 emit m_infoSignalProxy->transitionsTriggered(
1041 transitions: QList<QScxmlStateMachineInfo::TransitionId>(enabledTransitions.list().begin(),
1042 enabledTransitions.list().end()));
1043 }
1044}
1045
1046void QScxmlStateMachinePrivate::enterStates(const OrderedSet &enabledTransitions)
1047{
1048 Q_Q(QScxmlStateMachine);
1049
1050 OrderedSet statesToEnter, statesForDefaultEntry;
1051 HistoryContent defaultHistoryContent;
1052 computeEntrySet(enabledTransitions, statesToEnter: &statesToEnter, statesForDefaultEntry: &statesForDefaultEntry,
1053 defaultHistoryContent: &defaultHistoryContent);
1054 auto sortedStates = statesToEnter.takeList();
1055 std::sort(first: sortedStates.begin(), last: sortedStates.end());
1056 qCDebug(qscxmlLog) << q_func() << "entering states" << stateNames(stateIndexes: sortedStates);
1057 for (int s : sortedStates) {
1058 const auto &state = m_stateTable->state(idx: s);
1059 m_configuration.add(i: s);
1060 if (state.serviceFactoryIds != StateTable::InvalidIndex)
1061 m_statesToInvoke.insert(value: s);
1062 if (m_stateTable->binding == StateTable::LateBinding && m_isFirstStateEntry[s]) {
1063 if (state.initInstructions != StateTable::InvalidIndex)
1064 m_executionEngine->execute(ip: state.initInstructions);
1065 m_isFirstStateEntry[s] = false;
1066 }
1067 if (state.entryInstructions != StateTable::InvalidIndex)
1068 m_executionEngine->execute(ip: state.entryInstructions);
1069 if (statesForDefaultEntry.contains(i: s)) {
1070 const auto &initialTransition = m_stateTable->transition(idx: state.initialTransition);
1071 if (initialTransition.transitionInstructions != StateTable::InvalidIndex)
1072 m_executionEngine->execute(ip: initialTransition.transitionInstructions);
1073 }
1074 const int dhc = defaultHistoryContent.value(idx: s);
1075 if (dhc != StateTable::InvalidIndex)
1076 m_executionEngine->execute(ip: dhc);
1077 if (state.type == StateTable::State::Final) {
1078 if (state.parentIsScxmlElement()) {
1079 bool running = isRunnable() && !isPaused();
1080 m_runningState = Finished;
1081 if (running)
1082 emit q->runningChanged(running: false);
1083 } else {
1084 const auto &parent = m_stateTable->state(idx: state.parent);
1085 m_executionEngine->execute(ip: state.doneData, extraData: m_tableData.value()->string(id: parent.name));
1086 if (parent.parent != StateTable::InvalidIndex) {
1087 const auto &grandParent = m_stateTable->state(idx: parent.parent);
1088 if (grandParent.isParallel()) {
1089 if (allInFinalStates(states: getChildStates(state: grandParent))) {
1090 auto e = new QScxmlEvent;
1091 e->setEventType(QScxmlEvent::InternalEvent);
1092 e->setName(QStringLiteral("done.state.")
1093 + m_tableData.value()->string(id: grandParent.name));
1094 q->submitEvent(event: e);
1095 }
1096 }
1097 }
1098 }
1099 }
1100 }
1101 for (int s : sortedStates)
1102 emitStateActive(stateIndex: s, active: true);
1103 if (m_infoSignalProxy) {
1104 emit m_infoSignalProxy->statesEntered(
1105 states: QList<QScxmlStateMachineInfo::StateId>(sortedStates.begin(),
1106 sortedStates.end()));
1107 }
1108}
1109
1110void QScxmlStateMachinePrivate::computeEntrySet(const OrderedSet &enabledTransitions,
1111 OrderedSet *statesToEnter,
1112 OrderedSet *statesForDefaultEntry,
1113 HistoryContent *defaultHistoryContent) const
1114{
1115 Q_ASSERT(statesToEnter);
1116 Q_ASSERT(statesForDefaultEntry);
1117 Q_ASSERT(defaultHistoryContent);
1118
1119 for (int t : enabledTransitions) {
1120 const auto &transition = m_stateTable->transition(idx: t);
1121 if (transition.targets == StateTable::InvalidIndex)
1122 // targetless transition, so nothing to do
1123 continue;
1124 for (int s : m_stateTable->array(idx: transition.targets))
1125 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1126 defaultHistoryContent);
1127 auto ancestor = getTransitionDomain(transitionIndex: t);
1128 OrderedSet targets;
1129 getEffectiveTargetStates(targets: &targets, transitionIndex: t);
1130 for (auto s : targets)
1131 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: ancestor, statesToEnter, statesForDefaultEntry,
1132 defaultHistoryContent);
1133 }
1134}
1135
1136void QScxmlStateMachinePrivate::addDescendantStatesToEnter(
1137 int stateIndex, OrderedSet *statesToEnter, OrderedSet *statesForDefaultEntry,
1138 HistoryContent *defaultHistoryContent) const
1139{
1140 Q_ASSERT(statesToEnter);
1141 Q_ASSERT(statesForDefaultEntry);
1142 Q_ASSERT(defaultHistoryContent);
1143
1144 const auto &state = m_stateTable->state(idx: stateIndex);
1145 if (state.isHistoryState()) {
1146 HistoryValues::const_iterator historyValueIter = m_historyValue.find(key: stateIndex);
1147 if (historyValueIter != m_historyValue.end()) {
1148 auto historyValue = historyValueIter.value();
1149 for (int s : historyValue)
1150 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1151 defaultHistoryContent);
1152 for (int s : historyValue)
1153 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: state.parent, statesToEnter, statesForDefaultEntry,
1154 defaultHistoryContent);
1155 } else {
1156 int transitionIdx = StateTable::InvalidIndex;
1157 if (state.transitions == StateTable::InvalidIndex) {
1158 int parentInitialTransition = m_stateTable->state(idx: state.parent).initialTransition;
1159 if (parentInitialTransition == StateTable::InvalidIndex)
1160 return;
1161 transitionIdx = parentInitialTransition;
1162 } else {
1163 transitionIdx = m_stateTable->array(idx: state.transitions)[0];
1164 }
1165 const auto &defaultHistoryTransition = m_stateTable->transition(idx: transitionIdx);
1166 defaultHistoryContent->operator[](idx: state.parent) =
1167 defaultHistoryTransition.transitionInstructions;
1168 StateTable::Array targetStates = m_stateTable->array(idx: defaultHistoryTransition.targets);
1169 for (int s : targetStates)
1170 addDescendantStatesToEnter(stateIndex: s, statesToEnter, statesForDefaultEntry,
1171 defaultHistoryContent);
1172 for (int s : targetStates)
1173 addAncestorStatesToEnter(stateIndex: s, ancestorIndex: state.parent, statesToEnter, statesForDefaultEntry,
1174 defaultHistoryContent);
1175 }
1176 } else {
1177 statesToEnter->add(i: stateIndex);
1178 if (state.isCompound()) {
1179 statesForDefaultEntry->add(i: stateIndex);
1180 if (state.initialTransition != StateTable::InvalidIndex) {
1181 auto initialTransition = m_stateTable->transition(idx: state.initialTransition);
1182 auto initialTransitionTargets = m_stateTable->array(idx: initialTransition.targets);
1183 for (int targetStateIndex : initialTransitionTargets)
1184 addDescendantStatesToEnter(stateIndex: targetStateIndex, statesToEnter,
1185 statesForDefaultEntry, defaultHistoryContent);
1186 for (int targetStateIndex : initialTransitionTargets)
1187 addAncestorStatesToEnter(stateIndex: targetStateIndex, ancestorIndex: stateIndex, statesToEnter,
1188 statesForDefaultEntry, defaultHistoryContent);
1189 }
1190 } else {
1191 if (state.isParallel()) {
1192 for (int child : getChildStates(state)) {
1193 if (!hasDescendant(statesToEnter: *statesToEnter, childIdx: child))
1194 addDescendantStatesToEnter(stateIndex: child, statesToEnter, statesForDefaultEntry,
1195 defaultHistoryContent);
1196 }
1197 }
1198 }
1199 }
1200}
1201
1202void QScxmlStateMachinePrivate::addAncestorStatesToEnter(
1203 int stateIndex, int ancestorIndex, OrderedSet *statesToEnter,
1204 OrderedSet *statesForDefaultEntry, HistoryContent *defaultHistoryContent) const
1205{
1206 Q_ASSERT(statesToEnter);
1207 Q_ASSERT(statesForDefaultEntry);
1208 Q_ASSERT(defaultHistoryContent);
1209
1210 std::vector<int> ancestors;
1211 getProperAncestors(ancestors: &ancestors, state1: stateIndex, state2: ancestorIndex);
1212 for (int anc : ancestors) {
1213 if (anc == -1) {
1214 // we can't enter the state machine itself, so:
1215 continue;
1216 }
1217 statesToEnter->add(i: anc);
1218 const auto &ancState = m_stateTable->state(idx: anc);
1219 if (ancState.isParallel()) {
1220 for (int child : getChildStates(state: ancState)) {
1221 if (!hasDescendant(statesToEnter: *statesToEnter, childIdx: child))
1222 addDescendantStatesToEnter(stateIndex: child, statesToEnter, statesForDefaultEntry,
1223 defaultHistoryContent);
1224 }
1225 }
1226 }
1227}
1228
1229std::vector<int> QScxmlStateMachinePrivate::getChildStates(
1230 const QScxmlExecutableContent::StateTable::State &state) const
1231{
1232 std::vector<int> childStates;
1233 auto kids = m_stateTable->array(idx: state.childStates);
1234 if (kids.isValid()) {
1235 childStates.reserve(n: kids.size());
1236 for (int kiddo : kids) {
1237 switch (m_stateTable->state(idx: kiddo).type) {
1238 case StateTable::State::Normal:
1239 case StateTable::State::Final:
1240 case StateTable::State::Parallel:
1241 childStates.push_back(x: kiddo);
1242 break;
1243 default:
1244 break;
1245 }
1246 }
1247 }
1248 return childStates;
1249}
1250
1251bool QScxmlStateMachinePrivate::hasDescendant(const OrderedSet &statesToEnter, int childIdx) const
1252{
1253 for (int s : statesToEnter) {
1254 if (isDescendant(state1: s, state2: childIdx))
1255 return true;
1256 }
1257 return false;
1258}
1259
1260bool QScxmlStateMachinePrivate::allDescendants(const OrderedSet &statesToEnter, int childdx) const
1261{
1262 for (int s : statesToEnter) {
1263 if (!isDescendant(state1: s, state2: childdx))
1264 return false;
1265 }
1266 return true;
1267}
1268
1269bool QScxmlStateMachinePrivate::isDescendant(int state1, int state2) const
1270{
1271 int parent = state1;
1272 do {
1273 parent = m_stateTable->state(idx: parent).parent;
1274 if (parent == state2)
1275 return true;
1276 } while (parent != -1);
1277 return false;
1278}
1279
1280bool QScxmlStateMachinePrivate::allInFinalStates(const std::vector<int> &states) const
1281{
1282 if (states.empty())
1283 return false;
1284
1285 for (int idx : states) {
1286 if (!isInFinalState(stateIndex: idx))
1287 return false;
1288 }
1289
1290 return true;
1291}
1292
1293bool QScxmlStateMachinePrivate::someInFinalStates(const std::vector<int> &states) const
1294{
1295 for (int stateIndex : states) {
1296 const auto &state = m_stateTable->state(idx: stateIndex);
1297 if (state.type == StateTable::State::Final && m_configuration.contains(i: stateIndex))
1298 return true;
1299 }
1300 return false;
1301}
1302
1303bool QScxmlStateMachinePrivate::isInFinalState(int stateIndex) const
1304{
1305 const auto &state = m_stateTable->state(idx: stateIndex);
1306 if (state.isCompound())
1307 return someInFinalStates(states: getChildStates(state)) && m_configuration.contains(i: stateIndex);
1308 else if (state.isParallel())
1309 return allInFinalStates(states: getChildStates(state));
1310 else
1311 return false;
1312}
1313
1314int QScxmlStateMachinePrivate::getTransitionDomain(int transitionIndex) const
1315{
1316 const auto &transition = m_stateTable->transition(idx: transitionIndex);
1317 if (transition.source == -1)
1318 //oooh, we have the initial transition of the state machine.
1319 return -1;
1320
1321 OrderedSet tstates;
1322 getEffectiveTargetStates(targets: &tstates, transitionIndex);
1323 if (tstates.isEmpty()) {
1324 return StateTable::InvalidIndex;
1325 } else {
1326 const auto &sourceState = m_stateTable->state(idx: transition.source);
1327 if (transition.type == StateTable::Transition::Internal
1328 && sourceState.isCompound()
1329 && allDescendants(statesToEnter: tstates, childdx: transition.source)) {
1330 return transition.source;
1331 } else {
1332 tstates.add(i: transition.source);
1333 return findLCCA(states: std::move(tstates));
1334 }
1335 }
1336}
1337
1338int QScxmlStateMachinePrivate::findLCCA(OrderedSet &&states) const
1339{
1340 std::vector<int> ancestors;
1341 const int head = *states.begin();
1342 OrderedSet tail(std::move(states));
1343 tail.removeHead();
1344
1345 getProperAncestors(ancestors: &ancestors, state1: head, state2: StateTable::InvalidIndex);
1346 for (int anc : ancestors) {
1347 if (anc != -1) { // the state machine itself is always compound
1348 const auto &ancState = m_stateTable->state(idx: anc);
1349 if (!ancState.isCompound())
1350 continue;
1351 }
1352
1353 if (allDescendants(statesToEnter: tail, childdx: anc))
1354 return anc;
1355 }
1356
1357 return StateTable::InvalidIndex;
1358}
1359
1360void QScxmlStateMachinePrivate::getEffectiveTargetStates(OrderedSet *targets,
1361 int transitionIndex) const
1362{
1363 Q_ASSERT(targets);
1364
1365 const auto &transition = m_stateTable->transition(idx: transitionIndex);
1366 for (int s : m_stateTable->array(idx: transition.targets)) {
1367 const auto &state = m_stateTable->state(idx: s);
1368 if (state.isHistoryState()) {
1369 HistoryValues::const_iterator historyValueIter = m_historyValue.find(key: s);
1370 if (historyValueIter != m_historyValue.end()) {
1371 for (int historyState : historyValueIter.value()) {
1372 targets->add(i: historyState);
1373 }
1374 } else if (state.transitions != StateTable::InvalidIndex) {
1375 getEffectiveTargetStates(targets, transitionIndex: m_stateTable->array(idx: state.transitions)[0]);
1376 }
1377 } else {
1378 targets->add(i: s);
1379 }
1380 }
1381}
1382
1383/*!
1384 * Creates a state machine from the SCXML file specified by \a fileName.
1385 *
1386 * This method will always return a state machine. If errors occur while reading the SCXML file,
1387 * the state machine cannot be started. The errors can be retrieved by calling the parseErrors()
1388 * method.
1389 *
1390 * \sa parseErrors()
1391 */
1392QScxmlStateMachine *QScxmlStateMachine::fromFile(const QString &fileName)
1393{
1394 QFile scxmlFile(fileName);
1395 if (!scxmlFile.open(flags: QIODevice::ReadOnly)) {
1396 auto stateMachine = new QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject);
1397 QScxmlError err(scxmlFile.fileName(), 0, 0, QStringLiteral("cannot open for reading"));
1398 QScxmlStateMachinePrivate::get(t: stateMachine)->parserData()->m_errors.append(t: err);
1399 return stateMachine;
1400 }
1401
1402 QScxmlStateMachine *stateMachine = fromData(data: &scxmlFile, fileName);
1403 scxmlFile.close();
1404 return stateMachine;
1405}
1406
1407/*!
1408 * Creates a state machine by reading from the QIODevice specified by \a data.
1409 *
1410 * This method will always return a state machine. If errors occur while reading the SCXML file,
1411 * \a fileName, the state machine cannot be started. The errors can be retrieved by calling the
1412 * parseErrors() method.
1413 *
1414 * \sa parseErrors()
1415 */
1416QScxmlStateMachine *QScxmlStateMachine::fromData(QIODevice *data, const QString &fileName)
1417{
1418 QXmlStreamReader xmlReader(data);
1419 QScxmlCompiler compiler(&xmlReader);
1420 compiler.setFileName(fileName);
1421 return compiler.compile();
1422}
1423
1424QList<QScxmlError> QScxmlStateMachine::parseErrors() const
1425{
1426 Q_D(const QScxmlStateMachine);
1427 return d->m_parserData ? d->m_parserData->m_errors : QList<QScxmlError>();
1428}
1429
1430QScxmlStateMachine::QScxmlStateMachine(const QMetaObject *metaObject, QObject *parent)
1431 : QObject(*new QScxmlStateMachinePrivate(metaObject), parent)
1432{
1433 Q_D(QScxmlStateMachine);
1434 d->m_executionEngine = new QScxmlExecutionEngine(this);
1435}
1436
1437QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent)
1438 : QObject(dd, parent)
1439{
1440 Q_D(QScxmlStateMachine);
1441 d->m_executionEngine = new QScxmlExecutionEngine(this);
1442}
1443
1444/*!
1445 \property QScxmlStateMachine::running
1446
1447 \brief the running state of this state machine
1448
1449 \sa start()
1450 */
1451
1452/*!
1453 \qmlproperty bool ScxmlStateMachine::running
1454
1455 The running state of this state machine.
1456*/
1457
1458/*!
1459 \property QScxmlStateMachine::dataModel
1460
1461 \brief The data model to be used for this state machine.
1462
1463 SCXML data models are described in
1464 \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
1465 information about supported data models, see \l {SCXML Compliance}.
1466
1467 Changing the data model when the state machine has been \c initialized is
1468 not specified in the SCXML standard and leads to undefined behavior.
1469
1470 \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
1471*/
1472
1473/*!
1474 \qmlproperty ScxmlDataModel ScxmlStateMachine::dataModel
1475
1476 The data model to be used for this state machine.
1477
1478 SCXML data models are described in
1479 \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
1480 information about supported data models, see \l {SCXML Compliance}.
1481
1482 Changing the data model when the state machine has been \l initialized is
1483 not specified in the SCXML standard and leads to undefined behavior.
1484
1485 \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
1486*/
1487
1488/*!
1489 \property QScxmlStateMachine::initialized
1490
1491 \brief Whether the state machine has been initialized.
1492
1493 It is \c true if the state machine has been initialized, \c false otherwise.
1494
1495 \sa QScxmlStateMachine::init(), QScxmlDataModel
1496*/
1497
1498/*!
1499 \qmlproperty bool ScxmlStateMachine::initialized
1500
1501 This read-only property is set to \c true if the state machine has been
1502 initialized, \c false otherwise.
1503*/
1504
1505/*!
1506 \property QScxmlStateMachine::initialValues
1507
1508 \brief The initial values to be used for setting up the data model.
1509
1510 \sa QScxmlStateMachine::init(), QScxmlDataModel
1511*/
1512
1513/*!
1514 \qmlproperty var ScxmlStateMachine::initialValues
1515
1516 The initial values to be used for setting up the data model.
1517*/
1518
1519/*!
1520 \property QScxmlStateMachine::sessionId
1521
1522 \brief The session ID of the current state machine.
1523
1524 The session ID is used for message routing between parent and child state machines. If a state
1525 machine is started by an \c <invoke> element, any event it sends will have the \c invokeid field
1526 set to the session ID. The state machine will use the origin of an event (which is set by the
1527 \e target or \e targetexpr attribute in a \c <send> element) to dispatch messages to the correct
1528 child state machine.
1529
1530 \sa QScxmlEvent::invokeId()
1531 */
1532
1533/*!
1534 \qmlproperty string ScxmlStateMachine::sessionId
1535
1536 The session ID of the current state machine.
1537
1538 The session ID is used for message routing between parent and child state
1539 machines. If a state machine is started by an \c <invoke> element, any event
1540 it sends will have the \c invokeid field set to the session ID. The state
1541 machine will use the origin of an event (which is set by the \e target or
1542 \e targetexpr attribute in a \c <send> element) to dispatch messages to the
1543 correct child state machine.
1544*/
1545
1546/*!
1547 \property QScxmlStateMachine::name
1548
1549 \brief The name of the state machine as set by the \e name attribute of the \c <scxml> tag.
1550 */
1551
1552/*!
1553 \qmlproperty string ScxmlStateMachine::name
1554
1555 The name of the state machine as set by the \e name attribute of the
1556 \c <scxml> tag.
1557*/
1558
1559/*!
1560 \property QScxmlStateMachine::invoked
1561
1562 \brief Whether the state machine was invoked from an outer state machine.
1563
1564 \c true when the state machine was started as a service with the \c <invoke> element,
1565 \c false otherwise.
1566 */
1567
1568/*!
1569 \qmlproperty bool ScxmlStateMachine::invoked
1570
1571 Whether the state machine was invoked from an outer state machine.
1572
1573 This read-only property is set to \c true when the state machine was started
1574 as a service with the \c <invoke> element, \c false otherwise.
1575 */
1576
1577/*!
1578 \property QScxmlStateMachine::parseErrors
1579
1580 \brief The list of parse errors that occurred while creating a state machine from an SCXML file.
1581 */
1582
1583/*!
1584 \qmlproperty var ScxmlStateMachine::parseErrors
1585
1586 The list of parse errors that occurred while creating a state machine from
1587 an SCXML file.
1588 */
1589
1590/*!
1591 \property QScxmlStateMachine::loader
1592
1593 \brief The loader that is currently used to resolve and load URIs for the state machine.
1594 */
1595
1596/*!
1597 \qmlproperty Loader ScxmlStateMachine::loader
1598
1599 The loader that is currently used to resolve and load URIs for the state
1600 machine.
1601 */
1602
1603/*!
1604 \property QScxmlStateMachine::tableData
1605
1606 \brief The table data that is used when generating C++ from an SCXML file.
1607
1608 The class implementing
1609 the state machine will use this property to assign the generated table
1610 data. The state machine does not assume ownership of the table data.
1611 */
1612
1613QString QScxmlStateMachine::sessionId() const
1614{
1615 Q_D(const QScxmlStateMachine);
1616
1617 return d->m_sessionId;
1618}
1619
1620QString QScxmlStateMachinePrivate::generateSessionId(const QString &prefix)
1621{
1622 int id = ++QScxmlStateMachinePrivate::m_sessionIdCounter;
1623 return prefix + QString::number(id);
1624}
1625
1626bool QScxmlStateMachine::isInvoked() const
1627{
1628 Q_D(const QScxmlStateMachine);
1629 return d->m_isInvoked;
1630}
1631
1632bool QScxmlStateMachine::isInitialized() const
1633{
1634 Q_D(const QScxmlStateMachine);
1635 return d->m_isInitialized;
1636}
1637
1638QBindable<bool> QScxmlStateMachine::bindableInitialized() const
1639{
1640 Q_D(const QScxmlStateMachine);
1641 return &d->m_isInitialized;
1642}
1643
1644/*!
1645 * Sets the data model for this state machine to \a model. There is a 1:1
1646 * relation between state machines and models. After setting the model once you
1647 * cannot change it anymore. Any further attempts to set the model using this
1648 * method will be ignored.
1649 */
1650void QScxmlStateMachine::setDataModel(QScxmlDataModel *model)
1651{
1652 Q_D(QScxmlStateMachine);
1653
1654 if (d->m_dataModel.valueBypassingBindings() == nullptr && model != nullptr) {
1655 // the binding is removed only on the first valid set
1656 // as the later attempts are ignored
1657 d->m_dataModel.removeBindingUnlessInWrapper();
1658 d->m_dataModel.setValueBypassingBindings(model);
1659 model->setStateMachine(this);
1660 d->m_dataModel.notify();
1661 emit dataModelChanged(model);
1662 }
1663}
1664
1665/*!
1666 * Returns the data model used by the state machine.
1667 */
1668QScxmlDataModel *QScxmlStateMachine::dataModel() const
1669{
1670 Q_D(const QScxmlStateMachine);
1671
1672 return d->m_dataModel;
1673}
1674
1675QBindable<QScxmlDataModel*> QScxmlStateMachine::bindableDataModel()
1676{
1677 Q_D(QScxmlStateMachine);
1678 return &d->m_dataModel;
1679}
1680
1681void QScxmlStateMachine::setLoader(QScxmlCompiler::Loader *loader)
1682{
1683 Q_D(QScxmlStateMachine);
1684 d->m_loader.setValue(loader);
1685}
1686
1687QScxmlCompiler::Loader *QScxmlStateMachine::loader() const
1688{
1689 Q_D(const QScxmlStateMachine);
1690
1691 return d->m_loader;
1692}
1693
1694QBindable<QScxmlCompiler::Loader*> QScxmlStateMachine::bindableLoader()
1695{
1696 Q_D(QScxmlStateMachine);
1697 return &d->m_loader;
1698}
1699
1700QScxmlTableData *QScxmlStateMachine::tableData() const
1701{
1702 Q_D(const QScxmlStateMachine);
1703
1704 return d->m_tableData;
1705}
1706
1707void QScxmlStateMachine::setTableData(QScxmlTableData *tableData)
1708{
1709 Q_D(QScxmlStateMachine);
1710
1711 d->m_tableData.removeBindingUnlessInWrapper();
1712 if (d->m_tableData.valueBypassingBindings() == tableData)
1713 return;
1714
1715 d->m_tableData.setValueBypassingBindings(tableData);
1716 if (tableData) {
1717 d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>(
1718 tableData->stateMachineTable());
1719 // cannot use objectName() here, because it creates binding loop
1720 const QString currentObjectName = d->extraData
1721 ? d->extraData->objectName.valueBypassingBindings() : QString();
1722 if (currentObjectName.isEmpty()) {
1723 setObjectName(tableData->name());
1724 }
1725 if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) {
1726 const size_t serviceCount = size_t(d->m_stateTable->maxServiceId + 1);
1727 d->m_invokedServices.resize(new_size: serviceCount, x: { .invokingState: -1, .service: nullptr, .serviceName: QString() });
1728 d->m_cachedFactories.resize(new_size: serviceCount, x: nullptr);
1729 }
1730
1731 if (d->m_stateTable->version != Q_QSCXMLC_OUTPUT_REVISION) {
1732 qFatal(msg: "Cannot mix incompatible state table (version 0x%x) with this library "
1733 "(version 0x%x)", d->m_stateTable->version, Q_QSCXMLC_OUTPUT_REVISION);
1734 }
1735 Q_ASSERT(tableData->stateMachineTable()[d->m_stateTable->arrayOffset +
1736 d->m_stateTable->arraySize]
1737 == QScxmlExecutableContent::StateTable::terminator);
1738 }
1739
1740 d->updateMetaCache();
1741
1742 d->m_tableData.notify();
1743 emit tableDataChanged(tableData);
1744}
1745
1746QBindable<QScxmlTableData*> QScxmlStateMachine::bindableTableData()
1747{
1748 Q_D(QScxmlStateMachine);
1749 return &d->m_tableData;
1750}
1751
1752/*!
1753 \qmlmethod ScxmlStateMachine::stateNames(bool compress)
1754
1755 Retrieves a list of state names of all states.
1756
1757 When \a compress is \c true (the default), the states that contain child
1758 states is filtered out and only the \e {leaf states} is returned. When it
1759 is \c false, the full list of all states is returned.
1760
1761 The returned list does not contain the states of possible nested state
1762 machines.
1763
1764 \note The order of the state names in the list is the order in which the
1765 states occurred in the SCXML document.
1766*/
1767
1768/*!
1769 * Retrieves a list of state names of all states.
1770 *
1771 * When \a compress is \c true (the default), the states that contain child states
1772 * will be filtered out and only the \e {leaf states} will be returned.
1773 * When it is \c false, the full list of all states will be returned.
1774 *
1775 * The returned list does not contain the states of possible nested state machines.
1776 *
1777 * \note The order of the state names in the list is the order in which the states occurred in
1778 * the SCXML document.
1779 */
1780QStringList QScxmlStateMachine::stateNames(bool compress) const
1781{
1782 Q_D(const QScxmlStateMachine);
1783
1784 QStringList names;
1785 for (int i = 0; i < d->m_stateTable->stateCount; ++i) {
1786 const auto &state = d->m_stateTable->state(idx: i);
1787 if (!compress || state.isAtomic())
1788 names.append(t: d->m_tableData.value()->string(id: state.name));
1789 }
1790 return names;
1791}
1792
1793/*!
1794 \qmlmethod ScxmlStateMachine::activeStateNames(bool compress)
1795
1796 Retrieves a list of state names of all active states.
1797
1798 When a state is active, all its parent states are active by definition. When
1799 \a compress is \c true (the default), the parent states are filtered out and
1800 only the \e {leaf states} are returned. When it is \c false, the full list
1801 of active states is returned.
1802*/
1803
1804/*!
1805 * Retrieves a list of state names of all active states.
1806 *
1807 * When a state is active, all its parent states are active by definition. When \a compress
1808 * is \c true (the default), the parent states will be filtered out and only the \e {leaf states}
1809 * will be returned. When it is \c false, the full list of active states will be returned.
1810 */
1811QStringList QScxmlStateMachine::activeStateNames(bool compress) const
1812{
1813 Q_D(const QScxmlStateMachine);
1814
1815 QStringList result;
1816 for (int stateIdx : d->m_configuration) {
1817 const auto &state = d->m_stateTable->state(idx: stateIdx);
1818 if (state.isAtomic() || !compress)
1819 result.append(t: d->m_tableData.value()->string(id: state.name));
1820 }
1821 return result;
1822}
1823
1824/*!
1825 \qmlmethod ScxmlStateMachine::isActive(string scxmlStateName)
1826
1827 Returns \c true if the state specified by \a scxmlStateName is active,
1828 \c false otherwise.
1829*/
1830
1831/*!
1832 * Returns \c true if the state specified by \a scxmlStateName is active, \c false otherwise.
1833 */
1834bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const
1835{
1836 Q_D(const QScxmlStateMachine);
1837
1838 for (int stateIndex : d->m_configuration) {
1839 const auto &state = d->m_stateTable->state(idx: stateIndex);
1840 if (d->m_tableData.value()->string(id: state.name) == scxmlStateName)
1841 return true;
1842 }
1843
1844 return false;
1845}
1846
1847QMetaObject::Connection QScxmlStateMachine::connectToStateImpl(const QString &scxmlStateName,
1848 const QObject *receiver, void **slot,
1849 QtPrivate::QSlotObjectBase *slotObj,
1850 Qt::ConnectionType type)
1851{
1852 const int *types = nullptr;
1853 if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
1854 types = QtPrivate::ConnectionTypes<QtPrivate::List<bool> >::types();
1855
1856 Q_D(QScxmlStateMachine);
1857 const int signalIndex = d->m_stateNameToSignalIndex.value(key: scxmlStateName, defaultValue: -1);
1858 return signalIndex < 0 ? QMetaObject::Connection()
1859 : QObjectPrivate::connectImpl(sender: this, signal_index: signalIndex, receiver, slot, slotObj,
1860 type, types, senderMetaObject: d->m_metaObject);
1861}
1862
1863/*!
1864 Creates a connection of the given \a type from the state identified by \a scxmlStateName
1865 to the \a method in the \a receiver object. The receiver's \a method
1866 may take a boolean argument that indicates whether the state connected
1867 became active or inactive. For example:
1868
1869 \code
1870 void mySlot(bool active);
1871 \endcode
1872
1873 Returns a handle to the connection, which can be used later to disconnect.
1874 */
1875QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlStateName,
1876 const QObject *receiver,
1877 const char *method,
1878 Qt::ConnectionType type)
1879{
1880 QByteArray signalName = QByteArray::number(QSIGNAL_CODE) + scxmlStateName.toUtf8()
1881 + "Changed(bool)";
1882 return QObject::connect(sender: this, signal: signalName.constData(), receiver, member: method, type);
1883}
1884
1885/*!
1886 Creates a connection of the specified \a type from the event specified by
1887 \a scxmlEventSpec to the \a method in the \a receiver object. The receiver's
1888 \a method may take a QScxmlEvent as a parameter. For example:
1889
1890 \code
1891 void mySlot(const QScxmlEvent &event);
1892 \endcode
1893
1894 In contrast to event specifications in SCXML documents, spaces are not
1895 allowed in the \a scxmlEventSpec here. In order to connect to multiple
1896 events with different prefixes, connectToEvent() has to be called multiple
1897 times.
1898
1899 Returns a handle to the connection, which can be used later to disconnect.
1900*/
1901QMetaObject::Connection QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec,
1902 const QObject *receiver,
1903 const char *method,
1904 Qt::ConnectionType type)
1905{
1906 Q_D(QScxmlStateMachine);
1907 return d->m_router.connectToEvent(segments: scxmlEventSpec.split(sep: QLatin1Char('.')), receiver, method,
1908 type);
1909}
1910
1911QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &scxmlEventSpec,
1912 const QObject *receiver, void **slot,
1913 QtPrivate::QSlotObjectBase *slotObj,
1914 Qt::ConnectionType type)
1915{
1916 Q_D(QScxmlStateMachine);
1917 return d->m_router.connectToEvent(segments: scxmlEventSpec.split(sep: QLatin1Char('.')), receiver, slot,
1918 method: slotObj, type);
1919}
1920
1921/*!
1922 \qmlmethod ScxmlStateMachine::init()
1923
1924 Initializes the state machine by setting the initial values for \c <data>
1925 elements and executing any \c <script> tags of the \c <scxml> tag. The
1926 initial data values are taken from the \l initialValues property.
1927
1928 Returns \c false if parse errors occur or if any of the initialization steps
1929 fail. Returns \c true otherwise.
1930*/
1931
1932/*!
1933 * Initializes the state machine.
1934 *
1935 * State machine initialization consists of calling QScxmlDataModel::setup(), setting the initial
1936 * values for \c <data> elements, and executing any \c <script> tags of the \c <scxml> tag. The
1937 * initial data values are taken from the \c initialValues property.
1938 *
1939 * Returns \c false if parse errors occur or if any of the initialization steps fail.
1940 * Returns \c true otherwise.
1941 */
1942bool QScxmlStateMachine::init()
1943{
1944 Q_D(QScxmlStateMachine);
1945
1946 if (d->m_isInitialized.value())
1947 return false;
1948
1949 if (!parseErrors().isEmpty())
1950 return false;
1951
1952 if (!dataModel() || !dataModel()->setup(d->m_initialValues.value()))
1953 return false;
1954
1955 if (!d->executeInitialSetup())
1956 return false;
1957
1958 d->m_isInitialized.setValue(true);
1959 return true;
1960}
1961
1962/*!
1963 * Returns \c true if the state machine is running, \c false otherwise.
1964 *
1965 * \sa setRunning(), runningChanged()
1966 */
1967bool QScxmlStateMachine::isRunning() const
1968{
1969 Q_D(const QScxmlStateMachine);
1970
1971 return d->isRunnable() && !d->isPaused();
1972}
1973
1974/*!
1975 * Starts the state machine if \a running is \c true, or stops it otherwise.
1976 *
1977 * \sa start(), stop(), isRunning(), runningChanged()
1978 */
1979void QScxmlStateMachine::setRunning(bool running)
1980{
1981 if (running)
1982 start();
1983 else
1984 stop();
1985}
1986
1987QVariantMap QScxmlStateMachine::initialValues()
1988{
1989 Q_D(const QScxmlStateMachine);
1990 return d->m_initialValues;
1991}
1992
1993void QScxmlStateMachine::setInitialValues(const QVariantMap &initialValues)
1994{
1995 Q_D(QScxmlStateMachine);
1996 d->m_initialValues.setValue(initialValues);
1997}
1998
1999QBindable<QVariantMap> QScxmlStateMachine::bindableInitialValues()
2000{
2001 Q_D(QScxmlStateMachine);
2002 return &d->m_initialValues;
2003}
2004
2005QString QScxmlStateMachine::name() const
2006{
2007 return tableData()->name();
2008}
2009
2010/*!
2011 \qmlmethod ScxmlStateMachine::submitEvent(event)
2012
2013 Submits the SCXML event \a event to the internal or external event queue
2014 depending on the priority of the event.
2015
2016 When a delay is set, the event will be queued for delivery after the timeout
2017 has passed. The state machine takes ownership of the event and deletes it
2018 after processing.
2019
2020 \sa QScxmlEvent
2021 */
2022
2023/*!
2024 * Submits the SCXML event \a event to the internal or external event queue depending on the
2025 * priority of the event.
2026 *
2027 * When a delay is set, the event will be queued for delivery after the timeout has passed.
2028 * The state machine takes ownership of \a event and deletes it after processing.
2029 */
2030void QScxmlStateMachine::submitEvent(QScxmlEvent *event)
2031{
2032 Q_D(QScxmlStateMachine);
2033
2034 if (!event)
2035 return;
2036
2037 if (event->delay() > 0) {
2038 qCDebug(qscxmlLog) << this << "submitting event" << event->name()
2039 << "with delay" << event->delay() << "ms:"
2040 << QScxmlEventPrivate::debugString(event).constData();
2041
2042 Q_ASSERT(event->eventType() == QScxmlEvent::ExternalEvent);
2043 d->submitDelayedEvent(event);
2044 } else {
2045 qCDebug(qscxmlLog) << this << "submitting event" << event->name()
2046 << ":" << QScxmlEventPrivate::debugString(event).constData();
2047
2048 d->routeEvent(event);
2049 }
2050}
2051
2052/*!
2053 * A utility method to create and submit an external event with the specified
2054 * \a eventName as the name.
2055 */
2056void QScxmlStateMachine::submitEvent(const QString &eventName)
2057{
2058 QScxmlEvent *e = new QScxmlEvent;
2059 e->setName(eventName);
2060 e->setEventType(QScxmlEvent::ExternalEvent);
2061 submitEvent(event: e);
2062}
2063/*!
2064 \qmlmethod ScxmlStateMachine::submitEvent(string eventName, var data)
2065
2066 A utility method to create and submit an external event with the specified
2067 \a eventName as the name and \a data as the payload data (optional).
2068*/
2069
2070/*!
2071 * A utility method to create and submit an external event with the specified
2072 * \a eventName as the name and \a data as the payload data.
2073 */
2074void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &data)
2075{
2076 QScxmlEvent *e = new QScxmlEvent;
2077 e->setName(eventName);
2078 e->setEventType(QScxmlEvent::ExternalEvent);
2079 e->setData(data);
2080 submitEvent(event: e);
2081}
2082
2083/*!
2084 \qmlmethod ScxmlStateMachine::cancelDelayedEvent(string sendId)
2085
2086 Cancels a delayed event with the specified \a sendId.
2087*/
2088
2089/*!
2090 * Cancels a delayed event with the specified \a sendId.
2091 */
2092void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId)
2093{
2094 Q_D(QScxmlStateMachine);
2095
2096 for (auto it = d->m_delayedEvents.begin(), eit = d->m_delayedEvents.end(); it != eit; ++it) {
2097 if (it->second->sendId() == sendId) {
2098 qCDebug(qscxmlLog) << this
2099 << "canceling event" << sendId
2100 << "with timer id" << it->first;
2101 d->m_eventLoopHook.killTimer(id: it->first);
2102 delete it->second;
2103 d->m_delayedEvents.erase(position: it);
2104 return;
2105 }
2106 }
2107}
2108
2109/*!
2110 \qmlmethod ScxmlStateMachine::isDispatchableTarget(string target)
2111
2112 Returns \c true if a message to \a target can be dispatched by this state
2113 machine.
2114
2115 Valid targets are:
2116 \list
2117 \li \c #_parent for the parent state machine if the current state machine
2118 is started by \c <invoke>
2119 \li \c #_internal for the current state machine
2120 \li \c #_scxml_sessionid, where \c sessionid is the session ID of the
2121 current state machine
2122 \li \c #_servicename, where \c servicename is the ID or name of a service
2123 started with \c <invoke> by this state machine
2124 \endlist
2125 */
2126
2127/*!
2128 * Returns \c true if a message to \a target can be dispatched by this state machine.
2129 *
2130 * Valid targets are:
2131 * \list
2132 * \li \c #_parent for the parent state machine if the current state machine is started by
2133 * \c <invoke>
2134 * \li \c #_internal for the current state machine
2135 * \li \c #_scxml_sessionid, where \c sessionid is the session ID of the current state machine
2136 * \li \c #_servicename, where \c servicename is the ID or name of a service started with
2137 * \c <invoke> by this state machine
2138 * \endlist
2139 */
2140bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const
2141{
2142 Q_D(const QScxmlStateMachine);
2143
2144 if (isInvoked() && target == QStringLiteral("#_parent"))
2145 return true; // parent state machine, if we're <invoke>d.
2146 if (target == QStringLiteral("#_internal")
2147 || target == QStringLiteral("#_scxml_%1").arg(a: sessionId()))
2148 return true; // that's the current state machine
2149
2150 if (target.startsWith(QStringLiteral("#_"))) {
2151 QStringView targetId = QStringView{target}.mid(pos: 2);
2152 for (auto invokedService : d->m_invokedServices) {
2153 if (invokedService.service && invokedService.service->id() == targetId)
2154 return true;
2155 }
2156 }
2157
2158 return false;
2159}
2160
2161/*!
2162 \qmlproperty list ScxmlStateMachine::invokedServices
2163
2164 A list of SCXML services that were invoked from the main state machine
2165 (possibly recursively).
2166*/
2167
2168/*!
2169 \property QScxmlStateMachine::invokedServices
2170 \brief A list of SCXML services that were invoked from the main
2171 state machine (possibly recursively).
2172*/
2173
2174QList<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const
2175{
2176 Q_D(const QScxmlStateMachine);
2177 return d->m_invokedServicesComputedProperty;
2178}
2179
2180QBindable<QList<QScxmlInvokableService*>> QScxmlStateMachine::bindableInvokedServices()
2181{
2182 Q_D(QScxmlStateMachine);
2183 return &d->m_invokedServicesComputedProperty;
2184}
2185
2186/*!
2187 \fn QScxmlStateMachine::runningChanged(bool running)
2188
2189 This signal is emitted when the \c running property is changed with \a running as argument.
2190*/
2191
2192/*!
2193 \fn QScxmlStateMachine::log(const QString &label, const QString &msg)
2194
2195 This signal is emitted if a \c <log> tag is used in the SCXML. \a label is the value of the
2196 \e label attribute of the \c <log> tag. \a msg is the value of the evaluated \e expr attribute
2197 of the \c <log> tag. If there is no \e expr attribute, a null string will be returned.
2198*/
2199
2200/*!
2201 \qmlsignal ScxmlStateMachine::log(string label, string msg)
2202
2203 This signal is emitted if a \c <log> tag is used in the SCXML. \a label is
2204 the value of the \e label attribute of the \c <log> tag. \a msg is the value
2205 of the evaluated \e expr attribute of the \c <log> tag. If there is no
2206 \e expr attribute, a null string will be returned.
2207
2208 The corresponding signal handler is \c onLog().
2209*/
2210
2211/*!
2212 \fn QScxmlStateMachine::reachedStableState()
2213
2214 This signal is emitted when the event queue is empty at the end of a macro step or when a final
2215 state is reached.
2216*/
2217
2218/*!
2219 \qmlsignal ScxmlStateMachine::reachedStableState()
2220
2221 This signal is emitted when the event queue is empty at the end of a macro
2222 step or when a final state is reached.
2223
2224 The corresponding signal handler is \c onreachedStableState().
2225*/
2226
2227/*!
2228 \fn QScxmlStateMachine::finished()
2229
2230 This signal is emitted when the state machine reaches a top-level final state.
2231
2232 \sa running
2233*/
2234
2235/*!
2236 \qmlsignal ScxmlStateMachine::finished()
2237
2238 This signal is emitted when the state machine reaches a top-level final
2239 state.
2240
2241 The corresponding signal handler is \c onFinished().
2242*/
2243
2244/*!
2245 \qmlmethod ScxmlStateMachine::start()
2246
2247 Starts this state machine. The machine resets its configuration and
2248 transitions to the initial state. When a final top-level state
2249 is entered, the machine emits the finished() signal.
2250
2251 \sa stop(), finished()
2252*/
2253
2254/*!
2255 Starts this state machine. When a final top-level state
2256 is entered, the machine will emit the finished() signal.
2257
2258 \note A state machine will not run without a running event loop, such as
2259 the main application event loop started with QCoreApplication::exec() or
2260 QApplication::exec().
2261
2262 \note Calling start() after stop() will not result in a full reset of its
2263 configuration yet, hence it is strongly advised not to do it.
2264
2265 \note Starting a finished machine yields a warning.
2266
2267 \sa runningChanged(), setRunning(), stop(), finished()
2268*/
2269void QScxmlStateMachine::start()
2270{
2271 Q_D(QScxmlStateMachine);
2272
2273 if (d->isFinished()) {
2274 qCWarning(qscxmlLog) << this << "Can't start finished machine";
2275 }
2276
2277 if (!parseErrors().isEmpty())
2278 return;
2279
2280 // Failure to initialize doesn't prevent start(). See w3c-ecma/test487 in the scion test suite.
2281 if (!isInitialized() && !init())
2282 qCDebug(qscxmlLog) << this << "cannot be initialized on start(). Starting anyway ...";
2283
2284 d->start();
2285 d->m_eventLoopHook.queueProcessEvents();
2286}
2287
2288/*!
2289 \qmlmethod ScxmlStateMachine::stop()
2290
2291 Stops this state machine. The machine will not execute any further state
2292 transitions. Its \l running property is set to \c false.
2293
2294 \sa start(), finished()
2295*/
2296
2297/*!
2298 Stops this state machine. The machine will not execute any further state
2299 transitions. Its \c running property is set to \c false.
2300
2301 \sa runningChanged(), start(), setRunning()
2302 */
2303void QScxmlStateMachine::stop()
2304{
2305 Q_D(QScxmlStateMachine);
2306 d->pause();
2307}
2308
2309/*!
2310 Returns \c true if the state with the ID \a stateIndex is active.
2311
2312 This method is part of the interface to the compiled representation of SCXML
2313 state machines. It should only be used internally and by state machines
2314 compiled from SCXML documents.
2315 */
2316bool QScxmlStateMachine::isActive(int stateIndex) const
2317{
2318 Q_D(const QScxmlStateMachine);
2319 // Here we need to find the actual internal state index that corresponds with the
2320 // index of the compiled metaobject (which is same as its mapped signal index).
2321 // See updateMetaCache()
2322 const int mappedStateIndex = d->m_stateIndexToSignalIndex.key(value: stateIndex, defaultKey: -1);
2323 return d->m_configuration.contains(i: mappedStateIndex);
2324}
2325
2326QT_END_NAMESPACE
2327

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