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
39QT_BEGIN_NAMESPACE
40
41namespace Qt3DCore {
42
43#if QT_CONFIG(animation)
44class RequestFrameAnimation final : public QAbstractAnimation
45{
46public:
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
58RequestFrameAnimation::~RequestFrameAnimation() = default;
59#else
60namespace {
61
62class RequestFrameEvent : public QEvent
63{
64public:
65 RequestFrameEvent()
66 : QEvent(static_cast<QEvent::Type>(RequestFrameEvent::requestEventType))
67 {}
68
69 static int eventType() { return RequestFrameEvent::requestEventType; }
70
71private:
72 static int requestEventType;
73};
74
75int RequestFrameEvent::requestEventType = QEvent::registerEventType();
76
77} // anonymous
78#endif
79
80/*!
81 \class Qt3DCore::QAspectManager
82 \internal
83*/
84QAspectManager::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
105QAspectManager::~QAspectManager()
106{
107 delete m_changeArbiter;
108 delete m_jobManager;
109 delete m_scheduler;
110}
111
112void 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)
119void 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)
157void 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
202bool 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*/
213void 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*/
226void 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
234void 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
269void 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)
287void 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 */
318void 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 */
344void 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
356const QList<QAbstractAspect *> &QAspectManager::aspects() const
357{
358 return m_aspects;
359}
360
361QAbstractAspect *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
367QAbstractAspect *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
376QAbstractAspectJobManager *QAspectManager::jobManager() const
377{
378 return m_jobManager;
379}
380
381QChangeArbiter *QAspectManager::changeArbiter() const
382{
383 return m_changeArbiter;
384}
385
386QServiceLocator *QAspectManager::serviceLocator() const
387{
388 return m_serviceLocator.data();
389}
390
391void QAspectManager::setPostConstructorInit(NodePostConstructorInit *postConstructorInit)
392{
393 m_postConstructorInit = postConstructorInit;
394}
395
396QNode *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
405QList<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
414QScene *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
423void 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 */
433bool 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
452void 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
464void 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
536QT_END_NAMESPACE
537
538#include "moc_qaspectmanager_p.cpp"
539

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qt3d/src/core/aspects/qaspectmanager.cpp