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

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