1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29// TODO Remove in Qt6
30#include <QtCore/qcompilerdetection.h>
31QT_WARNING_DISABLE_DEPRECATED
32
33#include <QtTest/QTest>
34#include <Qt3DCore/qnode.h>
35#include <Qt3DCore/qentity.h>
36#include <Qt3DCore/qcomponent.h>
37#include <Qt3DCore/qaspectengine.h>
38#include <Qt3DCore/qabstractaspect.h>
39#include <Qt3DCore/private/qscene_p.h>
40#include <Qt3DCore/qcomponentaddedchange.h>
41#include <Qt3DCore/qcomponentremovedchange.h>
42#include <Qt3DCore/qnodedestroyedchange.h>
43#include <Qt3DCore/qpropertynodeaddedchange.h>
44#include <Qt3DCore/qpropertynoderemovedchange.h>
45#include <Qt3DCore/private/qnodecreatedchangegenerator_p.h>
46#include <Qt3DCore/private/qaspectengine_p.h>
47#include <Qt3DCore/private/qaspectengine_p.h>
48#include <private/qabstractaspect_p.h>
49#include <private/qpostman_p.h>
50
51#include <Qt3DCore/private/qlockableobserverinterface_p.h>
52#include <Qt3DCore/private/qnode_p.h>
53#include <Qt3DCore/private/qcomponent_p.h>
54#include <QSignalSpy>
55#include "testpostmanarbiter.h"
56#include <vector>
57
58class tst_Nodes : public QObject
59{
60 Q_OBJECT
61public:
62 tst_Nodes() : QObject()
63 {
64 qRegisterMetaType<Qt3DCore::QNode*>();
65 }
66 ~tst_Nodes() {}
67
68private slots:
69 void initTestCase();
70 void defaultNodeConstruction();
71 void defaultComponentConstruction();
72 void defaultEntityConstrution();
73
74 void appendSingleChildNodeToNodeNoSceneExplicitParenting();
75 void appendSingleChildNodeToNodeNoSceneImplicitParenting();
76 void appendMultipleChildNodesToNodeNoScene();
77
78 void appendSingleChildNodeToNodeSceneExplicitParenting();
79 void appendSingleChildNodeToNodeSceneImplicitParenting();
80 void appendMultipleChildNodesToNodeScene();
81
82 void checkParentChangeToNull();
83 void checkParentChangeToOtherParent();
84 void checkParentChangeFromExistingBackendParentToNewlyCreatedParent();
85 void checkBackendNodesCreatedFromTopDown(); //QTBUG-74106
86 void checkBackendNodesCreatedFromTopDownWithReparenting();
87 void checkAllBackendCreationDoneInSingleFrame();
88
89 void removingSingleChildNodeFromNode();
90 void removingMultipleChildNodesFromNode();
91
92 void appendingChildEntitiesToNode();
93 void removingChildEntitiesFromNode();
94
95 void checkConstructionSetParentMix(); // QTBUG-60612
96 void checkParentingQEntityToQNode(); // QTBUG-73905
97 void checkConstructionWithParent();
98 void checkConstructionWithNonRootParent(); // QTBUG-73986
99 void checkConstructionAsListElement();
100 void checkSceneIsSetOnConstructionWithParent(); // QTBUG-69352
101 void checkSubNodePostConstructIsCalledWhenReferincingNodeProperty(); // QTBUG-79350
102
103 void appendingComponentToEntity();
104 void appendingParentlessComponentToEntityWithoutScene();
105 void appendingParentlessComponentToEntityWithScene();
106 void appendingParentlessComponentToNonRootEntity();
107 void removingComponentFromEntity();
108
109 void changeCustomProperty();
110 void checkDestruction();
111
112 void checkDefaultConstruction();
113 void checkPropertyChanges();
114 void checkCreationData();
115 void checkEnabledUpdate();
116 void checkPropertyTrackModeUpdate();
117 void checkTrackedPropertyNamesUpdate();
118
119 void checkNodeRemovedFromDirtyListOnDestruction();
120
121 void checkBookkeepingSingleNode();
122 void checkBookkeeping_QVector_OfNode();
123 void checkBookkeeping_stdvector_OfNode();
124};
125
126class ObserverSpy;
127class SimplePostman : public Qt3DCore::QAbstractPostman
128{
129public:
130 SimplePostman(ObserverSpy *spy)
131 : m_spy(spy)
132 {}
133
134 void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &) final {}
135 void setScene(Qt3DCore::QScene *) final {}
136 void notifyBackend(const Qt3DCore::QSceneChangePtr &change) final;
137 bool shouldNotifyFrontend(const Qt3DCore::QSceneChangePtr &changee) final { Q_UNUSED(changee); return false; }
138
139private:
140 ObserverSpy *m_spy;
141};
142
143class ObserverSpy : public Qt3DCore::QAbstractArbiter
144{
145public:
146 class ChangeRecord : public QPair<Qt3DCore::QSceneChangePtr, bool>
147 {
148 public:
149 ChangeRecord(const Qt3DCore::QSceneChangePtr &event, bool locked)
150 : QPair<Qt3DCore::QSceneChangePtr, bool>(event, locked)
151 {}
152
153 Qt3DCore::QSceneChangePtr change() const { return first; }
154
155 bool wasLocked() const { return second; }
156 };
157
158 ObserverSpy()
159 : Qt3DCore::QAbstractArbiter()
160 , m_postman(new SimplePostman(this))
161 {
162 }
163
164 ~ObserverSpy();
165
166 void sceneChangeEventWithLock(const Qt3DCore::QSceneChangePtr &e) override
167 {
168 events << ChangeRecord(e, true);
169 }
170
171 void sceneChangeEventWithLock(const Qt3DCore::QSceneChangeList &e) override
172 {
173 for (size_t i = 0, m = e.size(); i < m; ++i) {
174 events << ChangeRecord(e.at(n: i), false);
175 }
176 }
177
178 void sceneChangeEvent(const Qt3DCore::QSceneChangePtr &e) override
179 {
180 events << ChangeRecord(e, false);
181 }
182
183 Qt3DCore::QAbstractPostman *postman() const final
184 {
185 return m_postman.data();
186 }
187
188 void addDirtyFrontEndNode(Qt3DCore::QNode *node) final {
189 if (!dirtyNodes.contains(t: node))
190 dirtyNodes << node;
191 }
192
193 void addDirtyFrontEndNode(Qt3DCore::QNode *node, Qt3DCore::QNode *subNode, const char *property, Qt3DCore::ChangeFlag change) final {
194 if (!dirtyNodes.contains(t: node))
195 dirtyNodes << node;
196 dirtySubNodes.push_back(t: {.node: node, .subNode: subNode, .change: change, .property: property});
197 }
198
199 void removeDirtyFrontEndNode(Qt3DCore::QNode *node) final {
200 dirtyNodes.removeOne(t: node);
201 }
202
203 QVector<Qt3DCore::QNode *> dirtyNodes;
204 QVector<Qt3DCore::NodeRelationshipChange> dirtySubNodes;
205 QList<ChangeRecord> events;
206 QScopedPointer<SimplePostman> m_postman;
207};
208
209ObserverSpy::~ObserverSpy()
210{
211}
212
213void SimplePostman::notifyBackend(const Qt3DCore::QSceneChangePtr &change)
214{
215 m_spy->sceneChangeEventWithLock(e: change);
216}
217
218
219
220class MyQNode : public Qt3DCore::QNode
221{
222 Q_OBJECT
223 Q_PROPERTY(QString customProperty READ customProperty WRITE setCustomProperty NOTIFY customPropertyChanged)
224 Q_PROPERTY(Qt3DCore::QNode *nodeProperty READ nodeProperty WRITE setNodeProperty NOTIFY nodePropertyChanged)
225
226public:
227 explicit MyQNode(Qt3DCore::QNode *parent = nullptr)
228 : QNode(parent)
229 , m_nodeProperty(nullptr)
230 {}
231
232 ~MyQNode()
233 {
234 }
235
236 void setCustomProperty(const QString &s)
237 {
238 if (m_customProperty == s)
239 return;
240
241 m_customProperty = s;
242 emit customPropertyChanged();
243 }
244
245 QString customProperty() const
246 {
247 return m_customProperty;
248 }
249
250 void setArbiterAndScene(Qt3DCore::QAbstractArbiter *arbiter,
251 Qt3DCore::QScene *scene = nullptr)
252 {
253 Q_ASSERT(arbiter);
254 if (scene)
255 scene->setArbiter(arbiter);
256 Qt3DCore::QNodePrivate::get(q: this)->setScene(scene);
257 Qt3DCore::QNodePrivate::get(q: this)->setArbiter(arbiter);
258 }
259
260 void setSimulateBackendCreated(bool created)
261 {
262 Qt3DCore::QNodePrivate::get(q: this)->m_hasBackendNode = created;
263 }
264
265 Qt3DCore::QNode *nodeProperty() const { return m_nodeProperty; }
266
267 const QList<Qt3DCore::QNode *> &attributes() const { return m_attributes; }
268
269public slots:
270 void setNodeProperty(Qt3DCore::QNode *node)
271 {
272 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
273 if (m_nodeProperty == node)
274 return;
275
276 if (m_nodeProperty)
277 d->unregisterDestructionHelper(node: m_nodeProperty);
278
279 if (node && !node->parent())
280 node->setParent(this);
281
282 m_nodeProperty = node;
283
284 // Ensures proper bookkeeping
285 if (m_nodeProperty)
286 d->registerDestructionHelper(node: m_nodeProperty, func: &MyQNode::setNodeProperty, m_nodeProperty);
287
288 emit nodePropertyChanged(node);
289 }
290
291 void addAttribute(Qt3DCore::QNode *attribute)
292 {
293 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
294 if (!m_attributes.contains(t: attribute)) {
295 m_attributes.append(t: attribute);
296
297 // Ensures proper bookkeeping
298 d->registerDestructionHelper(node: attribute, func: &MyQNode::removeAttribute, m_attributes);
299
300 // We need to add it as a child of the current node if it has been declared inline
301 // Or not previously added as a child of the current node so that
302 // 1) The backend gets notified about it's creation
303 // 2) When the current node is destroyed, it gets destroyed as well
304 if (!attribute->parent())
305 attribute->setParent(this);
306
307 d->updateNode(node: attribute, property: "attribute", change: Qt3DCore::PropertyValueAdded);
308 }
309 }
310
311 void removeAttribute(Qt3DCore::QNode *attribute)
312 {
313 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
314 d->updateNode(node: attribute, property: "attribute", change: Qt3DCore::PropertyValueRemoved);
315
316 m_attributes.removeOne(t: attribute);
317 // Remove bookkeeping connection
318 d->unregisterDestructionHelper(node: attribute);
319 }
320
321signals:
322 void customPropertyChanged();
323 void nodePropertyChanged(Qt3DCore::QNode *node);
324
325protected:
326 QString m_customProperty;
327 Qt3DCore::QNode *m_nodeProperty;
328 QList<Qt3DCore::QNode *> m_attributes;
329};
330
331
332class MyQNodeVec : public Qt3DCore::QNode
333{
334 Q_OBJECT
335public:
336 explicit MyQNodeVec(Qt3DCore::QNode *parent = nullptr)
337 : QNode(parent)
338 {}
339
340 ~MyQNodeVec()
341 {
342 }
343
344 void setArbiterAndScene(Qt3DCore::QChangeArbiter *arbiter,
345 Qt3DCore::QScene *scene = nullptr)
346 {
347 Q_ASSERT(arbiter);
348 if (scene)
349 scene->setArbiter(arbiter);
350 Qt3DCore::QNodePrivate::get(q: this)->setScene(scene);
351 Qt3DCore::QNodePrivate::get(q: this)->setArbiter(arbiter);
352 }
353
354 void setSimulateBackendCreated(bool created)
355 {
356 Qt3DCore::QNodePrivate::get(q: this)->m_hasBackendNode = created;
357 }
358
359 const std::vector<Qt3DCore::QNode *> &attributes() const { return m_attributes; }
360
361public slots:
362
363 void addAttribute(Qt3DCore::QNode *attribute)
364 {
365 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
366 if (std::find(first: std::begin(cont&: m_attributes), last: std::end(cont&: m_attributes), val: attribute) == std::end(cont&: m_attributes)) {
367 m_attributes.push_back(x: attribute);
368
369 // Ensures proper bookkeeping
370 d->registerDestructionHelper(node: attribute, func: &MyQNodeVec::removeAttribute, m_attributes);
371
372 // We need to add it as a child of the current node if it has been declared inline
373 // Or not previously added as a child of the current node so that
374 // 1) The backend gets notified about it's creation
375 // 2) When the current node is destroyed, it gets destroyed as well
376 if (!attribute->parent())
377 attribute->setParent(this);
378
379 d->update();
380 }
381 }
382
383 void removeAttribute(Qt3DCore::QNode *attribute)
384 {
385 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
386 d->update();
387
388 m_attributes.erase(first: std::remove(first: m_attributes.begin(),
389 last: m_attributes.end(),
390 value: attribute), last: m_attributes.end());
391 // Remove bookkeeping connection
392 d->unregisterDestructionHelper(node: attribute);
393 }
394
395protected:
396 std::vector<Qt3DCore::QNode *> m_attributes;
397};
398
399class MyQEntity : public Qt3DCore::QEntity
400{
401 Q_OBJECT
402 Q_PROPERTY(MyQNode *nodeProperty READ nodeProperty WRITE setNodeProperty NOTIFY nodePropertyChanged)
403public:
404 explicit MyQEntity(Qt3DCore::QNode *parent = nullptr)
405 : QEntity(parent)
406 {}
407
408 ~MyQEntity()
409 {
410 }
411
412 void setArbiterAndScene(Qt3DCore::QAbstractArbiter *arbiter,
413 Qt3DCore::QScene *scene = nullptr)
414 {
415 Q_ASSERT(arbiter);
416 if (scene)
417 scene->setArbiter(arbiter);
418 Qt3DCore::QNodePrivate::get(q: this)->setScene(scene);
419 Qt3DCore::QNodePrivate::get(q: this)->setArbiter(arbiter);
420 }
421
422 void setArbiterAndEngine(Qt3DCore::QAbstractArbiter *arbiter,
423 Qt3DCore::QAspectEngine *engine)
424 {
425 Q_ASSERT(arbiter);
426 Q_ASSERT(engine);
427 auto scene = Qt3DCore::QAspectEnginePrivate::get(engine)->m_scene;
428 Qt3DCore::QNodePrivate::get(q: this)->setScene(scene);
429 Qt3DCore::QNodePrivate::get(q: this)->setArbiter(arbiter);
430
431 if (scene) {
432 scene->setArbiter(arbiter);
433 engine->setRootEntity(Qt3DCore::QEntityPtr(this, [](QEntity *) {}));
434 scene->setArbiter(arbiter);
435 Qt3DCore::QNodePrivate::get(q: this)->setArbiter(arbiter);
436 }
437 }
438
439 void setSimulateBackendCreated(bool created)
440 {
441 Qt3DCore::QNodePrivate::get(q: this)->m_hasBackendNode = created;
442 }
443
444 MyQNode *nodeProperty() const { return m_nodeProperty; }
445
446 void addAttribute(MyQNode *attribute)
447 {
448 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
449 if (!m_attributes.contains(t: attribute)) {
450 m_attributes.append(t: attribute);
451
452 // Ensures proper bookkeeping
453 d->registerDestructionHelper(node: attribute, func: &MyQEntity::removeAttribute, m_attributes);
454
455 // We need to add it as a child of the current node if it has been declared inline
456 // Or not previously added as a child of the current node so that
457 // 1) The backend gets notified about it's creation
458 // 2) When the current node is destroyed, it gets destroyed as well
459 if (!attribute->parent())
460 attribute->setParent(this);
461
462 d->updateNode(node: attribute, property: "attribute", change: Qt3DCore::PropertyValueRemoved);
463 }
464 }
465
466 void removeAttribute(MyQNode *attribute)
467 {
468 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
469 d->update();
470
471 m_attributes.removeOne(t: attribute);
472 // Remove bookkeeping connection
473 d->unregisterDestructionHelper(node: attribute);
474 }
475
476public slots:
477 void setNodeProperty(MyQNode *node)
478 {
479 Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(q: this);
480 if (m_nodeProperty == node)
481 return;
482
483 if (m_nodeProperty)
484 d->unregisterDestructionHelper(node: m_nodeProperty);
485
486 if (node && !node->parent())
487 node->setParent(this);
488
489 m_nodeProperty = node;
490
491 // Ensures proper bookkeeping
492 if (m_nodeProperty)
493 d->registerDestructionHelper(node: m_nodeProperty, func: &MyQEntity::setNodeProperty, m_nodeProperty);
494
495 emit nodePropertyChanged(node);
496 }
497
498signals:
499 void nodePropertyChanged(MyQNode *node);
500
501private:
502 MyQNode *m_nodeProperty;
503 QVector<MyQNode *> m_attributes;
504};
505
506class MyQComponent : public Qt3DCore::QComponent
507{
508 Q_OBJECT
509public:
510 explicit MyQComponent(Qt3DCore::QNode *parent = nullptr) : QComponent(parent)
511 {}
512 void setArbiter(Qt3DCore::QAbstractArbiter *arbiter)
513 {
514 Q_ASSERT(arbiter);
515 Qt3DCore::QComponentPrivate::get(q: this)->setArbiter(arbiter);
516 }
517};
518
519class MyFakeMaterial : public Qt3DCore::QComponent
520{
521 Q_OBJECT
522public:
523 explicit MyFakeMaterial(Qt3DCore::QNode *parent = nullptr)
524 : QComponent(parent)
525 , m_effect(new MyQNode(this))
526 , m_technique(new MyQNode(m_effect))
527 , m_renderPass(new MyQNode(m_technique))
528 {
529 }
530
531 void setArbiter(Qt3DCore::QAbstractArbiter *arbiter)
532 {
533 Q_ASSERT(arbiter);
534 Qt3DCore::QComponentPrivate::get(q: this)->setArbiter(arbiter);
535 }
536
537 MyQNode *m_effect;
538 MyQNode *m_technique;
539 MyQNode *m_renderPass;
540};
541
542class TestAspectPrivate;
543class TestAspect : public Qt3DCore::QAbstractAspect
544{
545 Q_OBJECT
546public:
547 explicit TestAspect(QObject *parent = nullptr);
548
549 enum ChangeType { Creation, Destruction };
550
551 void clearNodes()
552 {
553 events.clear();
554 allNodes.clear();
555 }
556
557 void addEvent(const Qt3DCore::QNodeId &id, ChangeType change)
558 {
559 events.push_back(t: Event{ .type: change, .nodeId: id});
560 }
561
562 QVector<Qt3DCore::QNodeId> filteredEvents(ChangeType change) const
563 {
564 QVector<Qt3DCore::QNodeId> result;
565 for (const auto &event : events) {
566 if (event.type == change)
567 result.push_back(t: event.nodeId);
568 }
569 return result;
570 }
571
572 struct Event{
573 ChangeType type;
574 Qt3DCore::QNodeId nodeId;
575 };
576
577 mutable QVector<Event> events;
578 mutable QHash<Qt3DCore::QNodeId, Qt3DCore::QNode *> allNodes;
579
580private:
581 Q_DECLARE_PRIVATE(TestAspect)
582 explicit TestAspect(TestAspectPrivate &dd, QObject *parent);
583};
584
585class TestFunctor : public Qt3DCore::QBackendNodeMapper
586{
587public:
588 TestFunctor(TestAspect *aspect) : m_aspect(aspect) {}
589
590 Qt3DCore::QBackendNode *create(const Qt3DCore::QNodeCreatedChangeBasePtr &change) const override
591 {
592 auto node = new Qt3DCore::QBackendNode;
593 m_Nodes.insert(key: change->subjectId(), value: node);
594 m_aspect->addEvent(id: change->subjectId(), change: TestAspect::Creation);
595 return node;
596 }
597
598 Qt3DCore::QBackendNode *get(Qt3DCore::QNodeId id) const override
599 {
600 return m_Nodes.value(key: id, defaultValue: nullptr);
601 }
602
603 void destroy(Qt3DCore::QNodeId id) const override
604 {
605 if (m_Nodes.contains(key: id)) {
606 m_aspect->addEvent(id, change: TestAspect::Destruction);
607 delete m_Nodes.take(key: id);
608 }
609 }
610
611private:
612 mutable QHash<Qt3DCore::QNodeId, Qt3DCore::QBackendNode *> m_Nodes;
613 TestAspect *m_aspect;
614};
615
616class TestAspectPrivate : public Qt3DCore::QAbstractAspectPrivate
617{
618public:
619 TestAspectPrivate() = default;
620 void syncDirtyFrontEndNode(Qt3DCore::QNode *node, Qt3DCore::QBackendNode *backend,
621 bool firstTime) const override
622 {
623 Q_UNUSED(backend);
624 auto q = q_func();
625 if (firstTime)
626 q->allNodes.insert(key: node->id(), value: node);
627 }
628
629 Q_DECLARE_PUBLIC(TestAspect)
630};
631
632TestAspect::TestAspect(QObject *parent) : TestAspect(*new TestAspectPrivate, parent)
633{
634 registerBackendType<Qt3DCore::QEntity, true>(functor: QSharedPointer<TestFunctor>::create(arguments: this));
635 registerBackendType<MyQEntity, true>(functor: QSharedPointer<TestFunctor>::create(arguments: this));
636 registerBackendType<MyQNode, true>(functor: QSharedPointer<TestFunctor>::create(arguments: this));
637 registerBackendType<Qt3DCore::QNode, true>(functor: QSharedPointer<TestFunctor>::create(arguments: this));
638}
639
640TestAspect::TestAspect(TestAspectPrivate &dd, QObject *parent)
641 : Qt3DCore::QAbstractAspect(dd, parent)
642{
643 setObjectName(QStringLiteral("Test Aspect"));
644}
645
646namespace {
647void verifyChildrenCreatedBeforeParents(Qt3DCore::QNode *root, TestAspect *aspect)
648{
649 QSet<Qt3DCore::QNodeId> processedNodes;
650 processedNodes.insert(value: root->id());
651 for (const auto &nodeId : aspect->filteredEvents(change: TestAspect::Creation)) {
652 const auto node = aspect->allNodes.value(key: nodeId);
653 Q_ASSERT(node);
654 const auto parentNode = node->parentNode();
655 QVERIFY(parentNode == nullptr || processedNodes.contains(parentNode->id()));
656 processedNodes.insert(value: nodeId);
657 }
658}
659}
660
661void tst_Nodes::initTestCase()
662{
663 qRegisterMetaType<Qt3DCore::QNode::PropertyTrackingMode>(typeName: "PropertyTrackingMode");
664}
665
666void tst_Nodes::defaultNodeConstruction()
667{
668 // GIVEN
669 QScopedPointer<MyQNode> node(new MyQNode());
670
671 // THEN
672 QVERIFY(node != nullptr);
673 QVERIFY(node->children().isEmpty());
674
675 // GIVEN
676 MyQNode *node2 = new MyQNode(node.data());
677 QCoreApplication::processEvents();
678
679 // THEN
680 QVERIFY(node2->parent() == node.data());
681 QVERIFY(!node->children().isEmpty());
682 QVERIFY(node2->children().isEmpty());
683}
684
685void tst_Nodes::defaultComponentConstruction()
686{
687 // GIVEN
688 QScopedPointer<MyQComponent> comp(new MyQComponent());
689 MyQComponent *comp2 = new MyQComponent(comp.data());
690 QCoreApplication::processEvents();
691
692 // THEN
693 QVERIFY(comp != nullptr);
694 QCOMPARE(comp2->parent(), comp.data());
695}
696
697void tst_Nodes::defaultEntityConstrution()
698{
699 // GIVEN
700 QScopedPointer<Qt3DCore::QEntity> entity(new Qt3DCore::QEntity());
701 Qt3DCore::QEntity *entity2 = new Qt3DCore::QEntity(entity.data());
702 QCoreApplication::processEvents();
703
704 // THEN
705 QVERIFY(entity->components().isEmpty());
706 QVERIFY(entity2->components().isEmpty());
707 QCOMPARE(entity2->parent(), entity.data());
708}
709
710void tst_Nodes::appendSingleChildNodeToNodeNoSceneExplicitParenting()
711{
712 // Check nodes added when no scene is set
713 // GIVEN
714 ObserverSpy spy;
715 QScopedPointer<MyQNode> node(new MyQNode());
716 node->setArbiterAndScene(arbiter: &spy);
717
718 // THEN
719 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
720 // WHEN
721 QScopedPointer<MyQNode> child(new MyQNode());
722
723 // THEN
724 QVERIFY(child->parent() == nullptr);
725
726 // WHEN
727 child->setParent(node.data());
728
729 // THEN
730 QVERIFY(child->parent() == node.data());
731 QVERIFY(child->parentNode() == node.data());
732 QCOMPARE(node->children().count(), 1);
733
734 // Events are only sent when a scene is set on the root node
735 QCOMPARE(spy.events.size(), 0);
736}
737
738void tst_Nodes::appendSingleChildNodeToNodeNoSceneImplicitParenting()
739{
740 // Check nodes added when no scene is set
741 // GIVEN
742 ObserverSpy spy;
743 QScopedPointer<MyQNode> node(new MyQNode());
744 node->setArbiterAndScene(arbiter: &spy);
745
746 // THEN
747 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
748 // WHEN
749 QScopedPointer<MyQNode> child(new MyQNode(node.data()));
750 QCoreApplication::processEvents();
751
752 // THEN
753 QVERIFY(child->parent() == node.data());
754 QVERIFY(child->parentNode() == node.data());
755 QCOMPARE(node->children().count(), 1);
756
757 // Events are only sent when a scene is set on the root node
758 QCOMPARE(spy.events.size(), 0);
759}
760
761void tst_Nodes::appendMultipleChildNodesToNodeNoScene()
762{
763 // Check multiple nodes added with no scene set
764 // GIVEN
765 ObserverSpy spy;
766 QScopedPointer<MyQNode> node(new MyQNode());
767 node->setArbiterAndScene(arbiter: &spy);
768
769 // THEN
770 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
771 // WHEN
772 for (int i = 0; i < 10; i++) {
773 // WHEN
774 Qt3DCore::QNode *child = nullptr;
775 if (i % 2 == 0) {
776 child = new MyQNode(node.data());
777 QCoreApplication::processEvents();
778 } else {
779 child = new MyQNode();
780 child->setParent(node.data());
781 }
782 // THEN
783 QVERIFY(child->parent() == node.data());
784 }
785
786 // THEN
787 QCOMPARE(node->children().count(), 10);
788
789 // Events are only sent when a scene is set on the root node
790 QCOMPARE(spy.events.size(), 0);
791}
792
793void tst_Nodes::appendSingleChildNodeToNodeSceneExplicitParenting()
794{
795 // Check nodes added when scene is set
796 // GIVEN
797 Qt3DCore::QScene scene;
798 ObserverSpy spy;
799 QScopedPointer<MyQNode> node(new MyQNode());
800 // WHEN
801 node->setArbiterAndScene(arbiter: &spy, scene: &scene);
802 node->setSimulateBackendCreated(true);
803
804 // THEN
805 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
806
807 // WHEN
808 QScopedPointer<MyQNode> child(new MyQNode());
809
810 // THEN
811 QVERIFY(child->parent() == nullptr);
812 QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() == nullptr);
813
814 // WHEN
815 child->setParent(node.data());
816
817 // THEN
818 QVERIFY(child->parent() == node.data());
819 QVERIFY(child->parentNode() == node.data());
820 QCOMPARE(spy.events.size(), 1); // Child Added
821 QCOMPARE(node->children().count(), 1);
822 QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
823
824 // Node Added event
825 QVERIFY(spy.events.first().wasLocked());
826 Qt3DCore::QPropertyNodeAddedChangePtr additionEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
827 QVERIFY(additionEvent);
828 QCOMPARE(additionEvent->subjectId(), node->id());
829 QCOMPARE(additionEvent->addedNodeId(), child->id());
830 QCOMPARE(additionEvent->metaObject(), child->metaObject());
831}
832
833void tst_Nodes::appendSingleChildNodeToNodeSceneImplicitParenting()
834{
835 // Check nodes added when scene is set
836 // GIVEN
837 Qt3DCore::QScene scene;
838 ObserverSpy spy;
839 QScopedPointer<MyQNode> node(new MyQNode());
840
841 // WHEN
842 node->setArbiterAndScene(arbiter: &spy, scene: &scene);
843 // THEN
844 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
845
846 // WHEN
847 QScopedPointer<MyQNode> child(new MyQNode(node.data()));
848 QCoreApplication::processEvents();
849
850 // THEN
851 QVERIFY(child->parent() == node.data());
852 QVERIFY(child->parentNode() == node.data());
853 QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
854
855 QCOMPARE(spy.events.size(), 1);
856 QVERIFY(spy.events.first().wasLocked());
857 QCOMPARE(node->children().count(), 1);
858
859 // Node Added event
860 QVERIFY(spy.events.first().wasLocked());
861 Qt3DCore::QPropertyNodeAddedChangePtr additionEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
862 QVERIFY(additionEvent);
863 QCOMPARE(additionEvent->subjectId(), node->id());
864 QCOMPARE(additionEvent->addedNodeId(), child->id());
865 QCOMPARE(additionEvent->metaObject(), child->metaObject());
866}
867
868void tst_Nodes::appendMultipleChildNodesToNodeScene()
869{
870 // Check nodes added when scene is set
871
872 // GIVEN
873 Qt3DCore::QScene scene;
874 ObserverSpy spy;
875 QScopedPointer<MyQNode> node(new MyQNode());
876
877 // WHEN
878 node->setArbiterAndScene(arbiter: &spy, scene: &scene);
879 node->setSimulateBackendCreated(true);
880 // THEN
881 QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
882
883 // WHEN
884 const auto childCount = 10;
885 for (int i = 0; i < childCount; i++) {
886 // WHEN
887 Qt3DCore::QNode *child = nullptr;
888 if (i % 2 == 0) {
889 child = new MyQNode(node.data());
890 } else {
891 child = new MyQNode();
892 child->setParent(node.data());
893 }
894
895 // THEN parent and scene should be set synchronously
896 QVERIFY(child->parent() == node.data());
897 QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() == Qt3DCore::QNodePrivate::get(node.data())->m_scene);
898 }
899 // THEN
900 QCOMPARE(node->children().count(), 10);
901
902 // WHEN
903 QCoreApplication::processEvents();
904
905 // THEN backend is notified after the event loop spins. The recorded events are a little
906 // tricky to understand and differs for children with the parent being set at construction
907 // time (even children and ids) and the children being created without a parent and then
908 // explicitly calling setParent() after (odd children and ids).
909 //
910 // Even children:
911 // child constructed
912 // notifications to backend scheduled via the event loop as object is not yet fully constructed
913 //
914 // Odd children:
915 // child constructed
916 // parent set
917 // notifications to backend sent immediately as object is fully constructed
918 //
919 // With this in mind, the recorded events should show:
920 //
921 // for each odd child:
922 // child addition to parent of odd child
923 //
924 // followed by:
925 //
926 // for each even child:
927 // child addition to parent of even child
928 //
929 const auto expectedEventCount = childCount;
930 QCOMPARE(spy.events.size(), 10);
931
932 for (auto i = 0; i < expectedEventCount; ++i) {
933 const auto childIndex = i;
934 Qt3DCore::QNode *child = node->childNodes().at(i: childIndex);
935
936 const auto recordIndex = (i % 2 == 0) ? expectedEventCount / 2 + i / 2 : i / 2;
937 const auto additionRecord = spy.events.at(i: recordIndex);
938 Qt3DCore::QPropertyNodeAddedChangePtr additionEvent = additionRecord.change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
939 QCOMPARE(additionEvent->subjectId(), node->id());
940 QCOMPARE(additionEvent->addedNodeId(), child->id());
941 QCOMPARE(additionEvent->metaObject(), child->metaObject());
942 }
943}
944
945void tst_Nodes::checkParentChangeToNull()
946{
947 // GIVEN
948 Qt3DCore::QScene scene;
949 ObserverSpy spy;
950 QScopedPointer<MyQNode> root(new MyQNode());
951
952 // WHEN
953 root->setArbiterAndScene(arbiter: &spy, scene: &scene);
954 QScopedPointer<Qt3DCore::QNode> child(new MyQNode(root.data()));
955 QCoreApplication::processEvents();
956
957 // THEN
958 QVERIFY(child->parent() == root.data());
959 QCOMPARE(spy.events.size(), 1);
960 QCOMPARE(root->children().size(), 1);
961
962 // WHEN
963 spy.events.clear();
964 child->setParent(Q_NODE_NULLPTR);
965
966 // THEN
967 QVERIFY(child->parent() == nullptr);
968 QCOMPARE(root->children().size(), 0);
969 QCOMPARE(spy.events.size(), 0);
970}
971
972void tst_Nodes::checkParentChangeToOtherParent()
973{
974 // GIVEN
975 Qt3DCore::QScene scene;
976 ObserverSpy spy;
977 QScopedPointer<MyQNode> root(new MyQNode());
978 root->setArbiterAndScene(arbiter: &spy, scene: &scene);
979 QScopedPointer<MyQNode> parent1(new MyQNode(root.data()));
980 QScopedPointer<MyQNode> parent2(new MyQNode(root.data()));
981 QCoreApplication::processEvents();
982
983 // THEN
984 QCOMPARE(spy.events.size(), 2); // 2 x (1 node added to children change)
985
986 // WHEN
987 spy.events.clear();
988 QScopedPointer<Qt3DCore::QNode> child(new MyQNode(parent1.data()));
989 QCoreApplication::processEvents();
990
991 // THEN
992 QVERIFY(child->parent() == parent1.data());
993 QCOMPARE(parent1->children().size(), 1);
994 QCOMPARE(parent2->children().size(), 0);
995 QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
996 QCOMPARE(spy.events.size(), 1); // 1 node added to children change
997
998 // WHEN
999 spy.events.clear();
1000 child->setParent(parent2.data());
1001
1002 // THEN
1003 QVERIFY(child->parent() == parent2.data());
1004 QCOMPARE(parent1->children().size(), 0);
1005 QCOMPARE(parent2->children().size(), 1);
1006 QCOMPARE(spy.events.size(), 1);
1007
1008 // CHECK event 1 is a Node Removed event
1009 // this no longer generated because nodes don't have backends yet
1010
1011 QVERIFY(!Qt3DCore::QNodePrivate::get(child.data())->m_hasBackendNode);
1012
1013
1014// QVERIFY(spy.events.first().wasLocked());
1015// const Qt3DCore::QPropertyNodeRemovedChangePtr event = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeRemovedChange>();
1016// QCOMPARE(event->type(), Qt3DCore::PropertyValueRemoved);
1017// QCOMPARE(event->subjectId(), parent1->id());
1018// QCOMPARE(event->removedNodeId(), child->id());
1019// QCOMPARE(event->metaObject(), child->metaObject());
1020
1021 // CHECK event 2 is a Node Added event
1022 QVERIFY(spy.events.last().wasLocked());
1023 const Qt3DCore::QPropertyNodeAddedChangePtr event2 = spy.events.last().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1024 QCOMPARE(event2->type(), Qt3DCore::PropertyValueAdded);
1025 QCOMPARE(event2->subjectId(), parent2->id());
1026 QCOMPARE(event2->addedNodeId(), child->id());
1027 QCOMPARE(event2->metaObject(), child->metaObject());
1028}
1029
1030void tst_Nodes::checkParentChangeFromExistingBackendParentToNewlyCreatedParent()
1031{
1032 // GIVEN
1033 ObserverSpy spy;
1034 Qt3DCore::QAspectEngine engine;
1035 engine.setRunMode(Qt3DCore::QAspectEngine::Manual);
1036 QScopedPointer<MyQEntity> root(new MyQEntity());
1037 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1038 auto aspect = new TestAspect;
1039 engine.registerAspect(aspect);
1040
1041 MyQNode *child(new MyQNode(root.data()));
1042 MyQNode *child2(new MyQNode(root.data()));
1043
1044 QCoreApplication::processEvents();
1045 engine.processFrame();
1046
1047 // Due to the way we create root, it has a backend
1048 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode == true);
1049 QCOMPARE(aspect->events.count(), 2);
1050 QCOMPARE(aspect->events[0].type, TestAspect::Creation);
1051 QCOMPARE(aspect->events[0].nodeId, child->id());
1052 QCOMPARE(aspect->events[1].type, TestAspect::Creation);
1053 QCOMPARE(aspect->events[1].nodeId, child2->id());
1054
1055 // THEN
1056 QCOMPARE(spy.events.size(), 2); // 2 x (1 child added to parent change)
1057
1058 // WHEN -> Reparenting child with backend node to new parent with no backend yet
1059 aspect->clearNodes();
1060 spy.events.clear();
1061 QScopedPointer<Qt3DCore::QNode> newParent(new MyQNode(root.data()));
1062 child->setParent(newParent.data());
1063
1064 // THEN
1065 QVERIFY(child->parent() == newParent.data());
1066 QCOMPARE(newParent->childNodes().size(), 1);
1067 QCOMPARE(child2->childNodes().size(), 0);
1068 QCOMPARE(root->childNodes().size(), 2);
1069 QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->m_hasBackendNode == false);
1070 QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == false);
1071 QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->scene() != nullptr);
1072 QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() != nullptr);
1073
1074 // WHEN
1075 QCoreApplication::processEvents();
1076 engine.processFrame();
1077
1078 // THEN
1079 QCOMPARE(spy.events.size(), 2);
1080 // 1 node removed change
1081 // 1 node added to children change
1082 QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->m_hasBackendNode == true);
1083 QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == true);
1084
1085 {
1086 // CHECK event 1 is a Node Removed event
1087 QVERIFY(spy.events.first().wasLocked());
1088 const Qt3DCore::QPropertyNodeRemovedChangePtr event = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeRemovedChange>();
1089 QCOMPARE(event->type(), Qt3DCore::PropertyValueRemoved);
1090 QCOMPARE(event->subjectId(), root->id());
1091 QCOMPARE(event->removedNodeId(), child->id());
1092 QCOMPARE(event->metaObject(), child->metaObject());
1093
1094 const Qt3DCore::QPropertyNodeAddedChangePtr event2 = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeAddedChange>();
1095 QCOMPARE(event2->type(), Qt3DCore::PropertyValueAdded);
1096 QCOMPARE(event2->addedNodeId(), newParent->id());
1097 QCOMPARE(event2->metaObject(), newParent->metaObject());
1098 QCOMPARE(event2->subjectId(), root->id());
1099
1100 QCOMPARE(aspect->events.count(), 3);
1101
1102 // child backend is destroyed because it was reparented to node without backend (newParent)
1103 QCOMPARE(aspect->events[0].type, TestAspect::Destruction);
1104 QCOMPARE(aspect->events[0].nodeId, child->id());
1105
1106 // newParent and child both get backends created
1107 QCOMPARE(aspect->events[1].type, TestAspect::Creation);
1108 QCOMPARE(aspect->events[1].nodeId, newParent->id());
1109 QCOMPARE(aspect->events[2].type, TestAspect::Creation);
1110 QCOMPARE(aspect->events[2].nodeId, child->id());
1111 }
1112
1113 // WHEN -> Changing parent to node with existing backend
1114 aspect->clearNodes();
1115 child->setParent(child2);
1116
1117 // THEN
1118 QCOMPARE(spy.events.size(), 2);
1119
1120 // 1 node removed change, 1 node added change
1121 {
1122 QVERIFY(spy.events.first().wasLocked());
1123 const Qt3DCore::QPropertyNodeRemovedChangePtr event = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeRemovedChange>();
1124 QCOMPARE(event->type(), Qt3DCore::PropertyValueRemoved);
1125 QCOMPARE(event->subjectId(), newParent->id());
1126 QCOMPARE(event->removedNodeId(), child->id());
1127 QCOMPARE(event->metaObject(), child->metaObject());
1128
1129 const Qt3DCore::QPropertyNodeAddedChangePtr event2 = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeAddedChange>();
1130 QCOMPARE(event2->type(), Qt3DCore::PropertyValueAdded);
1131 QCOMPARE(event2->addedNodeId(), child->id());
1132 QCOMPARE(event2->metaObject(), child->metaObject());
1133 QCOMPARE(event2->subjectId(), child2->id());
1134 }
1135
1136 QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == true);
1137 QCOMPARE(root->childNodes().size(), 2);
1138 QCOMPARE(child2->childNodes().size(), 1);
1139 QCOMPARE(newParent->childNodes().size(), 0);
1140
1141 // WHEN -> Changing to parent which has no backend
1142 QScopedPointer<Qt3DCore::QNode> newParent2(new MyQNode(root.data()));
1143 child->setParent(newParent2.data());
1144
1145 // THEN
1146 QVERIFY(Qt3DCore::QNodePrivate::get(newParent2.data())->m_hasBackendNode == false);
1147 QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == false);
1148 QVERIFY(Qt3DCore::QNodePrivate::get(newParent2.data())->scene() != nullptr);
1149 QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() != nullptr);
1150
1151 // WHEN
1152 QCoreApplication::processEvents();
1153 engine.processFrame();
1154
1155 // THEN
1156 QCOMPARE(spy.events.size(), 2);
1157 // 1 node removed change
1158 // 1 node added to children change
1159 {
1160 // CHECK event 1 is a Node Removed event
1161 QVERIFY(spy.events.first().wasLocked());
1162 const Qt3DCore::QPropertyNodeRemovedChangePtr event = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeRemovedChange>();
1163 QCOMPARE(event->type(), Qt3DCore::PropertyValueRemoved);
1164 QCOMPARE(event->subjectId(), child2->id());
1165 QCOMPARE(event->removedNodeId(), child->id());
1166 QCOMPARE(event->metaObject(), child->metaObject());
1167
1168 const Qt3DCore::QPropertyNodeAddedChangePtr event2 = spy.events.takeFirst().change().staticCast<Qt3DCore::QPropertyNodeAddedChange>();
1169 QCOMPARE(event2->type(), Qt3DCore::PropertyValueAdded);
1170 QCOMPARE(event2->addedNodeId(), newParent2->id());
1171 QCOMPARE(event2->metaObject(), newParent2->metaObject());
1172 QCOMPARE(event2->subjectId(), root->id());
1173
1174 // child backend is destroyed because it was reparented to node without backend (newParent)
1175 QCOMPARE(aspect->events[0].type, TestAspect::Destruction);
1176 QCOMPARE(aspect->events[0].nodeId, child->id());
1177 }
1178}
1179
1180//Test creation changes happen for an entire subtree at once, starting at the top
1181// so that parents are always created before their children. Even if the front-end
1182// nodes are constructed in a different order.
1183void tst_Nodes::checkBackendNodesCreatedFromTopDown()
1184{
1185 // GIVEN
1186 ObserverSpy spy;
1187 Qt3DCore::QAspectEngine engine;
1188 auto aspect = new TestAspect;
1189 engine.registerAspect(aspect);
1190 QScopedPointer<MyQEntity> root(new MyQEntity());
1191
1192 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1193 QScopedPointer<Qt3DCore::QNode> parentWithBackend(new MyQNode(root.data()));
1194
1195 // create parent backend node
1196 QCoreApplication::processEvents();
1197 QVERIFY(Qt3DCore::QNodePrivate::get(parentWithBackend.get())->m_hasBackendNode);
1198
1199 // WHEN -> creating 3 nodes and setting one as a property on the other
1200 // child2 is set as property on child1, but is created AFTER child1
1201 spy.events.clear();
1202 MyQNode *dummyParent(new MyQNode(parentWithBackend.data()));
1203 MyQNode *child1(new MyQNode(parentWithBackend.data()));
1204 MyQNode *child2(new MyQNode(dummyParent));
1205 child1->setNodeProperty(child2);
1206
1207 // THEN - we should have no events because the new nodes have no backend yet
1208 QCOMPARE(spy.events.count(), 0);
1209
1210 // WHEN - create the backend nodes
1211 QCoreApplication::processEvents();
1212
1213 // THEN
1214 QVERIFY(dummyParent->parent() == parentWithBackend.data());
1215 QVERIFY(child1->parent() == parentWithBackend.data());
1216 QVERIFY(child2->parent() == dummyParent);
1217
1218 // THEN
1219 QCOMPARE(spy.events.size(), 2);
1220 // 1 node added to children change (dummyParent to parent)
1221 // 1 node added to children change (child1 to parent)
1222
1223 {
1224 QVERIFY(Qt3DCore::QNodePrivate::get(dummyParent)->m_hasBackendNode);
1225 QVERIFY(Qt3DCore::QNodePrivate::get(child1)->m_hasBackendNode);
1226 QVERIFY(Qt3DCore::QNodePrivate::get(child2)->m_hasBackendNode);
1227
1228 // 1st event: dummyParent added to parent
1229 const auto event = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1230 QCOMPARE(event->type(), Qt3DCore::PropertyValueAdded);
1231 QCOMPARE(event->addedNodeId(), dummyParent->id());
1232 QCOMPARE(event->subjectId(), parentWithBackend->id());
1233
1234 // 2nd event: child 1 added to parent
1235 const auto event2 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1236 QCOMPARE(event2->type(), Qt3DCore::PropertyValueAdded);
1237 QCOMPARE(event2->addedNodeId(), child1->id());
1238 QCOMPARE(event2->subjectId(), parentWithBackend->id());
1239
1240 verifyChildrenCreatedBeforeParents(root: root.get(), aspect);
1241 }
1242
1243}
1244
1245void tst_Nodes::checkBackendNodesCreatedFromTopDownWithReparenting()
1246{
1247 // GIVEN
1248 ObserverSpy spy;
1249 Qt3DCore::QAspectEngine engine;
1250 auto aspect = new TestAspect;
1251 engine.registerAspect(aspect);
1252 QScopedPointer<MyQEntity> root(new MyQEntity());
1253
1254 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1255 QScopedPointer<Qt3DCore::QNode> parentWithBackend(new MyQNode(root.data()));
1256
1257 // create parent backend node
1258 QCoreApplication::processEvents();
1259 QCoreApplication::processEvents();
1260 QVERIFY(Qt3DCore::QNodePrivate::get(parentWithBackend.get())->m_hasBackendNode);
1261
1262 // WHEN -> creating a node with a parent with backend, then reparenting it to another
1263 // parent with backend created after it.
1264 spy.events.clear();
1265 auto node1 = new MyQNode(parentWithBackend.data());
1266 auto parent1 = new MyQNode(parentWithBackend.data());
1267 node1->setParent(parent1);
1268
1269 QCoreApplication::processEvents();
1270
1271 // THEN
1272 QVERIFY(node1->parent() == parent1);
1273 QVERIFY(parent1->parent() == parentWithBackend.data());
1274 verifyChildrenCreatedBeforeParents(root: root.get(), aspect);
1275
1276
1277 // WHEN -> creating 2 nodes with no parent and reparenting the first to the second
1278 auto node2 = new MyQNode(nullptr);
1279 auto parent2 = new MyQNode(nullptr);
1280 node2->setParent(parent2);
1281 parent2->setParent(parentWithBackend.get());
1282
1283 QCoreApplication::processEvents();
1284
1285 // THEN
1286 QVERIFY(node2->parent() == parent2);
1287 QVERIFY(parent2->parent() == parentWithBackend.data());
1288 verifyChildrenCreatedBeforeParents(root: root.get(), aspect);
1289}
1290
1291void tst_Nodes::removingSingleChildNodeFromNode()
1292{
1293 // GIVEN
1294 ObserverSpy spy;
1295 Qt3DCore::QAspectEngine engine;
1296 QScopedPointer<MyQEntity> root(new MyQEntity());
1297 QScopedPointer<Qt3DCore::QNode> child(new MyQNode());
1298
1299 // WHEN
1300 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1301 root->setSimulateBackendCreated(true);
1302 child->setParent(root.data());
1303
1304 // Clear any creation event
1305 spy.events.clear();
1306
1307 // THEN
1308 QVERIFY(root->children().count() == 1);
1309 QVERIFY(child->parentNode() == root.data());
1310
1311 // WHEN
1312 child->setParent(Q_NODE_NULLPTR);
1313
1314 // THEN
1315 QVERIFY(child->parent() == nullptr);
1316 QVERIFY(root->children().count() == 0);
1317
1318 QCOMPARE(spy.events.size(), 1);
1319
1320 QVERIFY(spy.events.first().wasLocked());
1321 const Qt3DCore::QPropertyNodeRemovedChangePtr removalEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeRemovedChange>();
1322 QCOMPARE(removalEvent->subjectId(), root->id());
1323 QCOMPARE(removalEvent->removedNodeId(), child->id());
1324 QCOMPARE(removalEvent->metaObject(), child->metaObject());
1325}
1326
1327void tst_Nodes::checkAllBackendCreationDoneInSingleFrame()
1328{
1329 // GIVEN
1330 ObserverSpy spy;
1331 Qt3DCore::QAspectEngine engine;
1332 engine.setRunMode(Qt3DCore::QAspectEngine::Manual);
1333 auto aspect = new TestAspect;
1334 engine.registerAspect(aspect);
1335
1336 QScopedPointer<MyQEntity> root(new MyQEntity());
1337 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1338
1339 QCoreApplication::processEvents();
1340
1341 // THEN
1342 // Due to the way we create root, it has a backend
1343 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode == true);
1344 QCOMPARE(aspect->events.count(), 1);
1345 QCOMPARE(aspect->events[0].type, TestAspect::Creation);
1346 QCOMPARE(aspect->events[0].nodeId, root->id());
1347
1348 // WHEN -> create 2 children:
1349 // 1. a child with parent with backend node
1350 // 2. a child with no parent that is then reparented to a parent with backend node
1351 aspect->clearNodes();
1352 auto child1 = new MyQNode(root.data());
1353 auto child2 = new MyQNode;
1354 child2->setParent(root.data());
1355
1356 // THEN - reparented child should have a backend node, but other child should
1357 // still be waiting
1358 QCOMPARE(child1->parent(), root.data());
1359 QCOMPARE(child2->parent(), root.data());
1360 QVERIFY(Qt3DCore::QNodePrivate::get(child1)->m_hasBackendNode == false);
1361 QVERIFY(Qt3DCore::QNodePrivate::get(child2)->m_hasBackendNode == true);
1362
1363 // WHEN
1364 QCoreApplication::processEvents();
1365 engine.processFrame();
1366
1367 // THEN - both children have their backend nodes actually created.
1368 QCOMPARE(aspect->events.count(), 2);
1369 QCOMPARE(aspect->events[0].type, TestAspect::Creation);
1370 QCOMPARE(aspect->events[0].nodeId, child2->id());
1371 QCOMPARE(aspect->events[1].type, TestAspect::Creation);
1372 QCOMPARE(aspect->events[1].nodeId, child1->id());
1373}
1374
1375void tst_Nodes::removingMultipleChildNodesFromNode()
1376{
1377 // GIVEN
1378 ObserverSpy spy;
1379 Qt3DCore::QAspectEngine engine;
1380 QScopedPointer<MyQEntity> root(new MyQEntity());
1381
1382 // WHEN
1383 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1384
1385 // THEN
1386 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1387
1388 // WHEN
1389 Qt3DCore::QNodeIdVector childIds(10);
1390 for (int i = 0; i < 10; i++) {
1391 auto child = new MyQNode(root.data());
1392 childIds[i] = child->id();
1393 }
1394
1395 QCoreApplication::processEvents();
1396
1397 // THEN
1398 QCOMPARE(root->children().count(), 10);
1399 QCOMPARE(spy.events.size(), 10);
1400
1401 // WHEN
1402 spy.events.clear();
1403 qDeleteAll(c: root->children());
1404
1405 // THEN
1406 QVERIFY(root->children().count() == 0);
1407 QCOMPARE(spy.events.size(), 10);
1408 int i = 0;
1409 for (const ObserverSpy::ChangeRecord &r : qAsConst(t&: spy.events)) {
1410 QVERIFY(r.wasLocked());
1411 const Qt3DCore::QNodeId childId = childIds.at(i);
1412 Qt3DCore::QPropertyNodeRemovedChangePtr additionEvent = r.change().dynamicCast<Qt3DCore::QPropertyNodeRemovedChange>();
1413 QCOMPARE(additionEvent->subjectId(), root->id());
1414 QCOMPARE(additionEvent->removedNodeId(), childId);
1415 QCOMPARE(additionEvent->metaObject(), &MyQNode::staticMetaObject);
1416 ++i;
1417 }
1418}
1419
1420void tst_Nodes::appendingChildEntitiesToNode()
1421{
1422 // GIVEN
1423 QScopedPointer<MyQNode> root(new MyQNode());
1424
1425 // WHEN
1426 Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(root.data());
1427 QCoreApplication::processEvents();
1428
1429 // THEN
1430 QVERIFY(root->children().first() == childEntity);
1431 QVERIFY(childEntity->parentEntity() == nullptr);
1432 QVERIFY(childEntity->parentNode() == root.data());
1433}
1434
1435void tst_Nodes::removingChildEntitiesFromNode()
1436{
1437 // GIVEN
1438 QScopedPointer<MyQNode> root(new MyQNode());
1439
1440 // WHEN
1441 Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(root.data());
1442 QCoreApplication::processEvents();
1443
1444 // THEN
1445 QVERIFY(root->children().first() == childEntity);
1446 QVERIFY(childEntity->parentEntity() == nullptr);
1447 QVERIFY(childEntity->parentNode() == root.data());
1448
1449 // WHEN
1450 childEntity->setParent(Q_NODE_NULLPTR);
1451
1452 // THEN
1453 QVERIFY(root->children().isEmpty());
1454 QVERIFY(childEntity->parentNode() == nullptr);
1455 QVERIFY(childEntity->parent() == nullptr);
1456}
1457
1458void tst_Nodes::checkConstructionSetParentMix()
1459{
1460 // GIVEN
1461 ObserverSpy spy;
1462 Qt3DCore::QAspectEngine engine;
1463 auto aspect = new TestAspect;
1464 engine.registerAspect(aspect);
1465 QScopedPointer<MyQEntity> root(new MyQEntity());
1466
1467 // WHEN
1468 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1469 root->setSimulateBackendCreated(true);
1470
1471 // THEN
1472 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1473
1474 // WHEN
1475 Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
1476
1477 for (int i = 0; i < 100; ++i) {
1478 Qt3DCore::QEntity *child = new Qt3DCore::QEntity();
1479 child->setParent(subTreeRoot);
1480 }
1481
1482 // THEN
1483 QCoreApplication::processEvents();
1484 QCOMPARE(root->children().count(), 1);
1485 QCOMPARE(subTreeRoot->children().count(), 100);
1486 QCOMPARE(spy.events.size(), 1); // 1 child added (subTree to root)
1487
1488 // Ensure first event is subTreeRoot
1489 verifyChildrenCreatedBeforeParents(root: root.data(), aspect);
1490
1491 const Qt3DCore::QPropertyNodeAddedChangePtr lastEvent = spy.events.takeLast().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1492 QVERIFY(!lastEvent.isNull());
1493 QCOMPARE(lastEvent->subjectId(), root->id());
1494 QCOMPARE(lastEvent->propertyName(), "children");
1495 QCOMPARE(lastEvent->addedNodeId(), subTreeRoot->id());
1496}
1497
1498void tst_Nodes::checkParentingQEntityToQNode()
1499{
1500 // GIVEN
1501 ObserverSpy spy;
1502 Qt3DCore::QAspectEngine engine;
1503 QScopedPointer<MyQEntity> root(new MyQEntity());
1504
1505 // WHEN
1506 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1507 root->setSimulateBackendCreated(true);
1508
1509 // THEN
1510 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1511
1512 // WHEN
1513 auto subTreeRoot = new Qt3DCore::QEntity(root.data());
1514 auto childEntity = new Qt3DCore::QEntity(subTreeRoot);
1515 auto childNode = new Qt3DCore::QNode(subTreeRoot);
1516
1517 // THEN
1518 QCoreApplication::processEvents();
1519 QCOMPARE(spy.events.size(), 1);
1520
1521 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode);
1522 QVERIFY(Qt3DCore::QNodePrivate::get(subTreeRoot)->m_hasBackendNode);
1523 QVERIFY(Qt3DCore::QNodePrivate::get(childEntity)->m_hasBackendNode);
1524 QVERIFY(Qt3DCore::QNodePrivate::get(childNode)->m_hasBackendNode);
1525
1526 // WHEN we reparent the childEntity to the childNode (QNode)
1527 spy.events.clear();
1528 childEntity->setParent(childNode);
1529 QCoreApplication::processEvents();
1530
1531 // THEN we should get
1532 // - one child removed change for childEntity->subTreeRoot,
1533 // - one child added change for childEntity->childNode,
1534 QCOMPARE(spy.events.size(), 2);
1535
1536 const auto removedEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeRemovedChange>();
1537 QVERIFY(!removedEvent.isNull());
1538 QCOMPARE(removedEvent->subjectId(), subTreeRoot->id());
1539
1540 const auto addedEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1541 QVERIFY(!addedEvent.isNull());
1542 QCOMPARE(addedEvent->subjectId(), childNode->id());
1543
1544 // The arbiter's dirtyNodes should contain the childEntity and
1545 // - the dirty node's parent should be childNode
1546 // - the dirty node's parent entity should be the subTreeRoot
1547 QCOMPARE(spy.dirtyNodes.size(), 1);
1548 const auto dirtyEntity = qobject_cast<Qt3DCore::QEntity*>(object: spy.dirtyNodes.takeFirst());
1549 QVERIFY(dirtyEntity);
1550 QCOMPARE(dirtyEntity, childEntity);
1551 QCOMPARE(dirtyEntity->parent(), childNode);
1552 QCOMPARE(dirtyEntity->parentEntity(), subTreeRoot);
1553}
1554
1555void tst_Nodes::checkConstructionWithParent()
1556{
1557 // GIVEN
1558 ObserverSpy spy;
1559 Qt3DCore::QAspectEngine engine;
1560 QScopedPointer<MyQEntity> root(new MyQEntity());
1561
1562 // WHEN
1563 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1564 root->setSimulateBackendCreated(true);
1565
1566 // THEN
1567 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1568
1569 // WHEN we create a child and then set it as a Node* property
1570 auto *node = new MyQNode(root.data());
1571 root->setNodeProperty(node);
1572
1573 // THEN we should get one child added change
1574 QCoreApplication::processEvents();
1575 QCOMPARE(root->children().count(), 1);
1576 QCOMPARE(spy.events.size(), 1); // 1 child added change
1577
1578 const auto newChildEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1579 QVERIFY(!newChildEvent.isNull());
1580 QCOMPARE(newChildEvent->subjectId(), root->id());
1581 QCOMPARE(newChildEvent->propertyName(), "children");
1582 QCOMPARE(newChildEvent->addedNodeId(), node->id());
1583
1584 // Ensure the parent node is dirty
1585 QCOMPARE(spy.events.size(), 0);
1586 QCOMPARE(spy.dirtyNodes.size(), 1);
1587 QCOMPARE(spy.dirtyNodes.front(), root.data());
1588}
1589
1590void tst_Nodes::checkConstructionWithNonRootParent()
1591{
1592 // GIVEN
1593 ObserverSpy spy;
1594 Qt3DCore::QAspectEngine engine;
1595 QScopedPointer<MyQEntity> root(new MyQEntity());
1596
1597 // WHEN
1598 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1599 root->setSimulateBackendCreated(true);
1600 QScopedPointer<MyQNode> parent(new MyQNode(root.data()));
1601
1602 // THEN
1603 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1604 QVERIFY(Qt3DCore::QNodePrivate::get(parent.data())->scene() != nullptr);
1605
1606 // WHEN we create a child and then set it as a Node* property
1607 auto *child = new MyQNode(parent.data());
1608 root->setNodeProperty(child);
1609
1610 // THEN we should get
1611 // - one creation change for parent,
1612 // - one creation change for child,
1613 // - one child added change for root->parent,
1614 // - and one property change event,
1615 // in that order.
1616 QCoreApplication::processEvents();
1617 QCOMPARE(root->children().count(), 1);
1618 QCOMPARE(parent->children().count(), 1);
1619
1620 QCOMPARE(spy.events.size(), 1); // 1 child added changes
1621
1622 const auto parentNewChildEvent = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1623 QVERIFY(!parentNewChildEvent.isNull());
1624 QCOMPARE(parentNewChildEvent->subjectId(), root->id());
1625 QCOMPARE(parentNewChildEvent->propertyName(), "children");
1626 QCOMPARE(parentNewChildEvent->addedNodeId(), parent->id());
1627
1628 // Ensure second and last event is property set change
1629 QCOMPARE(spy.dirtyNodes.size(), 1);
1630 QCOMPARE(spy.dirtyNodes.front(), root.data());
1631}
1632
1633void tst_Nodes::checkConstructionAsListElement()
1634{
1635 // GIVEN
1636 ObserverSpy spy;
1637 Qt3DCore::QAspectEngine engine;
1638 QScopedPointer<MyQEntity> root(new MyQEntity());
1639
1640 // WHEN
1641 root->setArbiterAndEngine(arbiter: &spy, engine: &engine);
1642 root->setSimulateBackendCreated(true);
1643
1644 // THEN
1645 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1646
1647 // WHEN we create a child and then set it as a Node* property
1648 auto *node = new MyQNode(root.data());
1649 root->addAttribute(attribute: node);
1650
1651 // THEN we should get one creation change, one child added change
1652 // and one property change event, in that order.
1653 QCoreApplication::processEvents();
1654
1655 QCOMPARE(root->children().count(), 1);
1656 QCOMPARE(spy.dirtyNodes.size(), 1); // 1 property change
1657 QCOMPARE(spy.dirtySubNodes.size(), 1); // 1 child added change
1658}
1659
1660void tst_Nodes::checkSceneIsSetOnConstructionWithParent()
1661{
1662 // GIVEN
1663 ObserverSpy spy;
1664 Qt3DCore::QScene scene;
1665 QScopedPointer<MyQNode> root(new MyQNode());
1666
1667 // WHEN
1668 root->setArbiterAndScene(arbiter: &spy, scene: &scene);
1669 root->setSimulateBackendCreated(true);
1670
1671 // THEN
1672 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1673
1674 // WHEN
1675 Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
1676
1677 QVector<Qt3DCore::QEntity *> children;
1678 QVector<Qt3DCore::QComponent *> cmps;
1679 children.reserve(size: 5);
1680 cmps.reserve(size: 5);
1681 for (int i = 0; i < 5; ++i) {
1682 const auto cmp = new MyQComponent(subTreeRoot);
1683 cmps << cmp;
1684 children << new Qt3DCore::QEntity(cmp);
1685
1686 // When cmp is full created, it will also send the creation change for its child entity
1687 }
1688 QCoreApplication::processEvents();
1689 QCOMPARE(root->children().count(), 1);
1690 QCOMPARE(subTreeRoot->children().count(), 5);
1691 QCOMPARE(spy.events.size(), 1); // 1 child added (subTree to root)
1692
1693 spy.events.clear();
1694
1695 // Add component in a separate loop to ensure components are added after
1696 // entities have been fully created
1697 for (int i = 0; i < 5; ++i) {
1698 children.at(i)->addComponent(comp: cmps.at(i));
1699 }
1700
1701 // THEN
1702 QCOMPARE(spy.events.size(), 0);
1703 QCOMPARE(spy.dirtySubNodes.size(), 5); // 5 entities changed
1704}
1705
1706void tst_Nodes::checkSubNodePostConstructIsCalledWhenReferincingNodeProperty()
1707{
1708 // GIVEN
1709 ObserverSpy spy;
1710 Qt3DCore::QAspectEngine engine;
1711 Qt3DCore::QScene scene(&engine);
1712 QScopedPointer<MyQNode> root(new MyQNode());
1713
1714 // WHEN
1715 root->setArbiterAndScene(arbiter: &spy, scene: &scene);
1716 root->setSimulateBackendCreated(true);
1717
1718 // THEN
1719 QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
1720
1721 // WHEN
1722 Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
1723 QCoreApplication::processEvents();
1724
1725 // THEN
1726 QVERIFY(Qt3DCore::QNodePrivate::get(subTreeRoot)->m_hasBackendNode);
1727
1728 // WHEN
1729 MyFakeMaterial *material = new MyFakeMaterial(subTreeRoot);
1730 subTreeRoot->addComponent(comp: material);
1731
1732 // THEN
1733 QVERIFY(Qt3DCore::QNodePrivate::get(material)->m_hasBackendNode);
1734 QVERIFY(Qt3DCore::QNodePrivate::get(material->m_effect)->m_hasBackendNode);
1735 QVERIFY(Qt3DCore::QNodePrivate::get(material->m_technique)->m_hasBackendNode);
1736 QVERIFY(Qt3DCore::QNodePrivate::get(material->m_renderPass)->m_hasBackendNode);
1737
1738 // WHEN
1739 MyQNode *fakeRenderState = new MyQNode(material);
1740 Qt3DCore::QNodePrivate *dPtr = Qt3DCore::QNodePrivate::get(q: fakeRenderState);
1741
1742 // THEN
1743 QVERIFY(!dPtr->m_hasBackendNode);
1744
1745 // WHEN
1746 material->m_renderPass->addAttribute(attribute: fakeRenderState);
1747
1748 // THEN
1749 QVERIFY(dPtr->m_hasBackendNode);
1750}
1751
1752void tst_Nodes::appendingParentlessComponentToEntityWithoutScene()
1753{
1754 // GIVEN
1755 ObserverSpy entitySpy;
1756 ObserverSpy componentSpy;
1757 {
1758 QScopedPointer<MyQEntity> entity(new MyQEntity());
1759 entity->setArbiterAndScene(arbiter: &entitySpy);
1760 entity->setSimulateBackendCreated(true);
1761
1762 MyQComponent *comp = new MyQComponent();
1763 comp->setArbiter(&componentSpy);
1764
1765 // THEN
1766 QVERIFY(entity->parentNode() == nullptr);
1767 QVERIFY(entity->children().count() == 0);
1768 QVERIFY(entity->components().empty());
1769 QVERIFY(comp->parentNode() == nullptr);
1770
1771 // WHEN
1772 entity->addComponent(comp);
1773
1774 // THEN
1775 QVERIFY(entity->components().count() == 1);
1776 QVERIFY(entity->components().first() == comp);
1777 QVERIFY(comp->parentNode() == entity.data());
1778 QCOMPARE(entitySpy.events.size(), 0);
1779 QCOMPARE(entitySpy.dirtyNodes.size(), 1);
1780 QCOMPARE(entitySpy.dirtySubNodes.size(), 1);
1781
1782 const auto event = entitySpy.dirtySubNodes.first();
1783 QCOMPARE(event.change, Qt3DCore::ComponentAdded);
1784 QCOMPARE(event.node, entity.data());
1785 QCOMPARE(event.subNode, comp);
1786 QCOMPARE(event.property, nullptr);
1787
1788 // Note: since QEntity has no scene in this test case, we only have the
1789 // ComponentAdded event In theory we should also get the NodeCreated event
1790 // when setting the parent but that doesn't happen since no scene is
1791 // actually set on the entity and that QNodePrivate::_q_addChild will
1792 // return early in such a case.
1793 }
1794}
1795
1796void tst_Nodes::appendingParentlessComponentToNonRootEntity()
1797{
1798 // GIVEN
1799 ObserverSpy eventSpy;
1800 Qt3DCore::QAspectEngine engine;
1801
1802 {
1803 QScopedPointer<MyQEntity> root(new MyQEntity());
1804 root->setArbiterAndEngine(arbiter: &eventSpy, engine: &engine);
1805 root->setSimulateBackendCreated(true);
1806
1807 QCoreApplication::processEvents();
1808
1809 QScopedPointer<MyQEntity> entity(new MyQEntity(root.data()));
1810 MyQComponent *comp = new MyQComponent();
1811
1812 // THEN
1813 QVERIFY(root->parentNode() == nullptr);
1814 QVERIFY(root->children().count() == 1);
1815 QVERIFY(root->components().empty());
1816 QVERIFY(entity->parentNode() == root.data());
1817 QVERIFY(entity->children().count() == 0);
1818 QVERIFY(entity->components().empty());
1819 QVERIFY(comp->parentNode() == nullptr);
1820
1821 // WHEN
1822 entity->addComponent(comp);
1823 QCoreApplication::processEvents();
1824
1825 // THEN
1826 QVERIFY(entity->components().count() == 1);
1827 QVERIFY(entity->components().first() == comp);
1828 QVERIFY(comp->parentNode() == entity.data());
1829
1830 QCOMPARE(eventSpy.events.size(), 1);
1831 // - entity added as child to root
1832 QVERIFY(eventSpy.events.first().wasLocked());
1833
1834 QVERIFY(Qt3DCore::QNodePrivate::get(entity.data())->m_hasBackendNode);
1835 QVERIFY(Qt3DCore::QNodePrivate::get(comp)->m_hasBackendNode);
1836
1837 {
1838 const auto event = eventSpy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1839 QVERIFY(!event.isNull());
1840 QCOMPARE(event->type(), Qt3DCore::PropertyValueAdded);
1841 QCOMPARE(event->subjectId(), root->id());
1842 QCOMPARE(event->propertyName(), QByteArrayLiteral("children"));
1843 QCOMPARE(event->addedNodeId(), entity->id());
1844 }
1845 }
1846}
1847
1848void tst_Nodes::appendingParentlessComponentToEntityWithScene()
1849{
1850 // GIVEN
1851 ObserverSpy eventSpy;
1852 Qt3DCore::QAspectEngine engine;
1853
1854 {
1855 QScopedPointer<MyQEntity> entity(new MyQEntity());
1856 entity->setArbiterAndEngine(arbiter: &eventSpy, engine: &engine);
1857 entity->setSimulateBackendCreated(true);
1858
1859 QCoreApplication::processEvents();
1860
1861 MyQComponent *comp = new MyQComponent();
1862
1863 // THEN
1864 QVERIFY(entity->parentNode() == nullptr);
1865 QVERIFY(entity->children().count() == 0);
1866 QVERIFY(entity->components().empty());
1867 QVERIFY(comp->parentNode() == nullptr);
1868
1869 // WHEN
1870 entity->addComponent(comp);
1871 QCoreApplication::processEvents();
1872
1873 // THEN
1874 QVERIFY(entity->components().count() == 1);
1875 QVERIFY(entity->components().first() == comp);
1876 QVERIFY(comp->parentNode() == entity.data());
1877
1878 QCOMPARE(eventSpy.events.size(), 1);
1879 // - child added
1880 QVERIFY(eventSpy.events.first().wasLocked());
1881
1882 {
1883 const auto event = eventSpy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
1884 QVERIFY(!event.isNull());
1885 QCOMPARE(event->type(), Qt3DCore::PropertyValueAdded);
1886 QCOMPARE(event->subjectId(), entity->id());
1887 QCOMPARE(event->propertyName(), QByteArrayLiteral("children"));
1888 QCOMPARE(event->addedNodeId(), comp->id());
1889 }
1890 }
1891}
1892
1893
1894void tst_Nodes::appendingComponentToEntity()
1895{
1896 // GIVEN
1897 ObserverSpy entitySpy;
1898 ObserverSpy componentSpy;
1899 {
1900 QScopedPointer<MyQEntity> entity(new MyQEntity());
1901 entity->setArbiterAndScene(arbiter: &entitySpy);
1902 MyQComponent *comp = new MyQComponent(entity.data());
1903 comp->setArbiter(&componentSpy);
1904 QCoreApplication::processEvents();
1905
1906 // THEN
1907 QVERIFY(entity->parentNode() == nullptr);
1908 QVERIFY(entity->children().count() == 1);
1909 QVERIFY(entity->components().empty());
1910 QVERIFY(comp->parentNode() == entity.data());
1911
1912 // WHEN
1913 entity->addComponent(comp);
1914
1915 // THEN
1916 QVERIFY(entity->components().count() == 1);
1917 QVERIFY(entity->components().first() == comp);
1918 QVERIFY(comp->parentNode() == entity.data());
1919 QCOMPARE(entitySpy.events.size(), 0);
1920 QCOMPARE(entitySpy.dirtyNodes.size(), 1);
1921 QCOMPARE(entitySpy.dirtySubNodes.size(), 1);
1922 QCOMPARE(entitySpy.dirtyNodes.first(), entity.data());
1923 const auto event = entitySpy.dirtySubNodes.takeFirst();
1924 QCOMPARE(event.node, entity.data());
1925 QCOMPARE(event.subNode, comp);
1926 QCOMPARE(event.change, Qt3DCore::ComponentAdded);
1927 QCOMPARE(event.property, nullptr);
1928 }
1929}
1930
1931void tst_Nodes::removingComponentFromEntity()
1932{
1933 // GIVEN
1934 ObserverSpy entitySpy;
1935 ObserverSpy componentSpy;
1936 {
1937 QScopedPointer<MyQEntity> entity(new MyQEntity());
1938 entity->setArbiterAndScene(arbiter: &entitySpy);
1939 MyQComponent *comp = new MyQComponent();
1940 comp->setArbiter(&componentSpy);
1941
1942 // WHEN
1943 entity->addComponent(comp);
1944
1945 // THEN
1946 QVERIFY(entity->components().count() == 1);
1947 QCOMPARE(entity->children().count(), 1);
1948 QVERIFY(comp->parent() == entity.data());
1949
1950 // WHEN
1951 entitySpy.events.clear();
1952 entitySpy.dirtyNodes.clear();
1953 entitySpy.dirtySubNodes.clear();
1954 componentSpy.events.clear();
1955 entity->removeComponent(comp);
1956
1957 // THEN
1958 QVERIFY(entity->components().count() == 0);
1959 QVERIFY(comp->parent() == entity.data());
1960 QVERIFY(entity->children().count() == 1);
1961 QCOMPARE(entitySpy.events.size(), 0);
1962 QCOMPARE(entitySpy.dirtyNodes.size(), 1);
1963 QCOMPARE(entitySpy.dirtySubNodes.size(), 1);
1964 QCOMPARE(componentSpy.events.size(), 0);
1965 {
1966 const auto event = entitySpy.dirtySubNodes.takeFirst();
1967 qDebug() << event.change;
1968 QCOMPARE(event.change, Qt3DCore::ComponentRemoved);
1969 QCOMPARE(event.node, entity.data());
1970 QCOMPARE(event.subNode, comp);
1971 QCOMPARE(event.property, nullptr);
1972 }
1973 }
1974}
1975
1976void tst_Nodes::changeCustomProperty()
1977{
1978 // GIVEN
1979 ObserverSpy spy;
1980 QScopedPointer<MyQNode> node(new MyQNode());
1981 node->setArbiterAndScene(arbiter: &spy);
1982 // WHEN
1983 node->setCustomProperty(QStringLiteral("foo"));
1984 // THEN
1985 QCOMPARE(spy.dirtyNodes.size(), 1);
1986 QCOMPARE(spy.dirtyNodes.front(), node.data());
1987}
1988
1989void tst_Nodes::checkDestruction()
1990{
1991 // GIVEN
1992 QScopedPointer<MyQNode> root(new MyQNode());
1993 Qt3DCore::QEntity *entity = new Qt3DCore::QEntity(root.data());
1994
1995 QCoreApplication::processEvents();
1996
1997 MyQComponent *comp1 = new MyQComponent();
1998 MyQComponent *comp2 = new MyQComponent();
1999 MyQComponent *comp3 = new MyQComponent();
2000
2001 entity->addComponent(comp: comp1);
2002 entity->addComponent(comp: comp2);
2003 entity->addComponent(comp: comp3);
2004
2005 // THEN
2006 QVERIFY(!root->children().isEmpty());
2007
2008 // WHEN
2009 delete entity;
2010
2011 // THEN
2012 QVERIFY(root->children().isEmpty());
2013}
2014
2015void tst_Nodes::checkDefaultConstruction()
2016{
2017 // GIVEN
2018 Qt3DCore::QNode node;
2019
2020 // THEN
2021 QCOMPARE(node.parentNode(), nullptr);
2022 QCOMPARE(node.isEnabled(), true);
2023 QCOMPARE(node.defaultPropertyTrackingMode(), Qt3DCore::QNode::TrackFinalValues);
2024}
2025
2026void tst_Nodes::checkPropertyChanges()
2027{
2028 // GIVEN
2029 Qt3DCore::QNode parentNode;
2030 Qt3DCore::QNode node;
2031
2032 {
2033 // WHEN
2034 QSignalSpy spy(&node, SIGNAL(parentChanged(QObject *)));
2035 Qt3DCore::QNode *newValue = &parentNode;
2036 node.setParent(newValue);
2037
2038 // THEN
2039 QVERIFY(spy.isValid());
2040 QCOMPARE(node.parentNode(), newValue);
2041 QCOMPARE(spy.count(), 1);
2042
2043 // WHEN
2044 spy.clear();
2045 node.setParent(newValue);
2046
2047 // THEN
2048 QCOMPARE(node.parentNode(), newValue);
2049 QCOMPARE(spy.count(), 0);
2050 }
2051 {
2052 // WHEN
2053 QSignalSpy spy(&node, SIGNAL(enabledChanged(bool)));
2054 const bool newValue = false;
2055 node.setEnabled(newValue);
2056
2057 // THEN
2058 QVERIFY(spy.isValid());
2059 QCOMPARE(node.isEnabled(), newValue);
2060 QCOMPARE(spy.count(), 1);
2061
2062 // WHEN
2063 spy.clear();
2064 node.setEnabled(newValue);
2065
2066 // THEN
2067 QCOMPARE(node.isEnabled(), newValue);
2068 QCOMPARE(spy.count(), 0);
2069 }
2070 {
2071 // WHEN
2072 QSignalSpy spy(&node, SIGNAL(defaultPropertyTrackingModeChanged(PropertyTrackingMode)));
2073 const Qt3DCore::QNode::PropertyTrackingMode newValue = Qt3DCore::QNode::TrackAllValues;
2074 node.setDefaultPropertyTrackingMode(newValue);
2075
2076 // THEN
2077 QVERIFY(spy.isValid());
2078 QCOMPARE(node.defaultPropertyTrackingMode(), newValue);
2079 QCOMPARE(spy.count(), 1);
2080
2081 // WHEN
2082 spy.clear();
2083 node.setDefaultPropertyTrackingMode(newValue);
2084
2085 // THEN
2086 QCOMPARE(node.defaultPropertyTrackingMode(), newValue);
2087 QCOMPARE(spy.count(), 0);
2088 }
2089 {
2090 // WHEN
2091 const QString enabledPropertyName = QStringLiteral("enabled");
2092 node.setDefaultPropertyTrackingMode(Qt3DCore::QNode::DontTrackValues);
2093 node.setPropertyTracking(propertyName: enabledPropertyName, trackMode: Qt3DCore::QNode::TrackAllValues);
2094
2095 // THEN
2096 QCOMPARE(node.propertyTracking(enabledPropertyName), Qt3DCore::QNode::TrackAllValues);
2097
2098 // WHEN
2099 node.clearPropertyTracking(propertyName: enabledPropertyName);
2100
2101 // THEN
2102 QCOMPARE(node.propertyTracking(enabledPropertyName), Qt3DCore::QNode::DontTrackValues);
2103 }
2104}
2105
2106void tst_Nodes::checkCreationData()
2107{
2108 // GIVEN
2109 Qt3DCore::QNode root;
2110 Qt3DCore::QNode node;
2111
2112 node.setParent(&root);
2113 node.setEnabled(true);
2114 const QString enabledPropertyName = QStringLiteral("enabled");
2115 node.setDefaultPropertyTrackingMode(Qt3DCore::QNode::DontTrackValues);
2116 node.setPropertyTracking(propertyName: enabledPropertyName, trackMode: Qt3DCore::QNode::TrackAllValues);
2117
2118 // WHEN
2119 QVector<Qt3DCore::QNodeCreatedChangeBasePtr> creationChanges;
2120
2121 {
2122 Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&node);
2123 creationChanges = creationChangeGenerator.creationChanges();
2124 }
2125
2126 // THEN
2127 {
2128 QCOMPARE(creationChanges.size(), 1);
2129
2130 const auto creationChangeData = qSharedPointerCast<Qt3DCore::QNodeCreatedChangeBase>(src: creationChanges.first());
2131
2132 QCOMPARE(node.id(), creationChangeData->subjectId());
2133 QCOMPARE(node.isEnabled(), true);
2134 QCOMPARE(node.isEnabled(), creationChangeData->isNodeEnabled());
2135 QCOMPARE(node.metaObject(), creationChangeData->metaObject());
2136 }
2137
2138 // WHEN
2139 node.setEnabled(false);
2140
2141 {
2142 Qt3DCore::QNodeCreatedChangeGenerator creationChangeGenerator(&node);
2143 creationChanges = creationChangeGenerator.creationChanges();
2144 }
2145
2146 // THEN
2147 {
2148 QCOMPARE(creationChanges.size(), 1);
2149
2150 const auto creationChangeData = qSharedPointerCast<Qt3DCore::QNodeCreatedChangeBase>(src: creationChanges.first());
2151
2152 QCOMPARE(node.id(), creationChangeData->subjectId());
2153 QCOMPARE(node.isEnabled(), false);
2154 QCOMPARE(node.isEnabled(), creationChangeData->isNodeEnabled());
2155 QCOMPARE(node.metaObject(), creationChangeData->metaObject());
2156 }
2157}
2158
2159void tst_Nodes::checkEnabledUpdate()
2160{
2161 // GIVEN
2162 TestArbiter arbiter;
2163 Qt3DCore::QNode node;
2164 arbiter.setArbiterOnNode(&node);
2165
2166 {
2167 // WHEN
2168 node.setEnabled(false);
2169
2170 // THEN
2171 QCOMPARE(arbiter.events.size(), 0);
2172 QCOMPARE(arbiter.dirtyNodes.size(), 1);
2173 QCOMPARE(arbiter.dirtyNodes.front(), &node);
2174
2175 arbiter.dirtyNodes.clear();
2176 }
2177
2178 {
2179 // WHEN
2180 node.setEnabled(false);
2181
2182 // THEN
2183 QCOMPARE(arbiter.events.size(), 0);
2184 QCOMPARE(arbiter.dirtyNodes.size(), 0);
2185 }
2186
2187}
2188
2189void tst_Nodes::checkPropertyTrackModeUpdate()
2190{
2191 // GIVEN
2192 TestArbiter arbiter;
2193 Qt3DCore::QNode node;
2194 arbiter.setArbiterOnNode(&node);
2195
2196 {
2197 // WHEN
2198 node.setDefaultPropertyTrackingMode(Qt3DCore::QNode::TrackAllValues);
2199 QCoreApplication::processEvents();
2200
2201 // THEN -> this properties is non notifying
2202 QCOMPARE(arbiter.events.size(), 0);
2203 }
2204
2205 {
2206 // WHEN
2207 node.setDefaultPropertyTrackingMode(Qt3DCore::QNode::TrackAllValues);
2208 QCoreApplication::processEvents();
2209
2210 // THEN
2211 QCOMPARE(arbiter.events.size(), 0);
2212 }
2213
2214}
2215
2216void tst_Nodes::checkTrackedPropertyNamesUpdate()
2217{
2218 // GIVEN
2219 TestArbiter arbiter;
2220 Qt3DCore::QNode node;
2221 arbiter.setArbiterOnNode(&node);
2222
2223 {
2224 // WHEN
2225 node.setPropertyTracking(QStringLiteral("883"), trackMode: Qt3DCore::QNode::TrackAllValues);
2226 QCoreApplication::processEvents();
2227
2228 // THEN -> this properties is non notifying
2229 QCOMPARE(arbiter.events.size(), 0);
2230 }
2231
2232 {
2233 // WHEN
2234 node.setPropertyTracking(QStringLiteral("883"), trackMode: Qt3DCore::QNode::DontTrackValues);
2235 QCoreApplication::processEvents();
2236
2237 // THEN
2238 QCOMPARE(arbiter.events.size(), 0);
2239 }
2240
2241}
2242
2243void tst_Nodes::checkNodeRemovedFromDirtyListOnDestruction()
2244{
2245 // GIVEN
2246 TestArbiter arbiter;
2247 Qt3DCore::QScene scene;
2248
2249 {
2250 // GIVEN
2251 QScopedPointer<MyQNode> node(new MyQNode());
2252 node->setArbiterAndScene(arbiter: &arbiter, scene: &scene);
2253
2254 // WHEN
2255 node->setEnabled(false);
2256
2257 // THEN
2258 QCOMPARE(arbiter.events.size(), 0);
2259 QCOMPARE(arbiter.dirtyNodes.size(), 1);
2260 QCOMPARE(arbiter.dirtyNodes.front(), node.data());
2261
2262 // WHEN
2263 // scene should be unset and node removed from arbiter dirtyList
2264 node.reset();
2265
2266 // THEN
2267 QCOMPARE(arbiter.events.size(), 0);
2268 QCOMPARE(arbiter.dirtyNodes.size(), 0);
2269 }
2270 {
2271 // GIVEN
2272 QScopedPointer<MyQNode> node(new MyQNode());
2273 node->setArbiterAndScene(arbiter: &arbiter, scene: &scene);
2274
2275
2276 Qt3DCore::QNode *child = new Qt3DCore::QNode(node.data());
2277 // Wait for deferred initialization of child node
2278 QCoreApplication::processEvents();
2279
2280 // WHEN
2281 child->setEnabled(false);
2282
2283 // THEN
2284 QCOMPARE(arbiter.events.size(), 1); // childAdded
2285 QCOMPARE(arbiter.dirtyNodes.size(), 1);
2286 QCOMPARE(arbiter.dirtyNodes.front(), child);
2287
2288 // WHEN
2289 // scene should be unset and child node removed from arbiter dirtyList
2290 node.reset();
2291
2292 // THEN
2293 QCOMPARE(arbiter.events.size(), 1); // childRemoved (no destroyed change since we had no backend)
2294 QCOMPARE(arbiter.dirtyNodes.size(), 0);
2295 }
2296}
2297
2298void tst_Nodes::checkBookkeepingSingleNode()
2299{
2300 // GIVEN
2301 MyQNode root;
2302
2303 // THEN
2304 QVERIFY(root.nodeProperty() == nullptr);
2305
2306 {
2307 // WHEN
2308 MyQNode node;
2309 root.setNodeProperty(&node);
2310
2311 // THEN
2312 QCOMPARE(root.nodeProperty(), &node);
2313 }
2314 // WHEN -> node destroyed
2315
2316 // THEN
2317 QVERIFY(root.nodeProperty() == nullptr);
2318}
2319
2320
2321void tst_Nodes::checkBookkeeping_QVector_OfNode()
2322{
2323 // GIVEN
2324 MyQNode root;
2325
2326 // THEN
2327 QVERIFY(root.attributes().size() == 0);
2328
2329 {
2330 // WHEN
2331 MyQNode node1;
2332 root.addAttribute(attribute: &node1);
2333
2334 // THEN
2335 QVERIFY(root.attributes().size() == 1);
2336
2337 // WHEN
2338 {
2339 MyQNode node2;
2340 root.addAttribute(attribute: &node2);
2341
2342 // THEN
2343 QVERIFY(root.attributes().size() == 2);
2344 QCOMPARE(root.attributes().first(), &node1);
2345 QCOMPARE(root.attributes().last(), &node2);
2346 }
2347
2348 // THEN
2349 QVERIFY(root.attributes().size() == 1);
2350 QCOMPARE(root.attributes().first(), &node1);
2351 }
2352
2353 // THEN
2354 QVERIFY(root.attributes().size() == 0);
2355}
2356
2357void tst_Nodes::checkBookkeeping_stdvector_OfNode()
2358{
2359 // GIVEN
2360 MyQNodeVec root;
2361
2362 // THEN
2363 QVERIFY(root.attributes().size() == 0U);
2364
2365 {
2366 // WHEN
2367 MyQNode node1;
2368 root.addAttribute(attribute: &node1);
2369
2370 // THEN
2371 QVERIFY(root.attributes().size() == 1U);
2372
2373 // WHEN
2374 {
2375 MyQNode node2;
2376 root.addAttribute(attribute: &node2);
2377
2378 // THEN
2379 QVERIFY(root.attributes().size() == 2U);
2380 QCOMPARE(root.attributes().front(), &node1);
2381 QCOMPARE(root.attributes().back(), &node2);
2382 }
2383
2384 // THEN
2385 QVERIFY(root.attributes().size() == 1U);
2386 QCOMPARE(root.attributes().front(), &node1);
2387 }
2388
2389 // THEN
2390 QVERIFY(root.attributes().size() == 0U);
2391}
2392
2393
2394QTEST_MAIN(tst_Nodes)
2395
2396#include "tst_nodes.moc"
2397

source code of qt3d/tests/auto/core/nodes/tst_nodes.cpp