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