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
30QT_BEGIN_NAMESPACE
31
32namespace QScxmlInternal {
33class EventLoopHook: public QObject
34{
35 Q_OBJECT
36
37 QScxmlStateMachinePrivate *smp;
38
39public:
40 EventLoopHook(QScxmlStateMachinePrivate *smp)
41 : smp(smp)
42 {}
43
44 void queueProcessEvents();
45
46 Q_INVOKABLE void doProcessEvents();
47
48protected:
49 void timerEvent(QTimerEvent *timerEvent) override;
50};
51
52class ScxmlEventRouter : public QObject
53{
54 Q_OBJECT
55public:
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
65signals:
66 void eventOccurred(const QScxmlEvent &event);
67
68private:
69 QHash<QString, ScxmlEventRouter *> children;
70 ScxmlEventRouter *child(const QString &segment);
71
72 void disconnectNotify(const QMetaMethod &signal) override;
73};
74
75class StateMachineInfoProxy: public QObject
76{
77 Q_OBJECT
78
79public:
80 StateMachineInfoProxy(QObject *parent)
81 : QObject(parent)
82 {}
83
84Q_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
91class QScxmlInvokableService;
92class Q_SCXML_PRIVATE_EXPORT QScxmlStateMachinePrivate: public QObjectPrivate
93{
94 Q_DECLARE_PUBLIC(QScxmlStateMachine)
95
96 static QAtomicInt m_sessionIdCounter;
97
98public: // 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
216public:
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
256private:
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
297public: // 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
348private:
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
401QT_END_NAMESPACE
402
403#endif // QSCXMLSTATEMACHINE_P_H
404
405

source code of qtscxml/src/scxml/qscxmlstatemachine_p.h