Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qscxmlstatemachine_p.h"
++ #include "qscxmlexecutablecontent_p.h"
++ #include "qscxmlevent_p.h"
++ #include "qscxmlinvokableservice.h"
++ #include "qscxmldatamodel_p.h"
++
++ #include <qfile.h>
++ #include <qhash.h>
++ #include <qloggingcategory.h>
++ #include <qstring.h>
++ #include <qtimer.h>
++ #include <qthread.h>
++
++ #include <functional>
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(qscxmlLog, "qt.scxml.statemachine")
++ Q_LOGGING_CATEGORY(scxmlLog, "scxml.statemachine")
++
++ /*!
++ * \class QScxmlStateMachine
++ * \brief The QScxmlStateMachine class provides an interface to the state machines
++ * created from SCXML files.
++ * \since 5.7
++ * \inmodule QtScxml
++ *
++ * QScxmlStateMachine is an implementation of the
++ * \l{SCXML Specification}{State Chart XML (SCXML)}.
++ *
++ * All states that are defined in the SCXML file
++ * are accessible as properties of QScxmlStateMachine.
++ * These properties are boolean values and indicate
++ * whether the state is active or inactive.
++ *
++ * \note The QScxmlStateMachine needs a QEventLoop to work correctly. The event loop is used to
++ * implement the \c delay attribute for events and to schedule the processing of a state
++ * machine when events are received from nested (or parent) state machines.
++ */
++
++ /*!
++ \qmltype ScxmlStateMachine
++ \nativetype QScxmlStateMachine
++ \inqmlmodule QtScxml
++ \since 5.7
++
++ \brief Provides an interface to the state machines created from SCXML files.
++
++ The ScxmlStateMachine type is an implementation of the
++ \l{SCXML Specification}{State Chart XML (SCXML)}.
++
++ All states that are defined in the SCXML file are accessible as properties
++ of this type. These properties are boolean values and indicate whether the
++ state is active or inactive.
++ */
++
++ /*!
++ \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToEvent(
++ const QString &scxmlEventSpec,
++ const QObject *context,
++ Functor &&functor,
++ Qt::ConnectionType type)
++ \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToEvent(
++ const QString &scxmlEventSpec,
++ Functor &&functor,
++ Qt::ConnectionType type)
++
++ Creates a connection of the given \a type from the event specified by
++ \a scxmlEventSpec to \a functor, which can be a functor or a member function of
++ the optional \a context object.
++
++ The receiver's \a functor must take a QScxmlEvent as a parameter.
++
++ In contrast to event specifications in SCXML documents, spaces are not
++ allowed in the \a scxmlEventSpec here. In order to connect to multiple
++ events with different prefixes, connectToEvent() has to be called multiple
++ times.
++
++ Returns a handle to the connection, which can be used later to disconnect.
++ */
++
++ /*!
++ \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToState(
++ const QString &scxmlStateName,
++ const QObject *context,
++ Functor &&functor,
++ Qt::ConnectionType type)
++ \fn template<typename Functor> QMetaObject::Connection QScxmlStateMachine::connectToState(
++ const QString &scxmlStateName,
++ Functor &&functor,
++ Qt::ConnectionType type)
++
++ Creates a connection of the given \a type from the state specified by
++ \a scxmlStateName to \a functor, which can be a functor or a member function of
++ the optional \a context object.
++
++ The receiver's \a functor must take a boolean argument that indicates
++ whether the state connected became active or inactive.
++
++ Returns a handle to the connection, which can be used later to disconnect.
++ */
++
++ /*!
++ \fn [onentry] std::function<void(bool)> QScxmlStateMachine::onEntry(
++ const QObject *receiver, const char *method)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a method on \a receiver using QMetaObject::invokeMethod() if that argument
++ is \c true and \a receiver has not been deleted, yet.
++
++ The given \a method must not accept any arguments. \a method is the plain
++ method name, not enclosed in \c SIGNAL() or \c SLOT().
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is entered.
++ */
++
++ /*!
++ \fn [onexit] std::function<void(bool)> QScxmlStateMachine::onExit(
++ const QObject *receiver, const char *method)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a method on \a receiver using QMetaObject::invokeMethod() if that argument
++ is \c false and \a receiver has not been deleted, yet.
++
++ The given \a method must not accept any arguments. \a method is the plain
++ method name, not enclosed in SIGNAL(...) or SLOT(...).
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is left.
++ */
++
++ /*!
++ \fn [onentry-functor] template<typename Functor> std::function<void(bool)> QScxmlStateMachine::onEntry(
++ Functor functor)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a functor if that argument is \c true. The given \a functor must not
++ accept any arguments.
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is entered.
++ */
++
++ /*!
++ \fn [onexit-functor] template<typename Functor> std::function<void(bool)> QScxmlStateMachine::onExit(Functor functor)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a functor if that argument is \c false. The given \a functor must not
++ accept any arguments.
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is left.
++ */
++
++ /*!
++ \fn [onentry-template] template<typename PointerToMemberFunction> std::function<void(bool)> QScxmlStateMachine::onEntry(
++ const typename QtPrivate::FunctionPointer<PointerToMemberFunction>::Object *receiver,
++ PointerToMemberFunction method)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a method on \a receiver if that argument is \c true and the \a receiver
++ has not been deleted, yet. The given \a method must not accept any
++ arguments.
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is entered.
++ */
++
++ /*!
++ \fn [onexit-template] template<typename PointerToMemberFunction> std::function<void(bool)> QScxmlStateMachine::onExit(
++ const typename QtPrivate::FunctionPointer<PointerToMemberFunction>::Object *receiver,
++ PointerToMemberFunction method)
++
++ Returns a functor that accepts a boolean argument and calls the given
++ \a method on \a receiver if that argument is \c false and the \a receiver
++ has not been deleted, yet. The given \a method must not accept any
++ arguments.
++
++ This is useful to wrap handlers for connectToState() that should only
++ be executed when the state is left.
++ */
++
++ namespace QScxmlInternal {
++
++ static int signalIndex(const QMetaObject *meta, const QByteArray &signalName)
++ {
++ Q_ASSERT(meta);
++
++ int signalIndex = meta->indexOfSignal(signalName.constData());
++
++ // If signal doesn't exist, return negative value
++ if (signalIndex < 0)
++ return signalIndex;
++
++ // signal belongs to class whose meta object was passed, not some derived class.
++ Q_ASSERT(meta->methodOffset() <= signalIndex);
++
++ // Duplicate of computeOffsets in qobject.cpp
++ const QMetaObject *m = meta->d.superdata;
++ while (m) {
++ const QMetaObjectPrivate *d = QMetaObjectPrivate::get(m);
++ signalIndex = signalIndex - d->methodCount + d->signalCount;
++ m = m->d.superdata;
++ }
++
++ // Asserting about the signal not being cloned would be nice, too, but not practical.
++
++ return signalIndex;
++ }
++
++ void EventLoopHook::queueProcessEvents()
++ {
++ if (smp->m_isProcessingEvents)
++ return;
++
++ QMetaObject::invokeMethod(this, "doProcessEvents", Qt::QueuedConnection);
++ }
++
++ void EventLoopHook::doProcessEvents()
++ {
++ smp->processEvents();
++ }
++
++ void EventLoopHook::timerEvent(QTimerEvent *timerEvent)
++ {
++ const int timerId = timerEvent->timerId();
++ for (auto it = smp->m_delayedEvents.begin(), eit = smp->m_delayedEvents.end(); it != eit; ++it) {
++ if (it->first == timerId) {
++ QScxmlEvent *scxmlEvent = it->second;
++ smp->m_delayedEvents.erase(it);
++ smp->routeEvent(scxmlEvent);
++ killTimer(timerId);
++ return;
++ }
++ }
++ }
++
++ void ScxmlEventRouter::route(const QStringList &segments, QScxmlEvent *event)
++ {
++ emit eventOccurred(*event);
++ if (!segments.isEmpty()) {
++ auto it = children.find(segments.first());
++ if (it != children.end())
++ it.value()->route(segments.mid(1), event);
++ }
++ }
++
++ static QString nextSegment(const QStringList &segments)
++ {
++ if (segments.isEmpty())
++ return QString();
++
++ const QString &segment = segments.first();
++ return segment == QLatin1String("*") ? QString() : segment;
++ }
++
++ ScxmlEventRouter *ScxmlEventRouter::child(const QString &segment)
++ {
++ ScxmlEventRouter *&child = children[segment];
++ if (child == nullptr)
++ child = new ScxmlEventRouter(this);
++ return child;
++ }
++
++ void ScxmlEventRouter::disconnectNotify(const QMetaMethod &signal)
++ {
++ Q_UNUSED(signal);
++
++ // Defer the actual work, as this may be called from a destructor, or the signal may not
++ // actually be disconnected, yet.
++ QTimer::singleShot(0, this, [this] {
++ if (!children.isEmpty() || receivers(SIGNAL(eventOccurred(QScxmlEvent))) > 0)
++ return;
++
++ ScxmlEventRouter *parentRouter = qobject_cast<ScxmlEventRouter *>(parent());
++ if (!parentRouter) // root node
++ return;
++
++ QHash<QString, ScxmlEventRouter *>::Iterator it = parentRouter->children.begin(),
++ end = parentRouter->children.end();
++ for (; it != end; ++it) {
++ if (it.value() == this) {
++ parentRouter->children.erase(it);
++ parentRouter->disconnectNotify(QMetaMethod());
++ break;
++ }
++ }
++
++ deleteLater(); // The parent might delete itself, triggering QObject delete cascades.
++ });
++ }
++
++ QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments,
++ const QObject *receiver,
++ const char *method,
++ Qt::ConnectionType type)
++ {
++ QString segment = nextSegment(segments);
++ return segment.isEmpty() ?
++ connect(this, SIGNAL(eventOccurred(QScxmlEvent)), receiver, method, type) :
++ child(segment)->connectToEvent(segments.mid(1), receiver, method, type);
++ }
++
++ QMetaObject::Connection ScxmlEventRouter::connectToEvent(const QStringList &segments,
++ const QObject *receiver, void **slot,
++ QtPrivate::QSlotObjectBase *method,
++ Qt::ConnectionType type)
++ {
++ QString segment = nextSegment(segments);
++ if (segment.isEmpty()) {
++ const int *types = nullptr;
++ if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection) {
++ static const int eventTypeId[] = { qMetaTypeId<QScxmlEvent>(), 0 };
++ types = eventTypeId;
++ }
++
++ const QMetaObject *meta = metaObject();
++ static const int eventOccurredIndex = signalIndex(meta, "eventOccurred(QScxmlEvent)");
++ return QObjectPrivate::connectImpl(this, eventOccurredIndex, receiver, slot, method, type,
++ types, meta);
++ } else {
++ return child(segment)->connectToEvent(segments.mid(1), receiver, slot, method, type);
++ }
++ }
++
++ } // namespace QScxmlInternal
++
++ QAtomicInt QScxmlStateMachinePrivate::m_sessionIdCounter = QAtomicInt(0);
++
++ QScxmlStateMachinePrivate::QScxmlStateMachinePrivate(const QMetaObject *metaObject)
++ : QObjectPrivate()
++ , m_sessionId(QScxmlStateMachinePrivate::generateSessionId(QStringLiteral("session-")))
++ , m_isInvoked(false)
++ , m_isProcessingEvents(false)
++ , m_executionEngine(nullptr)
++ , m_parentStateMachine(nullptr)
++ , m_eventLoopHook(this)
++ , m_metaObject(metaObject)
++ , m_infoSignalProxy(nullptr)
++ {
++ static int metaType = qRegisterMetaType<QScxmlStateMachine *>();
++ Q_UNUSED(metaType);
++ m_loader.setValueBypassingBindings(&m_defaultLoader);
++ }
++
++ QScxmlStateMachinePrivate::~QScxmlStateMachinePrivate()
++ {
++ for (const InvokedService &invokedService : m_invokedServices)
++ delete invokedService.service;
++ qDeleteAll(m_cachedFactories);
++ delete m_executionEngine;
++ }
++
++ QScxmlStateMachinePrivate::ParserData *QScxmlStateMachinePrivate::parserData()
++ {
++ if (m_parserData.isNull())
++ m_parserData.reset(new ParserData);
++ return m_parserData.data();
++ }
++
++ void QScxmlStateMachinePrivate::addService(int invokingState)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds;
++ if (arrayId == StateTable::InvalidIndex)
++ return;
++
++ const auto &ids = m_stateTable->array(arrayId);
++ for (int id : ids) {
++ auto factory = serviceFactory(id);
++ auto service = factory->invoke(q);
++ if (service == nullptr)
++ continue; // service failed to start
++ const QString serviceName = service->name();
++ m_invokedServices[size_t(id)] = { invokingState, service, serviceName };
++ service->start();
++ }
++ emitInvokedServicesChanged();
++ }
++
++ void QScxmlStateMachinePrivate::removeService(int invokingState)
++ {
++ const int arrayId = m_stateTable->state(invokingState).serviceFactoryIds;
++ if (arrayId == StateTable::InvalidIndex)
++ return;
++
++ for (size_t i = 0, ei = m_invokedServices.size(); i != ei; ++i) {
++ auto &it = m_invokedServices[i];
++ QScxmlInvokableService *service = it.service;
++ if (it.invokingState == invokingState && service != nullptr) {
++ it.service = nullptr;
++ delete service;
++ }
++ }
++ emitInvokedServicesChanged();
++ }
++
++ QScxmlInvokableServiceFactory *QScxmlStateMachinePrivate::serviceFactory(int id)
++ {
++ Q_ASSERT(id <= m_stateTable->maxServiceId && id >= 0);
++ QScxmlInvokableServiceFactory *& factory = m_cachedFactories[size_t(id)];
++ if (factory == nullptr)
++ factory = m_tableData.value()->serviceFactory(id);
++ return factory;
++ }
++
++ bool QScxmlStateMachinePrivate::executeInitialSetup()
++ {
++ return m_executionEngine->execute(m_tableData.value()->initialSetup());
++ }
++
++ void QScxmlStateMachinePrivate::routeEvent(QScxmlEvent *event)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ if (!event)
++ return;
++
++ QString origin = event->origin();
++ if (origin == QStringLiteral("#_parent")) {
++ if (auto psm = m_parentStateMachine) {
++ qCDebug(qscxmlLog) << q << "routing event" << event->name() << "from" << q->name() << "to parent" << psm->name();
++ QScxmlStateMachinePrivate::get(psm)->postEvent(event);
++ } else {
++ qCDebug(qscxmlLog) << this << "is not invoked, so it cannot route a message to #_parent";
++ delete event;
++ }
++ } else if (origin.startsWith(QStringLiteral("#_")) && origin != QStringLiteral("#_internal")) {
++ // route to children
++ auto originId = QStringView{origin}.mid(2);
++ for (const auto &invokedService : m_invokedServices) {
++ auto service = invokedService.service;
++ if (service == nullptr)
++ continue;
++ if (service->id() == originId) {
++ qCDebug(qscxmlLog) << q << "routing event" << event->name()
++ << "from" << q->name()
++ << "to child" << service->id();
++ service->postEvent(new QScxmlEvent(*event));
++ }
++ }
++ delete event;
++ } else {
++ postEvent(event);
++ }
++ }
++
++ void QScxmlStateMachinePrivate::postEvent(QScxmlEvent *event)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ if (event->eventType() == QScxmlEvent::ExternalEvent) {
++ m_router.route(event->name().split(QLatin1Char('.')), event);
++ qCDebug(qscxmlLog) << q << "posting external event" << event->name();
++ m_externalQueue.enqueue(event);
++ } else {
++ qCDebug(qscxmlLog) << q << "posting internal event" << event->name();
++ m_internalQueue.enqueue(event);
++ }
++
++ m_eventLoopHook.queueProcessEvents();
++ }
++
++ void QScxmlStateMachinePrivate::submitDelayedEvent(QScxmlEvent *event)
++ {
++ Q_ASSERT(event);
++ Q_ASSERT(event->delay() > 0);
++
++ const int timerId = m_eventLoopHook.startTimer(event->delay());
++ if (timerId == 0) {
++ qWarning("QScxmlStateMachinePrivate::submitDelayedEvent: "
++ "failed to start timer for event '%s' (%p)",
++ qPrintable(event->name()), event);
++ delete event;
++ return;
++ }
++ m_delayedEvents.push_back(std::make_pair(timerId, event));
++
++ qCDebug(qscxmlLog) << q_func()
++ << ": delayed event" << event->name()
++ << "(" << event << ") got id:" << timerId;
++ }
++
++ /*!
++ \class QScxmlStateMachinePrivate
++ \inmodule QtScxml
++ \internal
++ */
++
++ /*!
++ * Submits an error event to the external event queue of this state machine.
++ *
++ * The type of the error is specified by \a type. The value of type has to begin
++ * with the string \e error. For example \c {error.execution}. The message,
++ * \a message, describes the error and is passed to the event as the
++ * \c errorMessage property. The \a sendId of the message causing the error is specified, if it has
++ * one.
++ */
++ void QScxmlStateMachinePrivate::submitError(const QString &type, const QString &message,
++ const QString &sendId)
++ {
++ Q_Q(QScxmlStateMachine);
++ qCDebug(qscxmlLog) << q << "had error" << type << ":" << message;
++ if (!type.startsWith(QStringLiteral("error.")))
++ qCWarning(qscxmlLog) << q << "Message type of error message does not start with 'error.'!";
++ q->submitEvent(QScxmlEventBuilder::errorEvent(q, type, message, sendId));
++ }
++
++ void QScxmlStateMachinePrivate::start()
++ {
++ Q_Q(QScxmlStateMachine);
++
++ if (m_stateTable->binding == StateTable::LateBinding)
++ m_isFirstStateEntry.resize(m_stateTable->stateCount, true);
++
++ bool running = isRunnable() && !isPaused();
++ m_runningState = Starting;
++ Q_ASSERT(m_stateTable->initialTransition != StateTable::InvalidIndex);
++
++ if (!running)
++ emit q->runningChanged(true);
++ }
++
++ void QScxmlStateMachinePrivate::pause()
++ {
++ Q_Q(QScxmlStateMachine);
++
++ if (isRunnable() && !isPaused()) {
++ m_runningState = Paused;
++ emit q->runningChanged(false);
++ }
++ }
++
++ void QScxmlStateMachinePrivate::processEvents()
++ {
++ if (m_isProcessingEvents || (!isRunnable() && !isPaused()))
++ return;
++
++ m_isProcessingEvents = true;
++
++ Q_Q(QScxmlStateMachine);
++ qCDebug(qscxmlLog) << q_func() << "starting macrostep";
++
++ while (isRunnable() && !isPaused()) {
++ if (m_runningState == Starting) {
++ enterStates({m_stateTable->initialTransition});
++ if (m_runningState == Starting)
++ m_runningState = Running;
++ continue;
++ }
++
++ OrderedSet enabledTransitions;
++ std::vector<int> configurationInDocumentOrder = m_configuration.list();
++ std::sort(configurationInDocumentOrder.begin(), configurationInDocumentOrder.end());
++ selectTransitions(enabledTransitions, configurationInDocumentOrder, nullptr);
++ if (!enabledTransitions.isEmpty()) {
++ microstep(enabledTransitions);
++ } else if (!m_internalQueue.isEmpty()) {
++ auto event = m_internalQueue.dequeue();
++ setEvent(event);
++ selectTransitions(enabledTransitions, configurationInDocumentOrder, event);
++ if (!enabledTransitions.isEmpty()) {
++ microstep(enabledTransitions);
++ }
++ resetEvent();
++ delete event;
++ } else if (!m_externalQueue.isEmpty()) {
++ auto event = m_externalQueue.dequeue();
++ setEvent(event);
++
++ for (int id = 0, end = static_cast<int>(m_invokedServices.size()); id != end; ++id) {
++ auto service = m_invokedServices[id].service;
++ if (service == nullptr)
++ continue;
++ auto factory = serviceFactory(id);
++ if (event->invokeId() == service->id()) {
++ const QScxmlExecutableContent::ContainerId finalize
++ = factory->invokeInfo().finalize;
++ if (finalize != QScxmlExecutableContent::NoContainer) {
++ auto psm = service->parentStateMachine();
++ qCDebug(qscxmlLog) << psm << "running finalize on event";
++ auto smp = QScxmlStateMachinePrivate::get(psm);
++ smp->m_executionEngine->execute(finalize);
++ }
++
++ }
++
++ if (factory->invokeInfo().autoforward) {
++ qCDebug(qscxmlLog) << q << "auto-forwarding event" << event->name()
++ << "from" << q->name()
++ << "to child" << service->id();
++ service->postEvent(new QScxmlEvent(*event));
++ }
++ }
++
++ selectTransitions(enabledTransitions, configurationInDocumentOrder, event);
++ if (!enabledTransitions.isEmpty()) {
++ microstep(enabledTransitions);
++ }
++ resetEvent();
++ delete event;
++ } else {
++ // nothing to do, so:
++ break;
++ }
++ }
++
++ if (!m_statesToInvoke.empty()) {
++ for (int stateId : std::as_const(m_statesToInvoke))
++ addService(stateId);
++ m_statesToInvoke.clear();
++ }
++
++ qCDebug(qscxmlLog) << q_func()
++ << "finished macrostep, runnable:" << isRunnable()
++ << "paused:" << isPaused();
++ emit q->reachedStableState();
++ if (!isRunnable() && !isPaused()) {
++ exitInterpreter();
++ emit q->finished();
++ }
++
++ m_isProcessingEvents = false;
++ }
++
++ void QScxmlStateMachinePrivate::setEvent(QScxmlEvent *event)
++ {
++ Q_ASSERT(event);
++ m_dataModel.value()->setScxmlEvent(*event);
++ }
++
++ void QScxmlStateMachinePrivate::resetEvent()
++ {
++ m_dataModel.value()->setScxmlEvent(QScxmlEvent());
++ }
++
++ void QScxmlStateMachinePrivate::emitStateActive(int stateIndex, bool active)
++ {
++ Q_Q(QScxmlStateMachine);
++ void *args[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&active)) };
++ const int signalIndex = m_stateIndexToSignalIndex.value(stateIndex, -1);
++ if (signalIndex >= 0)
++ QMetaObject::activate(q, m_metaObject, signalIndex, args);
++ }
++
++ void QScxmlStateMachinePrivate::emitInvokedServicesChanged()
++ {
++ Q_Q(QScxmlStateMachine);
++ m_invokedServicesComputedProperty.notify();
++ emit q->invokedServicesChanged(q->invokedServices());
++ }
++
++ void QScxmlStateMachinePrivate::attach(QScxmlStateMachineInfo *info)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ if (!m_infoSignalProxy)
++ m_infoSignalProxy = new QScxmlInternal::StateMachineInfoProxy(q);
++
++ QObject::connect(m_infoSignalProxy, &QScxmlInternal::StateMachineInfoProxy::statesEntered,
++ info, &QScxmlStateMachineInfo::statesEntered);
++ QObject::connect(m_infoSignalProxy, &QScxmlInternal::StateMachineInfoProxy::statesExited,
++ info, &QScxmlStateMachineInfo::statesExited);
++ QObject::connect(m_infoSignalProxy,&QScxmlInternal::StateMachineInfoProxy::transitionsTriggered,
++ info, &QScxmlStateMachineInfo::transitionsTriggered);
++ }
++
++ void QScxmlStateMachinePrivate::updateMetaCache()
++ {
++ // This function creates a mapping from state index/name to their signal indexes.
++ // The state index may differ from its signal index as we don't generate history
++ // and invalid states, effectively skipping them
++ m_stateIndexToSignalIndex.clear();
++ m_stateNameToSignalIndex.clear();
++
++ const QScxmlTableData *tableData = m_tableData.valueBypassingBindings();
++ if (!tableData)
++ return;
++
++ if (!m_stateTable)
++ return;
++
++ int signalIndex = 0;
++ const int methodOffset = QMetaObjectPrivate::signalOffset(m_metaObject);
++ for (int i = 0; i < m_stateTable->stateCount; ++i) {
++ const auto &s = m_stateTable->state(i);
++ if (!s.isHistoryState() && s.type != StateTable::State::Invalid) {
++ m_stateIndexToSignalIndex.insert(i, signalIndex);
++ m_stateNameToSignalIndex.insert(tableData->string(s.name),
++ signalIndex + methodOffset);
++
++ ++signalIndex;
++ }
++ }
++ }
++
++ QStringList QScxmlStateMachinePrivate::stateNames(const std::vector<int> &stateIndexes) const
++ {
++ QStringList names;
++ for (int idx : stateIndexes)
++ names.append(m_tableData.value()->string(m_stateTable->state(idx).name));
++ return names;
++ }
++
++ std::vector<int> QScxmlStateMachinePrivate::historyStates(int stateIdx) const {
++ const StateTable::Array kids = m_stateTable->array(m_stateTable->state(stateIdx).childStates);
++ std::vector<int> res;
++ if (!kids.isValid()) return res;
++ for (int k : kids) {
++ if (m_stateTable->state(k).isHistoryState())
++ res.push_back(k);
++ }
++ return res;
++ }
++
++ void QScxmlStateMachinePrivate::exitInterpreter()
++ {
++ qCDebug(qscxmlLog) << q_func() << "exiting SCXML processing";
++
++ for (auto it : m_delayedEvents) {
++ m_eventLoopHook.killTimer(it.first);
++ delete it.second;
++ }
++ m_delayedEvents.clear();
++
++ auto statesToExitSorted = m_configuration.list();
++ std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>());
++ for (int stateIndex : statesToExitSorted) {
++ const auto &state = m_stateTable->state(stateIndex);
++ if (state.exitInstructions != StateTable::InvalidIndex) {
++ m_executionEngine->execute(state.exitInstructions);
++ }
++ removeService(stateIndex);
++ if (state.type == StateTable::State::Final && state.parentIsScxmlElement()) {
++ returnDoneEvent(state.doneData);
++ }
++ }
++ }
++
++ void QScxmlStateMachinePrivate::returnDoneEvent(QScxmlExecutableContent::ContainerId doneData)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ m_executionEngine->execute(doneData, QVariant());
++ if (m_isInvoked) {
++ auto e = new QScxmlEvent;
++ e->setName(QStringLiteral("done.invoke.") + q->sessionId());
++ e->setInvokeId(q->sessionId());
++ QScxmlStateMachinePrivate::get(m_parentStateMachine)->postEvent(e);
++ }
++ }
++
++ bool QScxmlStateMachinePrivate::nameMatch(const StateTable::Array &patterns,
++ QScxmlEvent *event) const
++ {
++ const QString eventName = event->name();
++ bool selected = false;
++ for (int eventSelectorIter = 0; eventSelectorIter < patterns.size(); ++eventSelectorIter) {
++ QString eventStr = m_tableData.value()->string(patterns[eventSelectorIter]);
++ if (eventStr == QStringLiteral("*")) {
++ selected = true;
++ break;
++ }
++ if (eventStr.endsWith(QStringLiteral(".*")))
++ eventStr.chop(2);
++ if (eventName.startsWith(eventStr)) {
++ QChar nextC = QLatin1Char('.');
++ if (eventName.size() > eventStr.size())
++ nextC = eventName.at(eventStr.size());
++ if (nextC == QLatin1Char('.') || nextC == QLatin1Char('(')) {
++ selected = true;
++ break;
++ }
++ }
++ }
++ return selected;
++ }
++
++ void QScxmlStateMachinePrivate::selectTransitions(OrderedSet &enabledTransitions,
++ const std::vector<int> &configInDocumentOrder,
++ QScxmlEvent *event) const
++ {
++ if (event == nullptr) {
++ qCDebug(qscxmlLog) << q_func() << "selectEventlessTransitions";
++ } else {
++ qCDebug(qscxmlLog) << q_func() << "selectTransitions with event"
++ << QScxmlEventPrivate::debugString(event).constData();
++ }
++
++ std::vector<int> states;
++ states.reserve(16);
++ for (int configStateIdx : configInDocumentOrder) {
++ if (m_stateTable->state(configStateIdx).isAtomic()) {
++ states.clear();
++ states.push_back(configStateIdx);
++ getProperAncestors(&states, configStateIdx, -1);
++ for (int stateIdx : states) {
++ bool finishedWithThisConfigState = false;
++
++ if (stateIdx == -1) {
++ // the state machine has no transitions (other than the initial one, which has
++ // already been taken at this point)
++ continue;
++ }
++ const auto &state = m_stateTable->state(stateIdx);
++ const StateTable::Array transitions = m_stateTable->array(state.transitions);
++ if (!transitions.isValid())
++ continue;
++ std::vector<int> sortedTransitions(transitions.size(), -1);
++ std::copy(transitions.begin(), transitions.end(), sortedTransitions.begin());
++ for (int transitionIndex : sortedTransitions) {
++ const StateTable::Transition &t = m_stateTable->transition(transitionIndex);
++ bool enabled = false;
++ if (event == nullptr) {
++ if (t.events == -1) {
++ if (t.condition == -1) {
++ enabled = true;
++ } else {
++ bool ok = false;
++ enabled = m_dataModel.value()->evaluateToBool(t.condition, &ok) && ok;
++ }
++ }
++ } else {
++ if (t.events != -1 && nameMatch(m_stateTable->array(t.events), event)) {
++ if (t.condition == -1) {
++ enabled = true;
++ } else {
++ bool ok = false;
++ enabled = m_dataModel.value()->evaluateToBool(t.condition, &ok) && ok;
++ }
++ }
++ }
++ if (enabled) {
++ enabledTransitions.add(transitionIndex);
++ finishedWithThisConfigState = true;
++ break; // stop iterating over transitions
++ }
++ }
++
++ if (finishedWithThisConfigState)
++ break; // stop iterating over ancestors
++ }
++ }
++ }
++ if (!enabledTransitions.isEmpty())
++ removeConflictingTransitions(&enabledTransitions);
++ }
++
++ void QScxmlStateMachinePrivate::removeConflictingTransitions(OrderedSet *enabledTransitions) const
++ {
++ Q_ASSERT(enabledTransitions);
++
++ auto sortedTransitions = enabledTransitions->takeList();
++ std::sort(sortedTransitions.begin(), sortedTransitions.end(), [this](int t1, int t2) -> bool {
++ auto descendantDepth = [this](int state, int ancestor)->int {
++ int depth = 0;
++ for (int it = state; it != -1; it = m_stateTable->state(it).parent) {
++ if (it == ancestor)
++ break;
++ ++depth;
++ }
++ return depth;
++ };
++
++ const auto &s1 = m_stateTable->transition(t1).source;
++ const auto &s2 = m_stateTable->transition(t2).source;
++ if (s1 == s2) {
++ return t1 < t2;
++ } else if (isDescendant(s1, s2)) {
++ return true;
++ } else if (isDescendant(s2, s1)) {
++ return false;
++ } else {
++ const int lcca = findLCCA({ s1, s2 });
++ const int s1Depth = descendantDepth(s1, lcca);
++ const int s2Depth = descendantDepth(s2, lcca);
++ if (s1Depth == s2Depth)
++ return s1 < s2;
++ else
++ return s1Depth > s2Depth;
++ }
++ });
++
++ OrderedSet filteredTransitions;
++ for (int t1 : sortedTransitions) {
++ OrderedSet transitionsToRemove;
++ bool t1Preempted = false;
++ OrderedSet exitSetT1;
++ computeExitSet({t1}, exitSetT1);
++ const int source1 = m_stateTable->transition(t1).source;
++ for (int t2 : filteredTransitions) {
++ OrderedSet exitSetT2;
++ computeExitSet({t2}, exitSetT2);
++ if (exitSetT1.intersectsWith(exitSetT2)) {
++ const int source2 = m_stateTable->transition(t2).source;
++ if (isDescendant(source1, source2)) {
++ transitionsToRemove.add(t2);
++ } else {
++ t1Preempted = true;
++ break;
++ }
++ }
++ }
++ if (!t1Preempted) {
++ for (int t3 : transitionsToRemove) {
++ filteredTransitions.remove(t3);
++ }
++ filteredTransitions.add(t1);
++ }
++ }
++ *enabledTransitions = filteredTransitions;
++ }
++
++ void QScxmlStateMachinePrivate::getProperAncestors(std::vector<int> *ancestors, int state1,
++ int state2) const
++ {
++ Q_ASSERT(ancestors);
++
++ if (state1 == -1) {
++ return;
++ }
++
++ int parent = state1;
++ do {
++ parent = m_stateTable->state(parent).parent;
++ if (parent == state2) {
++ break;
++ }
++ ancestors->push_back(parent);
++ } while (parent != -1);
++ }
++
++ void QScxmlStateMachinePrivate::microstep(const OrderedSet &enabledTransitions)
++ {
++ if (qscxmlLog().isDebugEnabled()) {
++ qCDebug(qscxmlLog) << q_func()
++ << "starting microstep, configuration:"
++ << stateNames(m_configuration.list());
++ qCDebug(qscxmlLog) << q_func() << "enabled transitions:";
++ for (int t : enabledTransitions) {
++ const auto &transition = m_stateTable->transition(t);
++ QString from = QStringLiteral("(none)");
++ if (transition.source != StateTable::InvalidIndex)
++ from = m_tableData.value()->string(m_stateTable->state(transition.source).name);
++ QStringList to;
++ if (transition.targets == StateTable::InvalidIndex) {
++ to.append(QStringLiteral("(none)"));
++ } else {
++ for (int t : m_stateTable->array(transition.targets))
++ to.append(m_tableData.value()->string(m_stateTable->state(t).name));
++ }
++ qCDebug(qscxmlLog) << q_func() << "\t" << t << ":" << from << "->"
++ << to.join(QLatin1Char(','));
++ }
++ }
++
++ exitStates(enabledTransitions);
++ executeTransitionContent(enabledTransitions);
++ enterStates(enabledTransitions);
++
++ qCDebug(qscxmlLog) << q_func() << "finished microstep, configuration:"
++ << stateNames(m_configuration.list());
++ }
++
++ void QScxmlStateMachinePrivate::exitStates(const OrderedSet &enabledTransitions)
++ {
++ OrderedSet statesToExit;
++ computeExitSet(enabledTransitions, statesToExit);
++ auto statesToExitSorted = statesToExit.takeList();
++ std::sort(statesToExitSorted.begin(), statesToExitSorted.end(), std::greater<int>());
++ qCDebug(qscxmlLog) << q_func() << "exiting states" << stateNames(statesToExitSorted);
++ for (int s : statesToExitSorted) {
++ const auto &state = m_stateTable->state(s);
++ if (state.serviceFactoryIds != StateTable::InvalidIndex)
++ m_statesToInvoke.remove(s);
++ }
++ for (int s : statesToExitSorted) {
++ for (int h : historyStates(s)) {
++ const auto &hState = m_stateTable->state(h);
++ QList<int> history;
++
++ for (int s0 : m_configuration) {
++ const auto &s0State = m_stateTable->state(s0);
++ if (hState.type == StateTable::State::DeepHistory) {
++ if (s0State.isAtomic() && isDescendant(s0, s))
++ history.append(s0);
++ } else {
++ if (s0State.parent == s)
++ history.append(s0);
++ }
++ }
++
++ m_historyValue[h] = history;
++ }
++ }
++ for (int s : statesToExitSorted) {
++ const auto &state = m_stateTable->state(s);
++ if (state.exitInstructions != StateTable::InvalidIndex)
++ m_executionEngine->execute(state.exitInstructions);
++ m_configuration.remove(s);
++ emitStateActive(s, false);
++ removeService(s);
++ }
++
++ if (m_infoSignalProxy) {
++ emit m_infoSignalProxy->statesExited(
++ QList<QScxmlStateMachineInfo::StateId>(statesToExitSorted.begin(),
++ statesToExitSorted.end()));
++ }
++ }
++
++ void QScxmlStateMachinePrivate::computeExitSet(const OrderedSet &enabledTransitions,
++ OrderedSet &statesToExit) const
++ {
++ for (int t : enabledTransitions) {
++ const auto &transition = m_stateTable->transition(t);
++ if (transition.targets == StateTable::InvalidIndex) {
++ // nothing to do here: there is no exit set
++ } else {
++ const int domain = getTransitionDomain(t);
++ for (int s : m_configuration) {
++ if (isDescendant(s, domain))
++ statesToExit.add(s);
++ }
++ }
++ }
++ }
++
++ void QScxmlStateMachinePrivate::executeTransitionContent(const OrderedSet &enabledTransitions)
++ {
++ for (int t : enabledTransitions) {
++ const auto &transition = m_stateTable->transition(t);
++ if (transition.transitionInstructions != StateTable::InvalidIndex)
++ m_executionEngine->execute(transition.transitionInstructions);
++ }
++
++ if (m_infoSignalProxy) {
++ emit m_infoSignalProxy->transitionsTriggered(
++ QList<QScxmlStateMachineInfo::TransitionId>(enabledTransitions.list().begin(),
++ enabledTransitions.list().end()));
++ }
++ }
++
++ void QScxmlStateMachinePrivate::enterStates(const OrderedSet &enabledTransitions)
++ {
++ Q_Q(QScxmlStateMachine);
++
++ OrderedSet statesToEnter, statesForDefaultEntry;
++ HistoryContent defaultHistoryContent;
++ computeEntrySet(enabledTransitions, &statesToEnter, &statesForDefaultEntry,
++ &defaultHistoryContent);
++ auto sortedStates = statesToEnter.takeList();
++ std::sort(sortedStates.begin(), sortedStates.end());
++ qCDebug(qscxmlLog) << q_func() << "entering states" << stateNames(sortedStates);
++ for (int s : sortedStates) {
++ const auto &state = m_stateTable->state(s);
++ m_configuration.add(s);
++ if (state.serviceFactoryIds != StateTable::InvalidIndex)
++ m_statesToInvoke.insert(s);
++ if (m_stateTable->binding == StateTable::LateBinding && m_isFirstStateEntry[s]) {
++ if (state.initInstructions != StateTable::InvalidIndex)
++ m_executionEngine->execute(state.initInstructions);
++ m_isFirstStateEntry[s] = false;
++ }
++ if (state.entryInstructions != StateTable::InvalidIndex)
++ m_executionEngine->execute(state.entryInstructions);
++ if (statesForDefaultEntry.contains(s)) {
++ const auto &initialTransition = m_stateTable->transition(state.initialTransition);
++ if (initialTransition.transitionInstructions != StateTable::InvalidIndex)
++ m_executionEngine->execute(initialTransition.transitionInstructions);
++ }
++ const int dhc = defaultHistoryContent.value(s);
++ if (dhc != StateTable::InvalidIndex)
++ m_executionEngine->execute(dhc);
++ if (state.type == StateTable::State::Final) {
++ if (state.parentIsScxmlElement()) {
++ bool running = isRunnable() && !isPaused();
++ m_runningState = Finished;
++ if (running)
++ emit q->runningChanged(false);
++ } else {
++ const auto &parent = m_stateTable->state(state.parent);
++ m_executionEngine->execute(state.doneData, m_tableData.value()->string(parent.name));
++ if (parent.parent != StateTable::InvalidIndex) {
++ const auto &grandParent = m_stateTable->state(parent.parent);
++ if (grandParent.isParallel()) {
++ if (allInFinalStates(getChildStates(grandParent))) {
++ auto e = new QScxmlEvent;
++ e->setEventType(QScxmlEvent::InternalEvent);
++ e->setName(QStringLiteral("done.state.")
++ + m_tableData.value()->string(grandParent.name));
++ q->submitEvent(e);
++ }
++ }
++ }
++ }
++ }
++ }
++ for (int s : sortedStates)
++ emitStateActive(s, true);
++ if (m_infoSignalProxy) {
++ emit m_infoSignalProxy->statesEntered(
++ QList<QScxmlStateMachineInfo::StateId>(sortedStates.begin(),
++ sortedStates.end()));
++ }
++ }
++
++ void QScxmlStateMachinePrivate::computeEntrySet(const OrderedSet &enabledTransitions,
++ OrderedSet *statesToEnter,
++ OrderedSet *statesForDefaultEntry,
++ HistoryContent *defaultHistoryContent) const
++ {
++ Q_ASSERT(statesToEnter);
++ Q_ASSERT(statesForDefaultEntry);
++ Q_ASSERT(defaultHistoryContent);
++
++ for (int t : enabledTransitions) {
++ const auto &transition = m_stateTable->transition(t);
++ if (transition.targets == StateTable::InvalidIndex)
++ // targetless transition, so nothing to do
++ continue;
++ for (int s : m_stateTable->array(transition.targets))
++ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ auto ancestor = getTransitionDomain(t);
++ OrderedSet targets;
++ getEffectiveTargetStates(&targets, t);
++ for (auto s : targets)
++ addAncestorStatesToEnter(s, ancestor, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ }
++ }
++
++ void QScxmlStateMachinePrivate::addDescendantStatesToEnter(
++ int stateIndex, OrderedSet *statesToEnter, OrderedSet *statesForDefaultEntry,
++ HistoryContent *defaultHistoryContent) const
++ {
++ Q_ASSERT(statesToEnter);
++ Q_ASSERT(statesForDefaultEntry);
++ Q_ASSERT(defaultHistoryContent);
++
++ const auto &state = m_stateTable->state(stateIndex);
++ if (state.isHistoryState()) {
++ HistoryValues::const_iterator historyValueIter = m_historyValue.constFind(stateIndex);
++ if (historyValueIter != m_historyValue.constEnd()) {
++ const QList<int> historyValue = historyValueIter.value();
++ for (int s : historyValue)
++ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ for (int s : historyValue)
++ addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ } else {
++ int transitionIdx = StateTable::InvalidIndex;
++ if (state.transitions == StateTable::InvalidIndex) {
++ int parentInitialTransition = m_stateTable->state(state.parent).initialTransition;
++ if (parentInitialTransition == StateTable::InvalidIndex)
++ return;
++ transitionIdx = parentInitialTransition;
++ } else {
++ transitionIdx = m_stateTable->array(state.transitions)[0];
++ }
++ const auto &defaultHistoryTransition = m_stateTable->transition(transitionIdx);
++ defaultHistoryContent->operator[](state.parent) =
++ defaultHistoryTransition.transitionInstructions;
++ StateTable::Array targetStates = m_stateTable->array(defaultHistoryTransition.targets);
++ for (int s : targetStates)
++ addDescendantStatesToEnter(s, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ for (int s : targetStates)
++ addAncestorStatesToEnter(s, state.parent, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ }
++ } else {
++ statesToEnter->add(stateIndex);
++ if (state.isCompound()) {
++ statesForDefaultEntry->add(stateIndex);
++ if (state.initialTransition != StateTable::InvalidIndex) {
++ auto initialTransition = m_stateTable->transition(state.initialTransition);
++ auto initialTransitionTargets = m_stateTable->array(initialTransition.targets);
++ for (int targetStateIndex : initialTransitionTargets)
++ addDescendantStatesToEnter(targetStateIndex, statesToEnter,
++ statesForDefaultEntry, defaultHistoryContent);
++ for (int targetStateIndex : initialTransitionTargets)
++ addAncestorStatesToEnter(targetStateIndex, stateIndex, statesToEnter,
++ statesForDefaultEntry, defaultHistoryContent);
++ }
++ } else {
++ if (state.isParallel()) {
++ for (int child : getChildStates(state)) {
++ if (!hasDescendant(*statesToEnter, child))
++ addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ }
++ }
++ }
++ }
++ }
++
++ void QScxmlStateMachinePrivate::addAncestorStatesToEnter(
++ int stateIndex, int ancestorIndex, OrderedSet *statesToEnter,
++ OrderedSet *statesForDefaultEntry, HistoryContent *defaultHistoryContent) const
++ {
++ Q_ASSERT(statesToEnter);
++ Q_ASSERT(statesForDefaultEntry);
++ Q_ASSERT(defaultHistoryContent);
++
++ std::vector<int> ancestors;
++ getProperAncestors(&ancestors, stateIndex, ancestorIndex);
++ for (int anc : ancestors) {
++ if (anc == -1) {
++ // we can't enter the state machine itself, so:
++ continue;
++ }
++ statesToEnter->add(anc);
++ const auto &ancState = m_stateTable->state(anc);
++ if (ancState.isParallel()) {
++ for (int child : getChildStates(ancState)) {
++ if (!hasDescendant(*statesToEnter, child))
++ addDescendantStatesToEnter(child, statesToEnter, statesForDefaultEntry,
++ defaultHistoryContent);
++ }
++ }
++ }
++ }
++
++ std::vector<int> QScxmlStateMachinePrivate::getChildStates(
++ const QScxmlExecutableContent::StateTable::State &state) const
++ {
++ std::vector<int> childStates;
++ auto kids = m_stateTable->array(state.childStates);
++ if (kids.isValid()) {
++ childStates.reserve(kids.size());
++ for (int kiddo : kids) {
++ switch (m_stateTable->state(kiddo).type) {
++ case StateTable::State::Normal:
++ case StateTable::State::Final:
++ case StateTable::State::Parallel:
++ childStates.push_back(kiddo);
++ break;
++ default:
++ break;
++ }
++ }
++ }
++ return childStates;
++ }
++
++ bool QScxmlStateMachinePrivate::hasDescendant(const OrderedSet &statesToEnter, int childIdx) const
++ {
++ for (int s : statesToEnter) {
++ if (isDescendant(s, childIdx))
++ return true;
++ }
++ return false;
++ }
++
++ bool QScxmlStateMachinePrivate::allDescendants(const OrderedSet &statesToEnter, int childdx) const
++ {
++ for (int s : statesToEnter) {
++ if (!isDescendant(s, childdx))
++ return false;
++ }
++ return true;
++ }
++
++ bool QScxmlStateMachinePrivate::isDescendant(int state1, int state2) const
++ {
++ int parent = state1;
++ do {
++ parent = m_stateTable->state(parent).parent;
++ if (parent == state2)
++ return true;
++ } while (parent != -1);
++ return false;
++ }
++
++ bool QScxmlStateMachinePrivate::allInFinalStates(const std::vector<int> &states) const
++ {
++ if (states.empty())
++ return false;
++
++ for (int idx : states) {
++ if (!isInFinalState(idx))
++ return false;
++ }
++
++ return true;
++ }
++
++ bool QScxmlStateMachinePrivate::someInFinalStates(const std::vector<int> &states) const
++ {
++ for (int stateIndex : states) {
++ const auto &state = m_stateTable->state(stateIndex);
++ if (state.type == StateTable::State::Final && m_configuration.contains(stateIndex))
++ return true;
++ }
++ return false;
++ }
++
++ bool QScxmlStateMachinePrivate::isInFinalState(int stateIndex) const
++ {
++ const auto &state = m_stateTable->state(stateIndex);
++ if (state.isCompound())
++ return someInFinalStates(getChildStates(state)) && m_configuration.contains(stateIndex);
++ else if (state.isParallel())
++ return allInFinalStates(getChildStates(state));
++ else
++ return false;
++ }
++
++ int QScxmlStateMachinePrivate::getTransitionDomain(int transitionIndex) const
++ {
++ const auto &transition = m_stateTable->transition(transitionIndex);
++ if (transition.source == -1)
++ //oooh, we have the initial transition of the state machine.
++ return -1;
++
++ OrderedSet tstates;
++ getEffectiveTargetStates(&tstates, transitionIndex);
++ if (tstates.isEmpty()) {
++ return StateTable::InvalidIndex;
++ } else {
++ const auto &sourceState = m_stateTable->state(transition.source);
++ if (transition.type == StateTable::Transition::Internal
++ && sourceState.isCompound()
++ && allDescendants(tstates, transition.source)) {
++ return transition.source;
++ } else {
++ tstates.add(transition.source);
++ return findLCCA(std::move(tstates));
++ }
++ }
++ }
++
++ int QScxmlStateMachinePrivate::findLCCA(OrderedSet &&states) const
++ {
++ std::vector<int> ancestors;
++ const int head = *states.begin();
++ OrderedSet tail(std::move(states));
++ tail.removeHead();
++
++ getProperAncestors(&ancestors, head, StateTable::InvalidIndex);
++ for (int anc : ancestors) {
++ if (anc != -1) { // the state machine itself is always compound
++ const auto &ancState = m_stateTable->state(anc);
++ if (!ancState.isCompound())
++ continue;
++ }
++
++ if (allDescendants(tail, anc))
++ return anc;
++ }
++
++ return StateTable::InvalidIndex;
++ }
++
++ void QScxmlStateMachinePrivate::getEffectiveTargetStates(OrderedSet *targets,
++ int transitionIndex) const
++ {
++ Q_ASSERT(targets);
++
++ const auto &transition = m_stateTable->transition(transitionIndex);
++ for (int s : m_stateTable->array(transition.targets)) {
++ const auto &state = m_stateTable->state(s);
++ if (state.isHistoryState()) {
++ HistoryValues::const_iterator historyValueIter = m_historyValue.find(s);
++ if (historyValueIter != m_historyValue.end()) {
++ for (int historyState : historyValueIter.value()) {
++ targets->add(historyState);
++ }
++ } else if (state.transitions != StateTable::InvalidIndex) {
++ getEffectiveTargetStates(targets, m_stateTable->array(state.transitions)[0]);
++ }
++ } else {
++ targets->add(s);
++ }
++ }
++ }
++
++ /*!
++ * Creates a state machine from the SCXML file specified by \a fileName.
++ *
++ * This method will always return a state machine. If errors occur while reading the SCXML file,
++ * the state machine cannot be started. The errors can be retrieved by calling the parseErrors()
++ * method.
++ *
++ * \sa parseErrors()
++ */
++ QScxmlStateMachine *QScxmlStateMachine::fromFile(const QString &fileName)
++ {
++ QFile scxmlFile(fileName);
++ if (!scxmlFile.open(QIODevice::ReadOnly)) {
++ auto stateMachine = new QScxmlStateMachine(&QScxmlStateMachine::staticMetaObject);
++ QScxmlError err(scxmlFile.fileName(), 0, 0, QStringLiteral("cannot open for reading"));
++ QScxmlStateMachinePrivate::get(stateMachine)->parserData()->m_errors.append(err);
++ return stateMachine;
++ }
++
++ QScxmlStateMachine *stateMachine = fromData(&scxmlFile, fileName);
++ scxmlFile.close();
++ return stateMachine;
++ }
++
++ /*!
++ * Creates a state machine by reading from the QIODevice specified by \a data.
++ *
++ * This method will always return a state machine. If errors occur while reading the SCXML file,
++ * \a fileName, the state machine cannot be started. The errors can be retrieved by calling the
++ * parseErrors() method.
++ *
++ * \sa parseErrors()
++ */
++ QScxmlStateMachine *QScxmlStateMachine::fromData(QIODevice *data, const QString &fileName)
++ {
++ QXmlStreamReader xmlReader(data);
++ QScxmlCompiler compiler(&xmlReader);
++ compiler.setFileName(fileName);
++ return compiler.compile();
++ }
++
++ QList<QScxmlError> QScxmlStateMachine::parseErrors() const
++ {
++ Q_D(const QScxmlStateMachine);
++ return d->m_parserData ? d->m_parserData->m_errors : QList<QScxmlError>();
++ }
++
++ QScxmlStateMachine::QScxmlStateMachine(const QMetaObject *metaObject, QObject *parent)
++ : QObject(*new QScxmlStateMachinePrivate(metaObject), parent)
++ {
++ Q_D(QScxmlStateMachine);
++ d->m_executionEngine = new QScxmlExecutionEngine(this);
++ }
++
++ QScxmlStateMachine::QScxmlStateMachine(QScxmlStateMachinePrivate &dd, QObject *parent)
++ : QObject(dd, parent)
++ {
++ Q_D(QScxmlStateMachine);
++ d->m_executionEngine = new QScxmlExecutionEngine(this);
++ }
++
++ /*!
++ \property QScxmlStateMachine::running
++
++ \brief the running state of this state machine
++
++ \sa start()
++ */
++
++ /*!
++ \qmlproperty bool ScxmlStateMachine::running
++
++ The running state of this state machine.
++ */
++
++ /*!
++ \property QScxmlStateMachine::dataModel
++
++ \brief The data model to be used for this state machine.
++
++ SCXML data models are described in
++ \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
++ information about supported data models, see \l {SCXML Compliance}.
++
++ Changing the data model when the state machine has been \c initialized is
++ not specified in the SCXML standard and leads to undefined behavior.
++
++ \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
++ */
++
++ /*!
++ \qmlproperty ScxmlDataModel ScxmlStateMachine::dataModel
++
++ The data model to be used for this state machine.
++
++ SCXML data models are described in
++ \l {SCXML Specification - 5 Data Model and Data Manipulation}. For more
++ information about supported data models, see \l {SCXML Compliance}.
++
++ Changing the data model when the state machine has been \l initialized is
++ not specified in the SCXML standard and leads to undefined behavior.
++
++ \sa QScxmlDataModel, QScxmlNullDataModel, QScxmlCppDataModel
++ */
++
++ /*!
++ \property QScxmlStateMachine::initialized
++
++ \brief Whether the state machine has been initialized.
++
++ It is \c true if the state machine has been initialized, \c false otherwise.
++
++ \sa QScxmlStateMachine::init(), QScxmlDataModel
++ */
++
++ /*!
++ \qmlproperty bool ScxmlStateMachine::initialized
++
++ This read-only property is set to \c true if the state machine has been
++ initialized, \c false otherwise.
++ */
++
++ /*!
++ \property QScxmlStateMachine::initialValues
++
++ \brief The initial values to be used for setting up the data model.
++
++ \sa QScxmlStateMachine::init(), QScxmlDataModel
++ */
++
++ /*!
++ \qmlproperty var ScxmlStateMachine::initialValues
++
++ The initial values to be used for setting up the data model.
++ */
++
++ /*!
++ \property QScxmlStateMachine::sessionId
++
++ \brief The session ID of the current state machine.
++
++ The session ID is used for message routing between parent and child state machines. If a state
++ machine is started by an \c <invoke> element, any event it sends will have the \c invokeid field
++ set to the session ID. The state machine will use the origin of an event (which is set by the
++ \e target or \e targetexpr attribute in a \c <send> element) to dispatch messages to the correct
++ child state machine.
++
++ \sa QScxmlEvent::invokeId()
++ */
++
++ /*!
++ \qmlproperty string ScxmlStateMachine::sessionId
++
++ The session ID of the current state machine.
++
++ The session ID is used for message routing between parent and child state
++ machines. If a state machine is started by an \c <invoke> element, any event
++ it sends will have the \c invokeid field set to the session ID. The state
++ machine will use the origin of an event (which is set by the \e target or
++ \e targetexpr attribute in a \c <send> element) to dispatch messages to the
++ correct child state machine.
++ */
++
++ /*!
++ \property QScxmlStateMachine::name
++
++ \brief The name of the state machine as set by the \e name attribute of the \c <scxml> tag.
++ */
++
++ /*!
++ \qmlproperty string ScxmlStateMachine::name
++
++ The name of the state machine as set by the \e name attribute of the
++ \c <scxml> tag.
++ */
++
++ /*!
++ \property QScxmlStateMachine::invoked
++
++ \brief Whether the state machine was invoked from an outer state machine.
++
++ \c true when the state machine was started as a service with the \c <invoke> element,
++ \c false otherwise.
++ */
++
++ /*!
++ \qmlproperty bool ScxmlStateMachine::invoked
++
++ Whether the state machine was invoked from an outer state machine.
++
++ This read-only property is set to \c true when the state machine was started
++ as a service with the \c <invoke> element, \c false otherwise.
++ */
++
++ /*!
++ \property QScxmlStateMachine::parseErrors
++
++ \brief The list of parse errors that occurred while creating a state machine from an SCXML file.
++ */
++
++ /*!
++ \qmlproperty var ScxmlStateMachine::parseErrors
++
++ The list of parse errors that occurred while creating a state machine from
++ an SCXML file.
++ */
++
++ /*!
++ \property QScxmlStateMachine::loader
++
++ \brief The loader that is currently used to resolve and load URIs for the state machine.
++ */
++
++ /*!
++ \qmlproperty Loader ScxmlStateMachine::loader
++
++ The loader that is currently used to resolve and load URIs for the state
++ machine.
++ */
++
++ /*!
++ \property QScxmlStateMachine::tableData
++
++ \brief The table data that is used when generating C++ from an SCXML file.
++
++ The class implementing
++ the state machine will use this property to assign the generated table
++ data. The state machine does not assume ownership of the table data.
++ */
++
++ QString QScxmlStateMachine::sessionId() const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ return d->m_sessionId;
++ }
++
++ QString QScxmlStateMachinePrivate::generateSessionId(const QString &prefix)
++ {
++ int id = ++QScxmlStateMachinePrivate::m_sessionIdCounter;
++ return prefix + QString::number(id);
++ }
++
++ bool QScxmlStateMachine::isInvoked() const
++ {
++ Q_D(const QScxmlStateMachine);
++ return d->m_isInvoked;
++ }
++
++ bool QScxmlStateMachine::isInitialized() const
++ {
++ Q_D(const QScxmlStateMachine);
++ return d->m_isInitialized;
++ }
++
++ QBindable<bool> QScxmlStateMachine::bindableInitialized() const
++ {
++ Q_D(const QScxmlStateMachine);
++ return &d->m_isInitialized;
++ }
++
++ /*!
++ * Sets the data model for this state machine to \a model. There is a 1:1
++ * relation between state machines and models. After setting the model once you
++ * cannot change it anymore. Any further attempts to set the model using this
++ * method will be ignored.
++ */
++ void QScxmlStateMachine::setDataModel(QScxmlDataModel *model)
++ {
++ Q_D(QScxmlStateMachine);
++
++ if (d->m_dataModel.valueBypassingBindings() == nullptr && model != nullptr) {
++ // the binding is removed only on the first valid set
++ // as the later attempts are ignored
++ d->m_dataModel.removeBindingUnlessInWrapper();
++ d->m_dataModel.setValueBypassingBindings(model);
++ model->setStateMachine(this);
++ d->m_dataModel.notify();
++ emit dataModelChanged(model);
++ }
++ }
++
++ /*!
++ * Returns the data model used by the state machine.
++ */
++ QScxmlDataModel *QScxmlStateMachine::dataModel() const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ return d->m_dataModel;
++ }
++
++ QBindable<QScxmlDataModel*> QScxmlStateMachine::bindableDataModel()
++ {
++ Q_D(QScxmlStateMachine);
++ return &d->m_dataModel;
++ }
++
++ void QScxmlStateMachine::setLoader(QScxmlCompiler::Loader *loader)
++ {
++ Q_D(QScxmlStateMachine);
++ d->m_loader.setValue(loader);
++ }
++
++ QScxmlCompiler::Loader *QScxmlStateMachine::loader() const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ return d->m_loader;
++ }
++
++ QBindable<QScxmlCompiler::Loader*> QScxmlStateMachine::bindableLoader()
++ {
++ Q_D(QScxmlStateMachine);
++ return &d->m_loader;
++ }
++
++ QScxmlTableData *QScxmlStateMachine::tableData() const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ return d->m_tableData;
++ }
++
++ void QScxmlStateMachine::setTableData(QScxmlTableData *tableData)
++ {
++ Q_D(QScxmlStateMachine);
++
++ d->m_tableData.removeBindingUnlessInWrapper();
++ if (d->m_tableData.valueBypassingBindings() == tableData)
++ return;
++
++ d->m_tableData.setValueBypassingBindings(tableData);
++ if (tableData) {
++ d->m_stateTable = reinterpret_cast<const QScxmlExecutableContent::StateTable *>(
++ tableData->stateMachineTable());
++ // cannot use objectName() here, because it creates binding loop
++ const QString currentObjectName = d->extraData
++ ? d->extraData->objectName.valueBypassingBindings() : QString();
++ if (currentObjectName.isEmpty()) {
++ setObjectName(tableData->name());
++ }
++ if (d->m_stateTable->maxServiceId != QScxmlExecutableContent::StateTable::InvalidIndex) {
++ const size_t serviceCount = size_t(d->m_stateTable->maxServiceId + 1);
++ d->m_invokedServices.resize(serviceCount, { -1, nullptr, QString() });
++ d->m_cachedFactories.resize(serviceCount, nullptr);
++ }
++
++ if (d->m_stateTable->version != Q_QSCXMLC_OUTPUT_REVISION) {
++ qFatal("Cannot mix incompatible state table (version 0x%x) with this library "
++ "(version 0x%x)", d->m_stateTable->version, Q_QSCXMLC_OUTPUT_REVISION);
++ }
++ Q_ASSERT(tableData->stateMachineTable()[d->m_stateTable->arrayOffset +
++ d->m_stateTable->arraySize]
++ == QScxmlExecutableContent::StateTable::terminator);
++ }
++
++ d->updateMetaCache();
++
++ d->m_tableData.notify();
++ emit tableDataChanged(tableData);
++ }
++
++ QBindable<QScxmlTableData*> QScxmlStateMachine::bindableTableData()
++ {
++ Q_D(QScxmlStateMachine);
++ return &d->m_tableData;
++ }
++
++ /*!
++ \qmlmethod list<string> ScxmlStateMachine::stateNames(bool compress)
++
++ Retrieves a list of state names of all states.
++
++ When \a compress is \c true (the default), the states that contain child
++ states is filtered out and only the \e {leaf states} is returned. When it
++ is \c false, the full list of all states is returned.
++
++ The returned list does not contain the states of possible nested state
++ machines.
++
++ \note The order of the state names in the list is the order in which the
++ states occurred in the SCXML document.
++ */
++
++ /*!
++ * Retrieves a list of state names of all states.
++ *
++ * When \a compress is \c true (the default), the states that contain child states
++ * will be filtered out and only the \e {leaf states} will be returned.
++ * When it is \c false, the full list of all states will be returned.
++ *
++ * The returned list does not contain the states of possible nested state machines.
++ *
++ * \note The order of the state names in the list is the order in which the states occurred in
++ * the SCXML document.
++ */
++ QStringList QScxmlStateMachine::stateNames(bool compress) const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ QStringList names;
++ for (int i = 0; i < d->m_stateTable->stateCount; ++i) {
++ const auto &state = d->m_stateTable->state(i);
++ if (!compress || state.isAtomic())
++ names.append(d->m_tableData.value()->string(state.name));
++ }
++ return names;
++ }
++
++ /*!
++ \qmlmethod list<string> ScxmlStateMachine::activeStateNames(bool compress)
++
++ Retrieves a list of state names of all active states.
++
++ When a state is active, all its parent states are active by definition. When
++ \a compress is \c true (the default), the parent states are filtered out and
++ only the \e {leaf states} are returned. When it is \c false, the full list
++ of active states is returned.
++ */
++
++ /*!
++ * Retrieves a list of state names of all active states.
++ *
++ * When a state is active, all its parent states are active by definition. When \a compress
++ * is \c true (the default), the parent states will be filtered out and only the \e {leaf states}
++ * will be returned. When it is \c false, the full list of active states will be returned.
++ */
++ QStringList QScxmlStateMachine::activeStateNames(bool compress) const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ QStringList result;
++ for (int stateIdx : d->m_configuration) {
++ const auto &state = d->m_stateTable->state(stateIdx);
++ if (state.isAtomic() || !compress)
++ result.append(d->m_tableData.value()->string(state.name));
++ }
++ return result;
++ }
++
++ /*!
++ \qmlmethod bool ScxmlStateMachine::isActive(string scxmlStateName)
++
++ Returns \c true if the state specified by \a scxmlStateName is active,
++ \c false otherwise.
++ */
++
++ /*!
++ * Returns \c true if the state specified by \a scxmlStateName is active, \c false otherwise.
++ */
++ bool QScxmlStateMachine::isActive(const QString &scxmlStateName) const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ for (int stateIndex : d->m_configuration) {
++ const auto &state = d->m_stateTable->state(stateIndex);
++ if (d->m_tableData.value()->string(state.name) == scxmlStateName)
++ return true;
++ }
++
++ return false;
++ }
++
++ QMetaObject::Connection QScxmlStateMachine::connectToStateImpl(const QString &scxmlStateName,
++ const QObject *receiver, void **slot,
++ QtPrivate::QSlotObjectBase *slotObj,
++ Qt::ConnectionType type)
++ {
++ static const int metaTypeIds[] = { QMetaType::Bool, 0 };
++ const int *types = metaTypeIds;
++
++ Q_D(QScxmlStateMachine);
++ const int signalIndex = d->m_stateNameToSignalIndex.value(scxmlStateName, -1);
++ return signalIndex < 0 ? QMetaObject::Connection()
++ : QObjectPrivate::connectImpl(this, signalIndex, receiver, slot, slotObj,
++ type, types, d->m_metaObject);
++ }
++
++ /*!
++ Creates a connection of the given \a type from the state identified by \a scxmlStateName
++ to the \a method in the \a receiver object. The receiver's \a method
++ may take a boolean argument that indicates whether the state connected
++ became active or inactive. For example:
++
++ \code
++ void mySlot(bool active);
++ \endcode
++
++ Returns a handle to the connection, which can be used later to disconnect.
++ */
++ QMetaObject::Connection QScxmlStateMachine::connectToState(const QString &scxmlStateName,
++ const QObject *receiver,
++ const char *method,
++ Qt::ConnectionType type)
++ {
++ QByteArray signalName = QByteArray::number(QSIGNAL_CODE) + scxmlStateName.toUtf8()
++ + "Changed(bool)";
++ return QObject::connect(this, signalName.constData(), receiver, method, type);
++ }
++
++ /*!
++ Creates a connection of the specified \a type from the event specified by
++ \a scxmlEventSpec to the \a method in the \a receiver object. The receiver's
++ \a method may take a QScxmlEvent as a parameter. For example:
++
++ \code
++ void mySlot(const QScxmlEvent &event);
++ \endcode
++
++ In contrast to event specifications in SCXML documents, spaces are not
++ allowed in the \a scxmlEventSpec here. In order to connect to multiple
++ events with different prefixes, connectToEvent() has to be called multiple
++ times.
++
++ Returns a handle to the connection, which can be used later to disconnect.
++ */
++ QMetaObject::Connection QScxmlStateMachine::connectToEvent(const QString &scxmlEventSpec,
++ const QObject *receiver,
++ const char *method,
++ Qt::ConnectionType type)
++ {
++ Q_D(QScxmlStateMachine);
++ return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, method,
++ type);
++ }
++
++ QMetaObject::Connection QScxmlStateMachine::connectToEventImpl(const QString &scxmlEventSpec,
++ const QObject *receiver, void **slot,
++ QtPrivate::QSlotObjectBase *slotObj,
++ Qt::ConnectionType type)
++ {
++ Q_D(QScxmlStateMachine);
++ return d->m_router.connectToEvent(scxmlEventSpec.split(QLatin1Char('.')), receiver, slot,
++ slotObj, type);
++ }
++
++ /*!
++ \qmlmethod bool ScxmlStateMachine::init()
++
++ Initializes the state machine by setting the initial values for \c <data>
++ elements and executing any \c <script> tags of the \c <scxml> tag. The
++ initial data values are taken from the \l initialValues property.
++
++ Returns \c false if parse errors occur or if any of the initialization steps
++ fail. Returns \c true otherwise.
++ */
++
++ /*!
++ * Initializes the state machine.
++ *
++ * State machine initialization consists of calling QScxmlDataModel::setup(), setting the initial
++ * values for \c <data> elements, and executing any \c <script> tags of the \c <scxml> tag. The
++ * initial data values are taken from the \c initialValues property.
++ *
++ * Returns \c false if parse errors occur or if any of the initialization steps fail.
++ * Returns \c true otherwise.
++ */
++ bool QScxmlStateMachine::init()
++ {
++ Q_D(QScxmlStateMachine);
++
++ if (d->m_isInitialized.value())
++ return false;
++
++ if (!parseErrors().isEmpty())
++ return false;
++
++ if (!dataModel() || !dataModel()->setup(d->m_initialValues.value()))
++ return false;
++
++ if (!d->executeInitialSetup())
++ return false;
++
++ d->m_isInitialized.setValue(true);
++ return true;
++ }
++
++ /*!
++ * Returns \c true if the state machine is running, \c false otherwise.
++ *
++ * \sa setRunning(), runningChanged()
++ */
++ bool QScxmlStateMachine::isRunning() const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ return d->isRunnable() && !d->isPaused();
++ }
++
++ /*!
++ * Starts the state machine if \a running is \c true, or stops it otherwise.
++ *
++ * \sa start(), stop(), isRunning(), runningChanged()
++ */
++ void QScxmlStateMachine::setRunning(bool running)
++ {
++ if (running)
++ start();
++ else
++ stop();
++ }
++
++ QVariantMap QScxmlStateMachine::initialValues()
++ {
++ Q_D(const QScxmlStateMachine);
++ return d->m_initialValues;
++ }
++
++ void QScxmlStateMachine::setInitialValues(const QVariantMap &initialValues)
++ {
++ Q_D(QScxmlStateMachine);
++ d->m_initialValues.setValue(initialValues);
++ }
++
++ QBindable<QVariantMap> QScxmlStateMachine::bindableInitialValues()
++ {
++ Q_D(QScxmlStateMachine);
++ return &d->m_initialValues;
++ }
++
++ QString QScxmlStateMachine::name() const
++ {
++ return tableData()->name();
++ }
++
++ /*!
++ \qmlmethod void ScxmlStateMachine::submitEvent(event)
++
++ Submits the SCXML event \a event to the internal or external event queue
++ depending on the priority of the event.
++
++ When a delay is set, the event will be queued for delivery after the timeout
++ has passed. The state machine takes ownership of the event and deletes it
++ after processing.
++
++ \sa QScxmlEvent
++ */
++
++ /*!
++ * Submits the SCXML event \a event to the internal or external event queue depending on the
++ * priority of the event.
++ *
++ * When a delay is set, the event will be queued for delivery after the timeout has passed.
++ * The state machine takes ownership of \a event and deletes it after processing.
++ */
++ void QScxmlStateMachine::submitEvent(QScxmlEvent *event)
++ {
++ Q_D(QScxmlStateMachine);
++
++ if (!event)
++ return;
++
++ if (event->delay() > 0) {
++ qCDebug(qscxmlLog) << this << "submitting event" << event->name()
++ << "with delay" << event->delay() << "ms:"
++ << QScxmlEventPrivate::debugString(event).constData();
++
++ Q_ASSERT(event->eventType() == QScxmlEvent::ExternalEvent);
++ d->submitDelayedEvent(event);
++ } else {
++ qCDebug(qscxmlLog) << this << "submitting event" << event->name()
++ << ":" << QScxmlEventPrivate::debugString(event).constData();
++
++ d->routeEvent(event);
++ }
++ }
++
++ /*!
++ * A utility method to create and submit an external event with the specified
++ * \a eventName as the name.
++ */
++ void QScxmlStateMachine::submitEvent(const QString &eventName)
++ {
++ QScxmlEvent *e = new QScxmlEvent;
++ e->setName(eventName);
++ e->setEventType(QScxmlEvent::ExternalEvent);
++ submitEvent(e);
++ }
++ /*!
++ \qmlmethod void ScxmlStateMachine::submitEvent(string eventName, var data)
++
++ A utility method to create and submit an external event with the specified
++ \a eventName as the name and \a data as the payload data (optional).
++ */
++
++ /*!
++ * A utility method to create and submit an external event with the specified
++ * \a eventName as the name and \a data as the payload data.
++ */
++ void QScxmlStateMachine::submitEvent(const QString &eventName, const QVariant &data)
++ {
++ QScxmlEvent *e = new QScxmlEvent;
++ e->setName(eventName);
++ e->setEventType(QScxmlEvent::ExternalEvent);
++ e->setData(data);
++ submitEvent(e);
++ }
++
++ /*!
++ \qmlmethod void ScxmlStateMachine::cancelDelayedEvent(string sendId)
++
++ Cancels a delayed event with the specified \a sendId.
++ */
++
++ /*!
++ * Cancels a delayed event with the specified \a sendId.
++ */
++ void QScxmlStateMachine::cancelDelayedEvent(const QString &sendId)
++ {
++ Q_D(QScxmlStateMachine);
++
++ for (auto it = d->m_delayedEvents.begin(), eit = d->m_delayedEvents.end(); it != eit; ++it) {
++ if (it->second->sendId() == sendId) {
++ qCDebug(qscxmlLog) << this
++ << "canceling event" << sendId
++ << "with timer id" << it->first;
++ d->m_eventLoopHook.killTimer(it->first);
++ delete it->second;
++ d->m_delayedEvents.erase(it);
++ return;
++ }
++ }
++ }
++
++ /*!
++ \qmlmethod bool ScxmlStateMachine::isDispatchableTarget(string target)
++
++ Returns \c true if a message to \a target can be dispatched by this state
++ machine.
++
++ Valid targets are:
++ \list
++ \li \c #_parent for the parent state machine if the current state machine
++ is started by \c <invoke>
++ \li \c #_internal for the current state machine
++ \li \c #_scxml_sessionid, where \c sessionid is the session ID of the
++ current state machine
++ \li \c #_servicename, where \c servicename is the ID or name of a service
++ started with \c <invoke> by this state machine
++ \endlist
++ */
++
++ /*!
++ * Returns \c true if a message to \a target can be dispatched by this state machine.
++ *
++ * Valid targets are:
++ * \list
++ * \li \c #_parent for the parent state machine if the current state machine is started by
++ * \c <invoke>
++ * \li \c #_internal for the current state machine
++ * \li \c #_scxml_sessionid, where \c sessionid is the session ID of the current state machine
++ * \li \c #_servicename, where \c servicename is the ID or name of a service started with
++ * \c <invoke> by this state machine
++ * \endlist
++ */
++ bool QScxmlStateMachine::isDispatchableTarget(const QString &target) const
++ {
++ Q_D(const QScxmlStateMachine);
++
++ if (isInvoked() && target == QStringLiteral("#_parent"))
++ return true; // parent state machine, if we're <invoke>d.
++ if (target == QStringLiteral("#_internal")
++ || target == QStringLiteral("#_scxml_%1").arg(sessionId()))
++ return true; // that's the current state machine
++
++ if (target.startsWith(QStringLiteral("#_"))) {
++ QStringView targetId = QStringView{target}.mid(2);
++ for (const auto &invokedService : d->m_invokedServices) {
++ if (invokedService.service && invokedService.service->id() == targetId)
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ /*!
++ \qmlproperty list ScxmlStateMachine::invokedServices
++
++ A list of SCXML services that were invoked from the main state machine
++ (possibly recursively).
++ */
++
++ /*!
++ \property QScxmlStateMachine::invokedServices
++ \brief A list of SCXML services that were invoked from the main
++ state machine (possibly recursively).
++ */
++
++ QList<QScxmlInvokableService *> QScxmlStateMachine::invokedServices() const
++ {
++ Q_D(const QScxmlStateMachine);
++ return d->m_invokedServicesComputedProperty;
++ }
++
++ QBindable<QList<QScxmlInvokableService*>> QScxmlStateMachine::bindableInvokedServices()
++ {
++ Q_D(QScxmlStateMachine);
++ return &d->m_invokedServicesComputedProperty;
++ }
++
++ /*!
++ \fn QScxmlStateMachine::runningChanged(bool running)
++
++ This signal is emitted when the \c running property is changed with \a running as argument.
++ */
++
++ /*!
++ \fn QScxmlStateMachine::log(const QString &label, const QString &msg)
++
++ This signal is emitted if a \c <log> tag is used in the SCXML. \a label is the value of the
++ \e label attribute of the \c <log> tag. \a msg is the value of the evaluated \e expr attribute
++ of the \c <log> tag. If there is no \e expr attribute, a null string will be returned.
++ */
++
++ /*!
++ \qmlsignal ScxmlStateMachine::log(string label, string msg)
++
++ This signal is emitted if a \c <log> tag is used in the SCXML. \a label is
++ the value of the \e label attribute of the \c <log> tag. \a msg is the value
++ of the evaluated \e expr attribute of the \c <log> tag. If there is no
++ \e expr attribute, a null string will be returned.
++
++ The corresponding signal handler is \c onLog().
++ */
++
++ /*!
++ \fn QScxmlStateMachine::reachedStableState()
++
++ This signal is emitted when the event queue is empty at the end of a macro step or when a final
++ state is reached.
++ */
++
++ /*!
++ \qmlsignal ScxmlStateMachine::reachedStableState()
++
++ This signal is emitted when the event queue is empty at the end of a macro
++ step or when a final state is reached.
++
++ The corresponding signal handler is \c onreachedStableState().
++ */
++
++ /*!
++ \fn QScxmlStateMachine::finished()
++
++ This signal is emitted when the state machine reaches a top-level final state.
++
++ \sa running
++ */
++
++ /*!
++ \qmlsignal ScxmlStateMachine::finished()
++
++ This signal is emitted when the state machine reaches a top-level final
++ state.
++
++ The corresponding signal handler is \c onFinished().
++ */
++
++ /*!
++ \qmlmethod void ScxmlStateMachine::start()
++
++ Starts this state machine. The machine resets its configuration and
++ transitions to the initial state. When a final top-level state
++ is entered, the machine emits the finished() signal.
++
++ \sa stop(), finished()
++ */
++
++ /*!
++ Starts this state machine. When a final top-level state
++ is entered, the machine will emit the finished() signal.
++
++ \note A state machine will not run without a running event loop, such as
++ the main application event loop started with QCoreApplication::exec() or
++ QApplication::exec().
++
++ \note Calling start() after stop() will not result in a full reset of its
++ configuration yet, hence it is strongly advised not to do it.
++
++ \note Starting a finished machine yields a warning.
++
++ \sa runningChanged(), setRunning(), stop(), finished()
++ */
++ void QScxmlStateMachine::start()
++ {
++ Q_D(QScxmlStateMachine);
++
++ if (d->isFinished()) {
++ qCWarning(qscxmlLog) << this << "Can't start finished machine";
++ }
++
++ if (!parseErrors().isEmpty())
++ return;
++
++ // Failure to initialize doesn't prevent start(). See w3c-ecma/test487 in the scion test suite.
++ if (!isInitialized() && !init())
++ qCDebug(qscxmlLog) << this << "cannot be initialized on start(). Starting anyway ...";
++
++ d->start();
++ d->m_eventLoopHook.queueProcessEvents();
++ }
++
++ /*!
++ \qmlmethod void ScxmlStateMachine::stop()
++
++ Stops this state machine. The machine will not execute any further state
++ transitions. Its \l running property is set to \c false.
++
++ \sa start(), finished()
++ */
++
++ /*!
++ Stops this state machine. The machine will not execute any further state
++ transitions. Its \c running property is set to \c false.
++
++ \sa runningChanged(), start(), setRunning()
++ */
++ void QScxmlStateMachine::stop()
++ {
++ Q_D(QScxmlStateMachine);
++ d->pause();
++ }
++
++ /*!
++ Returns \c true if the state with the ID \a stateIndex is active.
++
++ This method is part of the interface to the compiled representation of SCXML
++ state machines. It should only be used internally and by state machines
++ compiled from SCXML documents.
++ */
++ bool QScxmlStateMachine::isActive(int stateIndex) const
++ {
++ Q_D(const QScxmlStateMachine);
++ // Here we need to find the actual internal state index that corresponds with the
++ // index of the compiled metaobject (which is same as its mapped signal index).
++ // See updateMetaCache()
++ const int mappedStateIndex = d->m_stateIndexToSignalIndex.key(stateIndex, -1);
++ return d->m_configuration.contains(mappedStateIndex);
++ }
++
++ QT_END_NAMESPACE