| 1 | /**************************************************************************** | 
| 2 | ** | 
| 3 | ** Copyright (C) 2016 The Qt Company Ltd. | 
| 4 | ** Contact: https://www.qt.io/licensing/ | 
| 5 | ** | 
| 6 | ** This file is part of the QtScxml module of the Qt Toolkit. | 
| 7 | ** | 
| 8 | ** $QT_BEGIN_LICENSE:LGPL$ | 
| 9 | ** Commercial License Usage | 
| 10 | ** Licensees holding valid commercial Qt licenses may use this file in | 
| 11 | ** accordance with the commercial license agreement provided with the | 
| 12 | ** Software or, alternatively, in accordance with the terms contained in | 
| 13 | ** a written agreement between you and The Qt Company. For licensing terms | 
| 14 | ** and conditions see https://www.qt.io/terms-conditions. For further | 
| 15 | ** information use the contact form at https://www.qt.io/contact-us. | 
| 16 | ** | 
| 17 | ** GNU Lesser General Public License Usage | 
| 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser | 
| 19 | ** General Public License version 3 as published by the Free Software | 
| 20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the | 
| 21 | ** packaging of this file. Please review the following information to | 
| 22 | ** ensure the GNU Lesser General Public License version 3 requirements | 
| 23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. | 
| 24 | ** | 
| 25 | ** GNU General Public License Usage | 
| 26 | ** Alternatively, this file may be used under the terms of the GNU | 
| 27 | ** General Public License version 2.0 or (at your option) the GNU General | 
| 28 | ** Public license version 3 or any later version approved by the KDE Free | 
| 29 | ** Qt Foundation. The licenses are as published by the Free Software | 
| 30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 | 
| 31 | ** included in the packaging of this file. Please review the following | 
| 32 | ** information to ensure the GNU General Public License requirements will | 
| 33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and | 
| 34 | ** https://www.gnu.org/licenses/gpl-3.0.html. | 
| 35 | ** | 
| 36 | ** $QT_END_LICENSE$ | 
| 37 | ** | 
| 38 | ****************************************************************************/ | 
| 39 |  | 
| 40 | #ifndef QSCXMLSTATEMACHINE_P_H | 
| 41 | #define QSCXMLSTATEMACHINE_P_H | 
| 42 |  | 
| 43 | // | 
| 44 | //  W A R N I N G | 
| 45 | //  ------------- | 
| 46 | // | 
| 47 | // This file is not part of the Qt API.  It exists purely as an | 
| 48 | // implementation detail.  This header file may change from version to | 
| 49 | // version without notice, or even be removed. | 
| 50 | // | 
| 51 | // We mean it. | 
| 52 | // | 
| 53 |  | 
| 54 | #include <QtScxml/private/qscxmlexecutablecontent_p.h> | 
| 55 | #include <QtScxml/qscxmlstatemachine.h> | 
| 56 | #include <QtScxml/private/qscxmlstatemachineinfo_p.h> | 
| 57 | #include <QtCore/private/qobject_p.h> | 
| 58 | #include <QtCore/private/qmetaobject_p.h> | 
| 59 | #include <QtCore/qmetaobject.h> | 
| 60 |  | 
| 61 | QT_BEGIN_NAMESPACE | 
| 62 |  | 
| 63 | namespace QScxmlInternal { | 
| 64 | class EventLoopHook: public QObject | 
| 65 | { | 
| 66 |     Q_OBJECT | 
| 67 |  | 
| 68 |     QScxmlStateMachinePrivate *smp; | 
| 69 |  | 
| 70 | public: | 
| 71 |     EventLoopHook(QScxmlStateMachinePrivate *smp) | 
| 72 |         : smp(smp) | 
| 73 |     {} | 
| 74 |  | 
| 75 |     void queueProcessEvents(); | 
| 76 |  | 
| 77 |     Q_INVOKABLE void doProcessEvents(); | 
| 78 |  | 
| 79 | protected: | 
| 80 |     void timerEvent(QTimerEvent *timerEvent) override; | 
| 81 | }; | 
| 82 |  | 
| 83 | class ScxmlEventRouter : public QObject | 
| 84 | { | 
| 85 |     Q_OBJECT | 
| 86 | public: | 
| 87 |     ScxmlEventRouter(QObject *parent = nullptr) : QObject(parent) {} | 
| 88 |     QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, | 
| 89 |                                            const char *method, Qt::ConnectionType type); | 
| 90 |     QMetaObject::Connection connectToEvent(const QStringList &segments, const QObject *receiver, | 
| 91 |                                            void **slot, QtPrivate::QSlotObjectBase *method, | 
| 92 |                                            Qt::ConnectionType type); | 
| 93 |  | 
| 94 |     void route(const QStringList &segments, QScxmlEvent *event); | 
| 95 |  | 
| 96 | signals: | 
| 97 |     void eventOccurred(const QScxmlEvent &event); | 
| 98 |  | 
| 99 | private: | 
| 100 |     QHash<QString, ScxmlEventRouter *> children; | 
| 101 |     ScxmlEventRouter *child(const QString &segment); | 
| 102 |  | 
| 103 |     void disconnectNotify(const QMetaMethod &signal) override; | 
| 104 | }; | 
| 105 |  | 
| 106 | class StateMachineInfoProxy: public QObject | 
| 107 | { | 
| 108 |     Q_OBJECT | 
| 109 |  | 
| 110 | public: | 
| 111 |     StateMachineInfoProxy(QObject *parent) | 
| 112 |         : QObject(parent) | 
| 113 |     {} | 
| 114 |  | 
| 115 | Q_SIGNALS: | 
| 116 |     void statesEntered(const QVector<QScxmlStateMachineInfo::StateId> &states); | 
| 117 |     void statesExited(const QVector<QScxmlStateMachineInfo::StateId> &states); | 
| 118 |     void transitionsTriggered(const QVector<QScxmlStateMachineInfo::TransitionId> &transitions); | 
| 119 | }; | 
| 120 | } // QScxmlInternal namespace | 
| 121 |  | 
| 122 | class QScxmlInvokableService; | 
| 123 | class QScxmlStateMachinePrivate: public QObjectPrivate | 
| 124 | { | 
| 125 |     Q_DECLARE_PUBLIC(QScxmlStateMachine) | 
| 126 |  | 
| 127 |     static QAtomicInt m_sessionIdCounter; | 
| 128 |  | 
| 129 | public: // types | 
| 130 |     typedef QScxmlExecutableContent::StateTable StateTable; | 
| 131 |  | 
| 132 |     class HistoryContent | 
| 133 |     { | 
| 134 |         QHash<int, int> storage; | 
| 135 |  | 
| 136 |     public: | 
| 137 |         HistoryContent() { storage.reserve(asize: 4); } | 
| 138 |  | 
| 139 |         int &operator[](int idx) { | 
| 140 |             QHash<int, int>::Iterator i = storage.find(akey: idx); | 
| 141 |             return (i == storage.end()) ? storage.insert(akey: idx, avalue: StateTable::InvalidIndex).value() : | 
| 142 |                                           i.value(); | 
| 143 |         } | 
| 144 |  | 
| 145 |         int value(int idx) const { | 
| 146 |             QHash<int, int>::ConstIterator i = storage.constFind(akey: idx); | 
| 147 |             return (i == storage.constEnd()) ? StateTable::InvalidIndex : i.value(); | 
| 148 |         } | 
| 149 |     }; | 
| 150 |  | 
| 151 |     class ParserData | 
| 152 |     { | 
| 153 |     public: | 
| 154 |         QScopedPointer<QScxmlDataModel> m_ownedDataModel; | 
| 155 |         QVector<QScxmlError> m_errors; | 
| 156 |     }; | 
| 157 |  | 
| 158 |     // The OrderedSet is a set where it elements are in insertion order. See | 
| 159 |     // http://www.w3.org/TR/scxml/#AlgorithmforSCXMLInterpretation under Algorithm, Datatypes. It | 
| 160 |     // is used to keep lists of states and transitions in document order. | 
| 161 |     class OrderedSet | 
| 162 |     { | 
| 163 |         std::vector<int> storage; | 
| 164 |  | 
| 165 |     public: | 
| 166 |         OrderedSet(){} | 
| 167 |         OrderedSet(std::initializer_list<int> l): storage(l) {} | 
| 168 |  | 
| 169 |         std::vector<int> takeList() const | 
| 170 |         { return std::move(storage); } | 
| 171 |  | 
| 172 |         const std::vector<int> &list() const | 
| 173 |         { return storage; } | 
| 174 |  | 
| 175 |         bool contains(int i) const | 
| 176 |         { | 
| 177 |             return std::find(first: storage.cbegin(), last: storage.cend(), val: i) != storage.cend(); | 
| 178 |         } | 
| 179 |  | 
| 180 |         bool remove(int i) | 
| 181 |         { | 
| 182 |             std::vector<int>::iterator it = std::find(first: storage.begin(), last: storage.end(), val: i); | 
| 183 |             if (it == storage.end()) { | 
| 184 |                 return false; | 
| 185 |             } | 
| 186 |             storage.erase(position: it); | 
| 187 |             return true; | 
| 188 |         } | 
| 189 |  | 
| 190 |         void removeHead() | 
| 191 |         { if (!isEmpty()) storage.erase(position: storage.begin()); } | 
| 192 |  | 
| 193 |         bool isEmpty() const | 
| 194 |         { return storage.empty(); } | 
| 195 |  | 
| 196 |         void add(int i) | 
| 197 |         { if (!contains(i)) storage.push_back(x: i); } | 
| 198 |  | 
| 199 |         bool intersectsWith(const OrderedSet &other) const | 
| 200 |         { | 
| 201 |             for (auto i : storage) { | 
| 202 |                 if (other.contains(i)) { | 
| 203 |                     return true; | 
| 204 |                 } | 
| 205 |             } | 
| 206 |             return false; | 
| 207 |         } | 
| 208 |  | 
| 209 |         void clear() | 
| 210 |         { storage.clear(); } | 
| 211 |  | 
| 212 |         typedef std::vector<int>::const_iterator const_iterator; | 
| 213 |         const_iterator begin() const { return storage.cbegin(); } | 
| 214 |         const_iterator end() const { return storage.cend(); } | 
| 215 |     }; | 
| 216 |  | 
| 217 |     class Queue | 
| 218 |     { | 
| 219 |         QVector<QScxmlEvent *> storage; | 
| 220 |  | 
| 221 |     public: | 
| 222 |         Queue() | 
| 223 |         { storage.reserve(asize: 4); } | 
| 224 |  | 
| 225 |         ~Queue() | 
| 226 |         {  qDeleteAll(c: storage); } | 
| 227 |  | 
| 228 |         void enqueue(QScxmlEvent *e) | 
| 229 |         { storage.append(t: e); } | 
| 230 |  | 
| 231 |         bool isEmpty() const | 
| 232 |         { return storage.empty(); } | 
| 233 |  | 
| 234 |         QScxmlEvent *dequeue() | 
| 235 |         { | 
| 236 |             Q_ASSERT(!isEmpty()); | 
| 237 |             QScxmlEvent *e = storage.first(); | 
| 238 |             storage.pop_front(); | 
| 239 |             int sz = storage.size(); | 
| 240 |             if (Q_UNLIKELY(sz > 4 && sz * 8 < storage.capacity())) { | 
| 241 |                 storage.squeeze(); | 
| 242 |             } | 
| 243 |             return e; | 
| 244 |         } | 
| 245 |     }; | 
| 246 |  | 
| 247 | public: | 
| 248 |     QScxmlStateMachinePrivate(const QMetaObject *qMetaObject); | 
| 249 |     ~QScxmlStateMachinePrivate(); | 
| 250 |  | 
| 251 |     static QScxmlStateMachinePrivate *get(QScxmlStateMachine *t) | 
| 252 |     { return t->d_func(); } | 
| 253 |  | 
| 254 |     static QString generateSessionId(const QString &prefix); | 
| 255 |  | 
| 256 |     ParserData *parserData(); | 
| 257 |  | 
| 258 |     void setIsInvoked(bool invoked) | 
| 259 |     { m_isInvoked = invoked; } | 
| 260 |  | 
| 261 |     void addService(int invokingState); | 
| 262 |     void removeService(int invokingState); | 
| 263 |     QScxmlInvokableServiceFactory *serviceFactory(int id); | 
| 264 |  | 
| 265 |     bool executeInitialSetup(); | 
| 266 |  | 
| 267 |     void routeEvent(QScxmlEvent *event); | 
| 268 |     void postEvent(QScxmlEvent *event); | 
| 269 |     void submitDelayedEvent(QScxmlEvent *event); | 
| 270 |     void submitError(const QString &type, const QString &msg, const QString &sendid = QString()); | 
| 271 |  | 
| 272 |     void start(); | 
| 273 |     void pause(); | 
| 274 |     void processEvents(); | 
| 275 |  | 
| 276 |     void setEvent(QScxmlEvent *event); | 
| 277 |     void resetEvent(); | 
| 278 |  | 
| 279 |     void emitStateActive(int stateIndex, bool active); | 
| 280 |     void emitInvokedServicesChanged(); | 
| 281 |  | 
| 282 |     void attach(QScxmlStateMachineInfo *info); | 
| 283 |     const OrderedSet &configuration() const { return m_configuration; } | 
| 284 |  | 
| 285 |     void updateMetaCache(); | 
| 286 |  | 
| 287 | private: | 
| 288 |     QStringList stateNames(const std::vector<int> &stateIndexes) const; | 
| 289 |     std::vector<int> historyStates(int stateIdx) const; | 
| 290 |  | 
| 291 |     void exitInterpreter(); | 
| 292 |     void returnDoneEvent(QScxmlExecutableContent::ContainerId doneData); | 
| 293 |     bool nameMatch(const StateTable::Array &patterns, QScxmlEvent *event) const; | 
| 294 |     void selectTransitions(OrderedSet &enabledTransitions, | 
| 295 |                            const std::vector<int> &configInDocumentOrder, | 
| 296 |                            QScxmlEvent *event) const; | 
| 297 |     void removeConflictingTransitions(OrderedSet *enabledTransitions) const; | 
| 298 |     void getProperAncestors(std::vector<int> *ancestors, int state1, int state2) const; | 
| 299 |     void microstep(const OrderedSet &enabledTransitions); | 
| 300 |     void exitStates(const OrderedSet &enabledTransitions); | 
| 301 |     void computeExitSet(const OrderedSet &enabledTransitions, OrderedSet &statesToExit) const; | 
| 302 |     void executeTransitionContent(const OrderedSet &enabledTransitions); | 
| 303 |     void enterStates(const OrderedSet &enabledTransitions); | 
| 304 |     void computeEntrySet(const OrderedSet &enabledTransitions, | 
| 305 |                          OrderedSet *statesToEnter, | 
| 306 |                          OrderedSet *statesForDefaultEntry, | 
| 307 |                          HistoryContent *defaultHistoryContent) const; | 
| 308 |     void addDescendantStatesToEnter(int stateIndex, | 
| 309 |                                     OrderedSet *statesToEnter, | 
| 310 |                                     OrderedSet *statesForDefaultEntry, | 
| 311 |                                     HistoryContent *defaultHistoryContent) const; | 
| 312 |     void addAncestorStatesToEnter(int stateIndex, | 
| 313 |                                   int ancestorIndex, | 
| 314 |                                   OrderedSet *statesToEnter, | 
| 315 |                                   OrderedSet *statesForDefaultEntry, | 
| 316 |                                   HistoryContent *defaultHistoryContent) const; | 
| 317 |     std::vector<int> getChildStates(const StateTable::State &state) const; | 
| 318 |     bool hasDescendant(const OrderedSet &statesToEnter, int childIdx) const; | 
| 319 |     bool allDescendants(const OrderedSet &statesToEnter, int childdx) const; | 
| 320 |     bool isDescendant(int state1, int state2) const; | 
| 321 |     bool allInFinalStates(const std::vector<int> &states) const; | 
| 322 |     bool someInFinalStates(const std::vector<int> &states) const; | 
| 323 |     bool isInFinalState(int stateIndex) const; | 
| 324 |     int getTransitionDomain(int transitionIndex) const; | 
| 325 |     int findLCCA(OrderedSet &&states) const; | 
| 326 |     void getEffectiveTargetStates(OrderedSet *targets, int transitionIndex) const; | 
| 327 |  | 
| 328 | public: // types & data fields: | 
| 329 |     QString m_sessionId; | 
| 330 |     bool m_isInvoked; | 
| 331 |     bool m_isInitialized; | 
| 332 |     bool m_isProcessingEvents; | 
| 333 |     QVariantMap m_initialValues; | 
| 334 |     QScxmlDataModel *m_dataModel; | 
| 335 |     QScxmlCompilerPrivate::DefaultLoader m_defaultLoader; | 
| 336 |     QScxmlCompiler::Loader *m_loader; | 
| 337 |     QScxmlExecutionEngine *m_executionEngine; | 
| 338 |     QScxmlTableData *m_tableData; | 
| 339 |     const StateTable *m_stateTable; | 
| 340 |     QScxmlStateMachine *m_parentStateMachine; | 
| 341 |     QScxmlInternal::EventLoopHook m_eventLoopHook; | 
| 342 |     typedef std::vector<std::pair<int, QScxmlEvent *>> DelayedQueue; | 
| 343 |     DelayedQueue m_delayedEvents; | 
| 344 |     const QMetaObject *m_metaObject; | 
| 345 |     QScxmlInternal::ScxmlEventRouter m_router; | 
| 346 |  | 
| 347 | private: | 
| 348 |     QScopedPointer<ParserData> m_parserData; // used when created by StateMachine::fromFile. | 
| 349 |     typedef QHash<int, QVector<int>> HistoryValues; | 
| 350 |     struct InvokedService { | 
| 351 |         int invokingState; | 
| 352 |         QScxmlInvokableService *service; | 
| 353 |         QString serviceName; | 
| 354 |     }; | 
| 355 |  | 
| 356 |     // TODO: move the stuff below to a struct that can be reset | 
| 357 |     HistoryValues m_historyValue; | 
| 358 |     OrderedSet m_configuration; | 
| 359 |     Queue m_internalQueue; | 
| 360 |     Queue m_externalQueue; | 
| 361 |     QSet<int> m_statesToInvoke; | 
| 362 |     std::vector<InvokedService> m_invokedServices; | 
| 363 |     std::vector<bool> m_isFirstStateEntry; | 
| 364 |     std::vector<QScxmlInvokableServiceFactory *> m_cachedFactories; | 
| 365 |     enum { Invalid = 0, Starting, Running, Paused, Finished } m_runningState = Invalid; | 
| 366 |     bool isRunnable() const { | 
| 367 |         switch (m_runningState) { | 
| 368 |         case Starting: | 
| 369 |         case Running: | 
| 370 |         case Paused: | 
| 371 |             return true; | 
| 372 |         case Invalid: | 
| 373 |         case Finished: | 
| 374 |             return false; | 
| 375 |         } | 
| 376 |  | 
| 377 |         return false; // Dead code, but many dumb compilers cannot (or are unwilling to) detect that. | 
| 378 |     } | 
| 379 |  | 
| 380 |     bool isPaused() const { return m_runningState == Paused; } | 
| 381 |  | 
| 382 |     QScxmlInternal::StateMachineInfoProxy *m_infoSignalProxy; | 
| 383 |  | 
| 384 |     QHash<int, int> m_stateIndexToSignalIndex; | 
| 385 |     QHash<QString, int> m_stateNameToSignalIndex; | 
| 386 | }; | 
| 387 |  | 
| 388 | QT_END_NAMESPACE | 
| 389 |  | 
| 390 | #endif // QSCXMLSTATEMACHINE_P_H | 
| 391 |  | 
| 392 |  |