1 | // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qaspectmanager_p.h" |
5 | |
6 | #include <Qt3DCore/qabstractaspect.h> |
7 | #include <Qt3DCore/qentity.h> |
8 | #include <QtCore/QAbstractEventDispatcher> |
9 | #include <QtCore/QEventLoop> |
10 | #include <QtCore/QThread> |
11 | #include <QtCore/QWaitCondition> |
12 | #include <QtGui/QSurface> |
13 | |
14 | #include <Qt3DCore/private/corelogging_p.h> |
15 | #include <Qt3DCore/private/qabstractaspect_p.h> |
16 | #include <Qt3DCore/private/qabstractaspectjobmanager_p.h> |
17 | #include <Qt3DCore/private/qabstractframeadvanceservice_p.h> |
18 | #include <Qt3DCore/private/qaspectengine_p.h> |
19 | // TODO Make the kind of job manager configurable (e.g. ThreadWeaver vs Intel TBB) |
20 | #include <Qt3DCore/private/qaspectjobmanager_p.h> |
21 | #include <Qt3DCore/private/qaspectjob_p.h> |
22 | #include <Qt3DCore/private/qchangearbiter_p.h> |
23 | #include <Qt3DCore/private/qscheduler_p.h> |
24 | #include <Qt3DCore/private/qservicelocator_p.h> |
25 | #include <Qt3DCore/private/qsysteminformationservice_p_p.h> |
26 | #include <Qt3DCore/private/qthreadpooler_p.h> |
27 | #include <Qt3DCore/private/qtickclock_p.h> |
28 | #include <Qt3DCore/private/qtickclockservice_p.h> |
29 | #include <Qt3DCore/private/qnodevisitor_p.h> |
30 | #include <Qt3DCore/private/qnode_p.h> |
31 | #include <Qt3DCore/private/qscene_p.h> |
32 | #include <Qt3DCore/private/vector_helper_p.h> |
33 | |
34 | #include <QtCore/QCoreApplication> |
35 | #if QT_CONFIG(animation) |
36 | #include <QtCore/QAbstractAnimation> |
37 | #endif |
38 | |
39 | QT_BEGIN_NAMESPACE |
40 | |
41 | namespace Qt3DCore { |
42 | |
43 | #if QT_CONFIG(animation) |
44 | class RequestFrameAnimation final : public QAbstractAnimation |
45 | { |
46 | public: |
47 | RequestFrameAnimation(QObject *parent) |
48 | : QAbstractAnimation(parent) |
49 | { |
50 | } |
51 | |
52 | ~RequestFrameAnimation() override; |
53 | |
54 | int duration() const override { return 1; } |
55 | void updateCurrentTime(int currentTime) override { Q_UNUSED(currentTime); } |
56 | }; |
57 | |
58 | RequestFrameAnimation::~RequestFrameAnimation() = default; |
59 | #else |
60 | namespace { |
61 | |
62 | class RequestFrameEvent : public QEvent |
63 | { |
64 | public: |
65 | RequestFrameEvent() |
66 | : QEvent(static_cast<QEvent::Type>(RequestFrameEvent::requestEventType)) |
67 | {} |
68 | |
69 | static int eventType() { return RequestFrameEvent::requestEventType; } |
70 | |
71 | private: |
72 | static int requestEventType; |
73 | }; |
74 | |
75 | int RequestFrameEvent::requestEventType = QEvent::registerEventType(); |
76 | |
77 | } // anonymous |
78 | #endif |
79 | |
80 | /*! |
81 | \class Qt3DCore::QAspectManager |
82 | \internal |
83 | */ |
84 | QAspectManager::QAspectManager(QAspectEngine *parent) |
85 | : QObject(parent) |
86 | , m_engine(parent) |
87 | , m_root(nullptr) |
88 | , m_scheduler(new QScheduler(this)) |
89 | , m_jobManager(new QAspectJobManager(this)) |
90 | , m_changeArbiter(new QChangeArbiter(this)) |
91 | , m_serviceLocator(new QServiceLocator(parent)) |
92 | , m_simulationLoopRunning(false) |
93 | , m_driveMode(QAspectEngine::Automatic) |
94 | , m_postConstructorInit(nullptr) |
95 | #if QT_CONFIG(animation) |
96 | , m_simulationAnimation(nullptr) |
97 | #endif |
98 | , m_jobsInLastFrame(0) |
99 | , m_dumpJobs(false) |
100 | { |
101 | qRegisterMetaType<QSurface *>(typeName: "QSurface*"); |
102 | qCDebug(Aspects) << Q_FUNC_INFO; |
103 | } |
104 | |
105 | QAspectManager::~QAspectManager() |
106 | { |
107 | delete m_changeArbiter; |
108 | delete m_jobManager; |
109 | delete m_scheduler; |
110 | } |
111 | |
112 | void QAspectManager::setRunMode(QAspectEngine::RunMode mode) |
113 | { |
114 | qCDebug(Aspects) << Q_FUNC_INFO << "Running Loop Drive Mode set to"<< mode; |
115 | m_driveMode = mode; |
116 | } |
117 | |
118 | // Main thread (called by QAspectEngine) |
119 | void QAspectManager::enterSimulationLoop() |
120 | { |
121 | qCDebug(Aspects) << Q_FUNC_INFO; |
122 | m_simulationLoopRunning = true; |
123 | |
124 | // Retrieve the frame advance service. Defaults to timer based if there is no renderer. |
125 | QAbstractFrameAdvanceService *frameAdvanceService = |
126 | m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService); |
127 | |
128 | // Start the frameAdvanceService |
129 | frameAdvanceService->start(); |
130 | |
131 | // We are about to enter the simulation loop. Give aspects a chance to do any last |
132 | // pieces of initialization |
133 | qCDebug(Aspects) << "Calling onEngineStartup() for each aspect"; |
134 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) { |
135 | qCDebug(Aspects) << "\t"<< aspect->objectName(); |
136 | aspect->onEngineStartup(); |
137 | } |
138 | qCDebug(Aspects) << "Done calling onEngineStartup() for each aspect"; |
139 | |
140 | // Start running loop if Qt3D is in charge of driving it |
141 | if (m_driveMode == QAspectEngine::Automatic) { |
142 | #if QT_CONFIG(animation) |
143 | if (!m_simulationAnimation) { |
144 | m_simulationAnimation = new RequestFrameAnimation(this); |
145 | connect(sender: m_simulationAnimation, signal: &QAbstractAnimation::finished, context: this, slot: [this]() { |
146 | processFrame(); |
147 | if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic) |
148 | requestNextFrame(); |
149 | }); |
150 | } |
151 | #endif |
152 | requestNextFrame(); |
153 | } |
154 | } |
155 | |
156 | // Main thread (called by QAspectEngine) |
157 | void QAspectManager::exitSimulationLoop() |
158 | { |
159 | qCDebug(Aspects) << Q_FUNC_INFO; |
160 | |
161 | // If this fails, simulation loop is already exited so nothing to do |
162 | if (!m_simulationLoopRunning) { |
163 | qCDebug(Aspects) << "Simulation loop was not running. Nothing to do"; |
164 | return; |
165 | } |
166 | |
167 | #if QT_CONFIG(animation) |
168 | if (m_simulationAnimation) |
169 | m_simulationAnimation->stop(); |
170 | #endif |
171 | |
172 | QAbstractFrameAdvanceService *frameAdvanceService = |
173 | m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService); |
174 | if (frameAdvanceService) |
175 | frameAdvanceService->stop(); |
176 | |
177 | // Give any aspects a chance to unqueue any asynchronous work they |
178 | // may have scheduled that would otherwise potentially deadlock or |
179 | // cause races. For example, the QLogicAspect queues up a vector of |
180 | // QNodeIds to be processed by a callback on the main thread. However, |
181 | // if we don't unqueue this work and release its semaphore, the logic |
182 | // aspect would cause a deadlock when trying to exit the inner loop. |
183 | // This is because we call this function from the main thread and the |
184 | // logic aspect is waiting for the main thread to execute the |
185 | // QLogicComponent::onFrameUpdate() callback. |
186 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
187 | aspect->d_func()->onEngineAboutToShutdown(); |
188 | |
189 | // Give aspects a chance to perform any shutdown actions. This may include unqueuing |
190 | // any blocking work on the main thread that could potentially deadlock during shutdown. |
191 | qCDebug(Aspects) << "Calling onEngineShutdown() for each aspect"; |
192 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) { |
193 | qCDebug(Aspects) << "\t"<< aspect->objectName(); |
194 | aspect->onEngineShutdown(); |
195 | } |
196 | qCDebug(Aspects) << "Done calling onEngineShutdown() for each aspect"; |
197 | |
198 | m_simulationLoopRunning = false; |
199 | qCDebug(Aspects) << "exitSimulationLoop completed"; |
200 | } |
201 | |
202 | bool QAspectManager::isShuttingDown() const |
203 | { |
204 | return !m_simulationLoopRunning; |
205 | } |
206 | |
207 | /*! |
208 | \internal |
209 | |
210 | Called by the QAspectThread's run() method immediately after the manager |
211 | has been created |
212 | */ |
213 | void QAspectManager::initialize() |
214 | { |
215 | qCDebug(Aspects) << Q_FUNC_INFO; |
216 | m_jobManager->initialize(); |
217 | m_scheduler->setAspectManager(this); |
218 | } |
219 | |
220 | /*! |
221 | \internal |
222 | |
223 | Called by the QAspectThread's run() method immediately after the manager's |
224 | exec() function has returned. |
225 | */ |
226 | void QAspectManager::shutdown() |
227 | { |
228 | qCDebug(Aspects) << Q_FUNC_INFO; |
229 | |
230 | // Aspects must be deleted in the Thread they were created in |
231 | } |
232 | |
233 | // MainThread called by QAspectEngine::setRootEntity |
234 | void QAspectManager::setRootEntity(Qt3DCore::QEntity *root, const QList<QNode *> &nodes) |
235 | { |
236 | qCDebug(Aspects) << Q_FUNC_INFO; |
237 | |
238 | if (root == m_root) |
239 | return; |
240 | |
241 | if (m_root) { |
242 | // TODO: Delete all backend nodes. This is to be symmetric with how |
243 | // we create them below in the call to setRootAndCreateNodes |
244 | } |
245 | |
246 | m_root = root; |
247 | |
248 | if (m_root) { |
249 | |
250 | QList<NodeTreeChange> nodeTreeChanges; |
251 | nodeTreeChanges.reserve(asize: nodes.size()); |
252 | |
253 | for (QNode *n : nodes) { |
254 | nodeTreeChanges.push_back(t: { |
255 | .id: n->id(), |
256 | .metaObj: QNodePrivate::get(q: n)->m_typeInfo, |
257 | .type: NodeTreeChange::Added, |
258 | .node: n |
259 | }); |
260 | } |
261 | |
262 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
263 | aspect->d_func()->setRootAndCreateNodes(rootObject: m_root, nodesTreeChanges: nodeTreeChanges); |
264 | } |
265 | } |
266 | |
267 | |
268 | // Main Thread -> immediately following node insertion |
269 | void QAspectManager::addNodes(const QList<QNode *> &nodes) |
270 | { |
271 | // We record the nodes added information, which we will actually use when |
272 | // processFrame is called (later but within the same loop of the even loop |
273 | // as this call) The idea is we want to avoid modifying the backend tree if |
274 | // the Renderer hasn't allowed processFrame to continue yet |
275 | |
276 | m_nodeTreeChanges.reserve(asize: m_nodeTreeChanges.size() + nodes.size()); |
277 | |
278 | for (QNode *node : nodes) { |
279 | m_nodeTreeChanges.push_back(t: { .id: node->id(), |
280 | .metaObj: QNodePrivate::get(q: node)->m_typeInfo, |
281 | .type: NodeTreeChange::Added, |
282 | .node: node }); |
283 | } |
284 | } |
285 | |
286 | // Main Thread -> immediately following node destruction (call from QNode dtor) |
287 | void QAspectManager::removeNodes(const QList<QNode *> &nodes) |
288 | { |
289 | // We record the nodes removed information, which we will actually use when |
290 | // processFrame is called (later but within the same loop of the even loop |
291 | // as this call) The idea is we want to avoid modifying the backend tree if |
292 | // the Renderer hasn't allowed processFrame to continue yet The drawback is |
293 | // that when processFrame is processed, the QNode* pointer might be invalid by |
294 | // that point. Therefore we record all we need to remove the object. |
295 | |
296 | for (QNode *node : nodes) { |
297 | // In addition, we check if we contain an Added change for a given node |
298 | // that is now about to be destroyed. If so we remove the Added change |
299 | // entirely |
300 | |
301 | m_nodeTreeChanges.erase(abegin: std::remove_if(first: m_nodeTreeChanges.begin(), |
302 | last: m_nodeTreeChanges.end(), |
303 | pred: [&node] (const NodeTreeChange &change) { return change.id == node->id(); }), |
304 | aend: m_nodeTreeChanges.end()); |
305 | |
306 | m_nodeTreeChanges.push_back(t: { .id: node->id(), |
307 | .metaObj: QNodePrivate::get(q: node)->m_typeInfo, |
308 | .type: NodeTreeChange::Removed, |
309 | .node: nullptr }); |
310 | } |
311 | } |
312 | |
313 | /*! |
314 | * \internal |
315 | * |
316 | * Registers a new \a aspect. |
317 | */ |
318 | void QAspectManager::registerAspect(QAbstractAspect *aspect) |
319 | { |
320 | qCDebug(Aspects) << "Registering aspect"; |
321 | |
322 | if (aspect != nullptr) { |
323 | m_aspects.append(t: aspect); |
324 | QAbstractAspectPrivate::get(aspect)->m_aspectManager = this; |
325 | QAbstractAspectPrivate::get(aspect)->m_jobManager = m_jobManager; |
326 | QAbstractAspectPrivate::get(aspect)->m_arbiter = m_changeArbiter; |
327 | |
328 | // Allow the aspect to do some work now that it is registered |
329 | aspect->onRegistered(); |
330 | } |
331 | else { |
332 | qCWarning(Aspects) << "Failed to register aspect"; |
333 | } |
334 | qCDebug(Aspects) << "Completed registering aspect"; |
335 | } |
336 | |
337 | /*! |
338 | * \internal |
339 | * |
340 | * Calls QAbstractAspect::onUnregistered(), unregisters the aspect from the |
341 | * change arbiter and unsets the arbiter, job manager and aspect manager. |
342 | * Operations are performed in the reverse order to registerAspect. |
343 | */ |
344 | void QAspectManager::unregisterAspect(Qt3DCore::QAbstractAspect *aspect) |
345 | { |
346 | qCDebug(Aspects) << "Unregistering aspect"; |
347 | Q_ASSERT(aspect); |
348 | aspect->onUnregistered(); |
349 | QAbstractAspectPrivate::get(aspect)->m_arbiter = nullptr; |
350 | QAbstractAspectPrivate::get(aspect)->m_jobManager = nullptr; |
351 | QAbstractAspectPrivate::get(aspect)->m_aspectManager = nullptr; |
352 | m_aspects.removeOne(t: aspect); |
353 | qCDebug(Aspects) << "Completed unregistering aspect"; |
354 | } |
355 | |
356 | const QList<QAbstractAspect *> &QAspectManager::aspects() const |
357 | { |
358 | return m_aspects; |
359 | } |
360 | |
361 | QAbstractAspect *QAspectManager::aspect(const QString &name) const |
362 | { |
363 | auto dengine = QAspectEnginePrivate::get(engine: m_engine); |
364 | return dengine->m_namedAspects.value(key: name, defaultValue: nullptr); |
365 | } |
366 | |
367 | QAbstractAspect *QAspectManager::aspect(const QMetaObject *metaType) const |
368 | { |
369 | for (auto *a: m_aspects) { |
370 | if (a->metaObject() == metaType) |
371 | return a; |
372 | } |
373 | return nullptr; |
374 | } |
375 | |
376 | QAbstractAspectJobManager *QAspectManager::jobManager() const |
377 | { |
378 | return m_jobManager; |
379 | } |
380 | |
381 | QChangeArbiter *QAspectManager::changeArbiter() const |
382 | { |
383 | return m_changeArbiter; |
384 | } |
385 | |
386 | QServiceLocator *QAspectManager::serviceLocator() const |
387 | { |
388 | return m_serviceLocator.data(); |
389 | } |
390 | |
391 | void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit) |
392 | { |
393 | m_postConstructorInit = postConstructorInit; |
394 | } |
395 | |
396 | QNode *QAspectManager::lookupNode(QNodeId id) const |
397 | { |
398 | if (!m_root) |
399 | return nullptr; |
400 | |
401 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
402 | return d->m_scene ? d->m_scene->lookupNode(id) : nullptr; |
403 | } |
404 | |
405 | QList<QNode *> QAspectManager::lookupNodes(const QList<QNodeId> &ids) const |
406 | { |
407 | if (!m_root) |
408 | return {}; |
409 | |
410 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
411 | return d->m_scene ? d->m_scene->lookupNodes(ids) : QList<QNode *>{}; |
412 | } |
413 | |
414 | QScene *QAspectManager::scene() const |
415 | { |
416 | if (!m_root) |
417 | return nullptr; |
418 | |
419 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
420 | return d->m_scene; |
421 | } |
422 | |
423 | void QAspectManager::dumpJobsOnNextFrame() |
424 | { |
425 | m_dumpJobs = true; |
426 | } |
427 | |
428 | #if !QT_CONFIG(animation) |
429 | /*! |
430 | \internal |
431 | \brief Drives the Qt3D simulation loop in the main thread |
432 | */ |
433 | bool QAspectManager::event(QEvent *e) |
434 | { |
435 | if (e->type() == RequestFrameEvent::eventType()) { |
436 | |
437 | // Process current frame |
438 | processFrame(); |
439 | |
440 | // Request next frame if we are still running and if Qt3D is driving |
441 | // the loop |
442 | if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic) |
443 | requestNextFrame(); |
444 | |
445 | return true; |
446 | } |
447 | |
448 | return QObject::event(e); |
449 | } |
450 | #endif |
451 | |
452 | void QAspectManager::requestNextFrame() |
453 | { |
454 | qCDebug(Aspects) << "Requesting new Frame"; |
455 | // Post event in the event loop to force |
456 | // next frame to be processed |
457 | #if QT_CONFIG(animation) |
458 | m_simulationAnimation->start(); |
459 | #else |
460 | QCoreApplication::postEvent(this, new RequestFrameEvent()); |
461 | #endif |
462 | } |
463 | |
464 | void QAspectManager::processFrame() |
465 | { |
466 | qCDebug(Aspects) << "Processing Frame"; |
467 | |
468 | // Retrieve the frame advance service. Defaults to timer based if there is no renderer. |
469 | QAbstractFrameAdvanceService *frameAdvanceService = |
470 | m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService); |
471 | |
472 | const qint64 t = frameAdvanceService->waitForNextFrame(); |
473 | if (t < 0) |
474 | return; |
475 | |
476 | // Distribute accumulated changes. This includes changes sent from the frontend |
477 | // to the backend nodes. We call this before the call to m_scheduler->update() to ensure |
478 | // that any property changes do not set dirty flags in a data race with the renderer's |
479 | // submission thread which may be looking for dirty flags, acting upon them and then |
480 | // clearing the dirty flags. |
481 | // |
482 | // Doing this as the first call in the new frame ensures the lock free approach works |
483 | // without any such data race. |
484 | { |
485 | // scope for QTaskLogger |
486 | QTaskLogger logger(m_serviceLocator->systemInformation(), 4096, 0, QTaskLogger::AspectJob); |
487 | |
488 | // Tell the NodePostConstructorInit to process any pending nodes which will add them to our list of |
489 | // tree changes |
490 | m_postConstructorInit->processNodes(); |
491 | |
492 | // Add and Remove Nodes |
493 | const QList<NodeTreeChange> nodeTreeChanges = Qt3DCore::moveAndClear(data&: m_nodeTreeChanges); |
494 | for (const NodeTreeChange &change : nodeTreeChanges) { |
495 | // Buckets ensure that even if we have intermingled node added / removed |
496 | // buckets, we preserve the order of the sequences |
497 | |
498 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) { |
499 | switch (change.type) { |
500 | case NodeTreeChange::Added: |
501 | aspect->d_func()->createBackendNode(change); |
502 | break; |
503 | case NodeTreeChange::Removed: |
504 | aspect->d_func()->clearBackendNode(change); |
505 | break; |
506 | } |
507 | } |
508 | } |
509 | |
510 | // Sync node / subnode relationship changes |
511 | const auto dirtySubNodes = m_changeArbiter->takeDirtyEntityComponentNodes(); |
512 | if (!dirtySubNodes.empty()) |
513 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
514 | QAbstractAspectPrivate::get(aspect)->syncDirtyEntityComponentNodes(nodes: dirtySubNodes); |
515 | |
516 | // Sync property updates |
517 | const auto dirtyFrontEndNodes = m_changeArbiter->takeDirtyFrontEndNodes(); |
518 | if (!dirtyFrontEndNodes.empty()) |
519 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
520 | QAbstractAspectPrivate::get(aspect)->syncDirtyFrontEndNodes(nodes: dirtyFrontEndNodes); |
521 | } |
522 | |
523 | // For each Aspect |
524 | // Ask them to launch set of jobs for the current frame |
525 | // Updates matrices, bounding volumes, render bins ... |
526 | m_jobsInLastFrame = m_scheduler->scheduleAndWaitForFrameAspectJobs(time: t, dumpJobs: m_dumpJobs); |
527 | m_dumpJobs = false; |
528 | |
529 | // Tell the aspect the frame is complete (except rendering) |
530 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
531 | aspect->frameDone(); |
532 | } |
533 | |
534 | } // namespace Qt3DCore |
535 | |
536 | QT_END_NAMESPACE |
537 | |
538 | #include "moc_qaspectmanager_p.cpp" |
539 |
Definitions
- RequestFrameAnimation
- RequestFrameAnimation
- duration
- updateCurrentTime
- ~RequestFrameAnimation
- QAspectManager
- ~QAspectManager
- setRunMode
- enterSimulationLoop
- exitSimulationLoop
- isShuttingDown
- initialize
- shutdown
- setRootEntity
- addNodes
- removeNodes
- registerAspect
- unregisterAspect
- aspects
- aspect
- aspect
- jobManager
- changeArbiter
- serviceLocator
- setPostConstructorInit
- lookupNode
- lookupNodes
- scene
- dumpJobsOnNextFrame
- requestNextFrame
Start learning QML with our Intro Training
Find out more