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 | QList<NodeTreeChange> treeChanges; |
277 | treeChanges.reserve(asize: nodes.size()); |
278 | |
279 | for (QNode *node : nodes) { |
280 | treeChanges.push_back(t: { .id: node->id(), |
281 | .metaObj: QNodePrivate::get(q: node)->m_typeInfo, |
282 | .type: NodeTreeChange::Added, |
283 | .node: node }); |
284 | } |
285 | |
286 | m_nodeTreeChanges += treeChanges; |
287 | } |
288 | |
289 | // Main Thread -> immediately following node destruction (call from QNode dtor) |
290 | void QAspectManager::removeNodes(const QList<QNode *> &nodes) |
291 | { |
292 | // We record the nodes removed information, which we will actually use when |
293 | // processFrame is called (later but within the same loop of the even loop |
294 | // as this call) The idea is we want to avoid modifying the backend tree if |
295 | // the Renderer hasn't allowed processFrame to continue yet The drawback is |
296 | // that when processFrame is processed, the QNode* pointer might be invalid by |
297 | // that point. Therefore we record all we need to remove the object. |
298 | |
299 | for (QNode *node : nodes) { |
300 | // In addition, we check if we contain an Added change for a given node |
301 | // that is now about to be destroyed. If so we remove the Added change |
302 | // entirely |
303 | |
304 | m_nodeTreeChanges.erase(abegin: std::remove_if(first: m_nodeTreeChanges.begin(), |
305 | last: m_nodeTreeChanges.end(), |
306 | pred: [&node] (const NodeTreeChange &change) { return change.id == node->id(); }), |
307 | aend: m_nodeTreeChanges.end()); |
308 | |
309 | m_nodeTreeChanges.push_back(t: { .id: node->id(), |
310 | .metaObj: QNodePrivate::get(q: node)->m_typeInfo, |
311 | .type: NodeTreeChange::Removed, |
312 | .node: nullptr }); |
313 | } |
314 | } |
315 | |
316 | /*! |
317 | * \internal |
318 | * |
319 | * Registers a new \a aspect. |
320 | */ |
321 | void QAspectManager::registerAspect(QAbstractAspect *aspect) |
322 | { |
323 | qCDebug(Aspects) << "Registering aspect" ; |
324 | |
325 | if (aspect != nullptr) { |
326 | m_aspects.append(t: aspect); |
327 | QAbstractAspectPrivate::get(aspect)->m_aspectManager = this; |
328 | QAbstractAspectPrivate::get(aspect)->m_jobManager = m_jobManager; |
329 | QAbstractAspectPrivate::get(aspect)->m_arbiter = m_changeArbiter; |
330 | |
331 | // Allow the aspect to do some work now that it is registered |
332 | aspect->onRegistered(); |
333 | } |
334 | else { |
335 | qCWarning(Aspects) << "Failed to register aspect" ; |
336 | } |
337 | qCDebug(Aspects) << "Completed registering aspect" ; |
338 | } |
339 | |
340 | /*! |
341 | * \internal |
342 | * |
343 | * Calls QAbstractAspect::onUnregistered(), unregisters the aspect from the |
344 | * change arbiter and unsets the arbiter, job manager and aspect manager. |
345 | * Operations are performed in the reverse order to registerAspect. |
346 | */ |
347 | void QAspectManager::unregisterAspect(Qt3DCore::QAbstractAspect *aspect) |
348 | { |
349 | qCDebug(Aspects) << "Unregistering aspect" ; |
350 | Q_ASSERT(aspect); |
351 | aspect->onUnregistered(); |
352 | QAbstractAspectPrivate::get(aspect)->m_arbiter = nullptr; |
353 | QAbstractAspectPrivate::get(aspect)->m_jobManager = nullptr; |
354 | QAbstractAspectPrivate::get(aspect)->m_aspectManager = nullptr; |
355 | m_aspects.removeOne(t: aspect); |
356 | qCDebug(Aspects) << "Completed unregistering aspect" ; |
357 | } |
358 | |
359 | const QList<QAbstractAspect *> &QAspectManager::aspects() const |
360 | { |
361 | return m_aspects; |
362 | } |
363 | |
364 | QAbstractAspect *QAspectManager::aspect(const QString &name) const |
365 | { |
366 | auto dengine = QAspectEnginePrivate::get(engine: m_engine); |
367 | return dengine->m_namedAspects.value(key: name, defaultValue: nullptr); |
368 | } |
369 | |
370 | QAbstractAspect *QAspectManager::aspect(const QMetaObject *metaType) const |
371 | { |
372 | for (auto *a: m_aspects) { |
373 | if (a->metaObject() == metaType) |
374 | return a; |
375 | } |
376 | return nullptr; |
377 | } |
378 | |
379 | QAbstractAspectJobManager *QAspectManager::jobManager() const |
380 | { |
381 | return m_jobManager; |
382 | } |
383 | |
384 | QChangeArbiter *QAspectManager::changeArbiter() const |
385 | { |
386 | return m_changeArbiter; |
387 | } |
388 | |
389 | QServiceLocator *QAspectManager::serviceLocator() const |
390 | { |
391 | return m_serviceLocator.data(); |
392 | } |
393 | |
394 | void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit) |
395 | { |
396 | m_postConstructorInit = postConstructorInit; |
397 | } |
398 | |
399 | QNode *QAspectManager::lookupNode(QNodeId id) const |
400 | { |
401 | if (!m_root) |
402 | return nullptr; |
403 | |
404 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
405 | return d->m_scene ? d->m_scene->lookupNode(id) : nullptr; |
406 | } |
407 | |
408 | QList<QNode *> QAspectManager::lookupNodes(const QList<QNodeId> &ids) const |
409 | { |
410 | if (!m_root) |
411 | return {}; |
412 | |
413 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
414 | return d->m_scene ? d->m_scene->lookupNodes(ids) : QList<QNode *>{}; |
415 | } |
416 | |
417 | QScene *QAspectManager::scene() const |
418 | { |
419 | if (!m_root) |
420 | return nullptr; |
421 | |
422 | QNodePrivate *d = QNodePrivate::get(q: m_root); |
423 | return d->m_scene; |
424 | } |
425 | |
426 | void QAspectManager::dumpJobsOnNextFrame() |
427 | { |
428 | m_dumpJobs = true; |
429 | } |
430 | |
431 | #if !QT_CONFIG(animation) |
432 | /*! |
433 | \internal |
434 | \brief Drives the Qt3D simulation loop in the main thread |
435 | */ |
436 | bool QAspectManager::event(QEvent *e) |
437 | { |
438 | if (e->type() == RequestFrameEvent::eventType()) { |
439 | |
440 | // Process current frame |
441 | processFrame(); |
442 | |
443 | // Request next frame if we are still running and if Qt3D is driving |
444 | // the loop |
445 | if (m_simulationLoopRunning && m_driveMode == QAspectEngine::Automatic) |
446 | requestNextFrame(); |
447 | |
448 | return true; |
449 | } |
450 | |
451 | return QObject::event(e); |
452 | } |
453 | #endif |
454 | |
455 | void QAspectManager::requestNextFrame() |
456 | { |
457 | qCDebug(Aspects) << "Requesting new Frame" ; |
458 | // Post event in the event loop to force |
459 | // next frame to be processed |
460 | #if QT_CONFIG(animation) |
461 | m_simulationAnimation->start(); |
462 | #else |
463 | QCoreApplication::postEvent(this, new RequestFrameEvent()); |
464 | #endif |
465 | } |
466 | |
467 | void QAspectManager::processFrame() |
468 | { |
469 | qCDebug(Aspects) << "Processing Frame" ; |
470 | |
471 | // Retrieve the frame advance service. Defaults to timer based if there is no renderer. |
472 | QAbstractFrameAdvanceService *frameAdvanceService = |
473 | m_serviceLocator->service<QAbstractFrameAdvanceService>(serviceType: QServiceLocator::FrameAdvanceService); |
474 | |
475 | const qint64 t = frameAdvanceService->waitForNextFrame(); |
476 | if (t < 0) |
477 | return; |
478 | |
479 | // Distribute accumulated changes. This includes changes sent from the frontend |
480 | // to the backend nodes. We call this before the call to m_scheduler->update() to ensure |
481 | // that any property changes do not set dirty flags in a data race with the renderer's |
482 | // submission thread which may be looking for dirty flags, acting upon them and then |
483 | // clearing the dirty flags. |
484 | // |
485 | // Doing this as the first call in the new frame ensures the lock free approach works |
486 | // without any such data race. |
487 | { |
488 | // scope for QTaskLogger |
489 | QTaskLogger logger(m_serviceLocator->systemInformation(), 4096, 0, QTaskLogger::AspectJob); |
490 | |
491 | // Tell the NodePostConstructorInit to process any pending nodes which will add them to our list of |
492 | // tree changes |
493 | m_postConstructorInit->processNodes(); |
494 | |
495 | // Add and Remove Nodes |
496 | const QList<NodeTreeChange> nodeTreeChanges = Qt3DCore::moveAndClear(data&: m_nodeTreeChanges); |
497 | for (const NodeTreeChange &change : nodeTreeChanges) { |
498 | // Buckets ensure that even if we have intermingled node added / removed |
499 | // buckets, we preserve the order of the sequences |
500 | |
501 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) { |
502 | switch (change.type) { |
503 | case NodeTreeChange::Added: |
504 | aspect->d_func()->createBackendNode(change); |
505 | break; |
506 | case NodeTreeChange::Removed: |
507 | aspect->d_func()->clearBackendNode(change); |
508 | break; |
509 | } |
510 | } |
511 | } |
512 | |
513 | // Sync node / subnode relationship changes |
514 | const auto dirtySubNodes = m_changeArbiter->takeDirtyEntityComponentNodes(); |
515 | if (dirtySubNodes.size()) |
516 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
517 | QAbstractAspectPrivate::get(aspect)->syncDirtyEntityComponentNodes(nodes: dirtySubNodes); |
518 | |
519 | // Sync property updates |
520 | const auto dirtyFrontEndNodes = m_changeArbiter->takeDirtyFrontEndNodes(); |
521 | if (dirtyFrontEndNodes.size()) |
522 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
523 | QAbstractAspectPrivate::get(aspect)->syncDirtyFrontEndNodes(nodes: dirtyFrontEndNodes); |
524 | } |
525 | |
526 | // For each Aspect |
527 | // Ask them to launch set of jobs for the current frame |
528 | // Updates matrices, bounding volumes, render bins ... |
529 | m_jobsInLastFrame = m_scheduler->scheduleAndWaitForFrameAspectJobs(time: t, dumpJobs: m_dumpJobs); |
530 | m_dumpJobs = false; |
531 | |
532 | // Tell the aspect the frame is complete (except rendering) |
533 | for (QAbstractAspect *aspect : std::as_const(t&: m_aspects)) |
534 | aspect->frameDone(); |
535 | } |
536 | |
537 | } // namespace Qt3DCore |
538 | |
539 | QT_END_NAMESPACE |
540 | |
541 | #include "moc_qaspectmanager_p.cpp" |
542 | |