Revision control
Copy as Markdown
// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtTest/QTest>
#include <Qt3DCore/qnode.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qcomponent.h>
#include <Qt3DCore/qaspectengine.h>
#include <Qt3DCore/qabstractaspect.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qaspectengine_p.h>
#include <Qt3DCore/private/qaspectengine_p.h>
#include <private/qabstractaspect_p.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qcomponent_p.h>
#include <Qt3DCore/private/vector_helper_p.h>
#include <QSet>
#include <QSignalSpy>
#include <testarbiter.h>
#include <vector>
class tst_Nodes : public QObject
{
Q_OBJECT
public:
tst_Nodes() : QObject()
{
qRegisterMetaType<Qt3DCore::QNode*>();
}
~tst_Nodes() {}
private slots:
void defaultNodeConstruction();
void defaultComponentConstruction();
void defaultEntityConstrution();
void appendSingleChildNodeToNodeNoSceneExplicitParenting();
void appendSingleChildNodeToNodeNoSceneImplicitParenting();
void appendMultipleChildNodesToNodeNoScene();
void appendSingleChildNodeToNodeSceneExplicitParenting();
void appendSingleChildNodeToNodeSceneImplicitParenting();
void appendMultipleChildNodesToNodeScene();
void checkParentChangeToNull();
void checkParentChangeToOtherParent();
void checkParentChangeFromExistingBackendParentToNewlyCreatedParent();
void checkBackendNodesCreatedFromTopDown(); //QTBUG-74106
void checkBackendNodesCreatedFromTopDownWithReparenting();
void checkAllBackendCreationDoneInSingleFrame();
void removingSingleChildNodeFromNode();
void removingMultipleChildNodesFromNode();
void appendingChildEntitiesToNode();
void removingChildEntitiesFromNode();
void checkConstructionSetParentMix(); // QTBUG-60612
void checkParentingQEntityToQNode(); // QTBUG-73905
void checkConstructionWithParent();
void checkConstructionWithNonRootParent(); // QTBUG-73986
void checkConstructionAsListElement();
void checkSceneIsSetOnConstructionWithParent(); // QTBUG-69352
void checkSubNodePostConstructIsCalledWhenReferincingNodeProperty(); // QTBUG-79350
void appendingComponentToEntity();
void appendingParentlessComponentToEntityWithoutScene();
void appendingParentlessComponentToEntityWithScene();
void appendingParentlessComponentToNonRootEntity();
void removingComponentFromEntity();
void changeCustomProperty();
void checkDestruction();
void checkDefaultConstruction();
void checkPropertyChanges();
void checkEnabledUpdate();
void checkNodeRemovedFromDirtyListOnDestruction();
void checkNodeDestroyedBeforeBackendCreation();
void checkBookkeepingSingleNode();
void checkBookkeeping_QVector_OfNode();
void checkBookkeeping_stdvector_OfNode();
};
class MyQNode : public Qt3DCore::QNode
{
Q_OBJECT
Q_PROPERTY(QString customProperty READ customProperty WRITE setCustomProperty NOTIFY customPropertyChanged)
Q_PROPERTY(Qt3DCore::QNode *nodeProperty READ nodeProperty WRITE setNodeProperty NOTIFY nodePropertyChanged)
public:
explicit MyQNode(Qt3DCore::QNode *parent = nullptr)
: QNode(parent)
, m_nodeProperty(nullptr)
{}
~MyQNode()
{
}
void setCustomProperty(const QString &s)
{
if (m_customProperty == s)
return;
m_customProperty = s;
emit customPropertyChanged();
}
QString customProperty() const
{
return m_customProperty;
}
void setArbiterAndScene(Qt3DCore::QChangeArbiter *arbiter,
Qt3DCore::QScene *scene = nullptr)
{
Q_ASSERT(arbiter);
if (scene)
scene->setArbiter(arbiter);
Qt3DCore::QNodePrivate::get(this)->setScene(scene);
Qt3DCore::QNodePrivate::get(this)->setArbiter(arbiter);
}
void setSimulateBackendCreated(bool created)
{
Qt3DCore::QNodePrivate::get(this)->m_hasBackendNode = created;
}
Qt3DCore::QNode *nodeProperty() const { return m_nodeProperty; }
const QList<Qt3DCore::QNode *> &attributes() const { return m_attributes; }
public slots:
void setNodeProperty(Qt3DCore::QNode *node)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
if (m_nodeProperty == node)
return;
if (m_nodeProperty)
d->unregisterDestructionHelper(m_nodeProperty);
if (node && !node->parent())
node->setParent(this);
m_nodeProperty = node;
// Ensures proper bookkeeping
if (m_nodeProperty)
d->registerDestructionHelper(m_nodeProperty, &MyQNode::setNodeProperty, m_nodeProperty);
emit nodePropertyChanged(node);
}
void addAttribute(Qt3DCore::QNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
if (!m_attributes.contains(attribute)) {
m_attributes.append(attribute);
// Ensures proper bookkeeping
d->registerDestructionHelper(attribute, &MyQNode::removeAttribute, m_attributes);
// We need to add it as a child of the current node if it has been declared inline
// Or not previously added as a child of the current node so that
// 1) The backend gets notified about it's creation
// 2) When the current node is destroyed, it gets destroyed as well
if (!attribute->parent())
attribute->setParent(this);
d->update();
}
}
void removeAttribute(Qt3DCore::QNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
d->update();
m_attributes.removeOne(attribute);
// Remove bookkeeping connection
d->unregisterDestructionHelper(attribute);
}
signals:
void customPropertyChanged();
void nodePropertyChanged(Qt3DCore::QNode *node);
protected:
QString m_customProperty;
Qt3DCore::QNode *m_nodeProperty;
QList<Qt3DCore::QNode *> m_attributes;
};
class MyQNodeVec : public Qt3DCore::QNode
{
Q_OBJECT
public:
explicit MyQNodeVec(Qt3DCore::QNode *parent = nullptr)
: QNode(parent)
{}
~MyQNodeVec()
{
}
void setArbiterAndScene(Qt3DCore::QChangeArbiter *arbiter,
Qt3DCore::QScene *scene = nullptr)
{
Q_ASSERT(arbiter);
if (scene)
scene->setArbiter(arbiter);
Qt3DCore::QNodePrivate::get(this)->setScene(scene);
Qt3DCore::QNodePrivate::get(this)->setArbiter(arbiter);
}
void setSimulateBackendCreated(bool created)
{
Qt3DCore::QNodePrivate::get(this)->m_hasBackendNode = created;
}
const std::vector<Qt3DCore::QNode *> &attributes() const { return m_attributes; }
public slots:
void addAttribute(Qt3DCore::QNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
if (!Qt3DCore::contains(m_attributes, attribute)) {
m_attributes.push_back(attribute);
// Ensures proper bookkeeping
d->registerDestructionHelper(attribute, &MyQNodeVec::removeAttribute, m_attributes);
// We need to add it as a child of the current node if it has been declared inline
// Or not previously added as a child of the current node so that
// 1) The backend gets notified about it's creation
// 2) When the current node is destroyed, it gets destroyed as well
if (!attribute->parent())
attribute->setParent(this);
d->update();
}
}
void removeAttribute(Qt3DCore::QNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
d->update();
m_attributes.erase(std::remove(m_attributes.begin(),
m_attributes.end(),
attribute), m_attributes.end());
// Remove bookkeeping connection
d->unregisterDestructionHelper(attribute);
}
protected:
std::vector<Qt3DCore::QNode *> m_attributes;
};
class MyQEntity : public Qt3DCore::QEntity
{
Q_OBJECT
Q_PROPERTY(MyQNode *nodeProperty READ nodeProperty WRITE setNodeProperty NOTIFY nodePropertyChanged)
public:
explicit MyQEntity(Qt3DCore::QNode *parent = nullptr)
: QEntity(parent)
{}
~MyQEntity()
{
}
void setArbiterAndScene(Qt3DCore::QChangeArbiter *arbiter,
Qt3DCore::QScene *scene = nullptr)
{
Q_ASSERT(arbiter);
if (scene)
scene->setArbiter(arbiter);
Qt3DCore::QNodePrivate::get(this)->setScene(scene);
Qt3DCore::QNodePrivate::get(this)->setArbiter(arbiter);
}
void setArbiterAndEngine(Qt3DCore::QChangeArbiter *arbiter,
Qt3DCore::QAspectEngine *engine)
{
Q_ASSERT(arbiter);
Q_ASSERT(engine);
auto scene = Qt3DCore::QAspectEnginePrivate::get(engine)->m_scene;
Qt3DCore::QNodePrivate::get(this)->setScene(scene);
Qt3DCore::QNodePrivate::get(this)->setArbiter(arbiter);
if (scene) {
scene->setArbiter(arbiter);
engine->setRootEntity(Qt3DCore::QEntityPtr(this, [](QEntity *) {}));
scene->setArbiter(arbiter);
Qt3DCore::QNodePrivate::get(this)->setArbiter(arbiter);
}
}
void setSimulateBackendCreated(bool created)
{
Qt3DCore::QNodePrivate::get(this)->m_hasBackendNode = created;
}
MyQNode *nodeProperty() const { return m_nodeProperty; }
void addAttribute(MyQNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
if (!m_attributes.contains(attribute)) {
m_attributes.append(attribute);
// Ensures proper bookkeeping
d->registerDestructionHelper(attribute, &MyQEntity::removeAttribute, m_attributes);
// We need to add it as a child of the current node if it has been declared inline
// Or not previously added as a child of the current node so that
// 1) The backend gets notified about it's creation
// 2) When the current node is destroyed, it gets destroyed as well
if (!attribute->parent())
attribute->setParent(this);
d->update();
}
}
void removeAttribute(MyQNode *attribute)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
d->update();
m_attributes.removeOne(attribute);
// Remove bookkeeping connection
d->unregisterDestructionHelper(attribute);
}
public slots:
void setNodeProperty(MyQNode *node)
{
Qt3DCore::QNodePrivate *d = Qt3DCore::QNodePrivate::get(this);
if (m_nodeProperty == node)
return;
if (m_nodeProperty)
d->unregisterDestructionHelper(m_nodeProperty);
if (node && !node->parent())
node->setParent(this);
m_nodeProperty = node;
// Ensures proper bookkeeping
if (m_nodeProperty)
d->registerDestructionHelper(m_nodeProperty, &MyQEntity::setNodeProperty, m_nodeProperty);
emit nodePropertyChanged(node);
}
signals:
void nodePropertyChanged(MyQNode *node);
private:
MyQNode *m_nodeProperty;
QList<MyQNode *> m_attributes;
};
class MyQComponent : public Qt3DCore::QComponent
{
Q_OBJECT
public:
explicit MyQComponent(Qt3DCore::QNode *parent = nullptr) : QComponent(parent)
{}
void setArbiter(Qt3DCore::QChangeArbiter *arbiter)
{
Q_ASSERT(arbiter);
Qt3DCore::QComponentPrivate::get(this)->setArbiter(arbiter);
}
};
class MyFakeMaterial : public Qt3DCore::QComponent
{
Q_OBJECT
public:
explicit MyFakeMaterial(Qt3DCore::QNode *parent = nullptr)
: QComponent(parent)
, m_effect(new MyQNode(this))
, m_technique(new MyQNode(m_effect))
, m_renderPass(new MyQNode(m_technique))
{
}
void setArbiter(Qt3DCore::QChangeArbiter *arbiter)
{
Q_ASSERT(arbiter);
Qt3DCore::QComponentPrivate::get(this)->setArbiter(arbiter);
}
MyQNode *m_effect;
MyQNode *m_technique;
MyQNode *m_renderPass;
};
class TestAspectPrivate;
class TestAspect : public Qt3DCore::QAbstractAspect
{
Q_OBJECT
public:
explicit TestAspect(QObject *parent = nullptr);
enum ChangeType { Creation, Destruction };
void clearNodes()
{
events.clear();
allNodes.clear();
}
void addEvent(const Qt3DCore::QNodeId &id, ChangeType change)
{
events.push_back(Event{ change, id});
}
QList<Qt3DCore::QNodeId> filteredEvents(ChangeType change) const
{
QList<Qt3DCore::QNodeId> result;
for (const auto &event : events) {
if (event.type == change)
result.push_back(event.nodeId);
}
return result;
}
struct Event{
ChangeType type;
Qt3DCore::QNodeId nodeId;
};
mutable QList<Event> events;
mutable QHash<Qt3DCore::QNodeId, Qt3DCore::QNode *> allNodes;
private:
Q_DECLARE_PRIVATE(TestAspect)
explicit TestAspect(TestAspectPrivate &dd, QObject *parent);
};
class TestFunctor : public Qt3DCore::QBackendNodeMapper
{
public:
TestFunctor(TestAspect *aspect) : m_aspect(aspect) {}
Qt3DCore::QBackendNode *create(Qt3DCore::QNodeId id) const override
{
auto node = new Qt3DCore::QBackendNode;
m_Nodes.insert(id, node);
m_aspect->addEvent(id, TestAspect::Creation);
return node;
}
Qt3DCore::QBackendNode *get(Qt3DCore::QNodeId id) const override
{
return m_Nodes.value(id, nullptr);
}
void destroy(Qt3DCore::QNodeId id) const override
{
if (m_Nodes.contains(id)) {
m_aspect->addEvent(id, TestAspect::Destruction);
delete m_Nodes.take(id);
}
}
private:
mutable QHash<Qt3DCore::QNodeId, Qt3DCore::QBackendNode *> m_Nodes;
TestAspect *m_aspect;
};
class TestAspectPrivate : public Qt3DCore::QAbstractAspectPrivate
{
public:
TestAspectPrivate() = default;
void syncDirtyFrontEndNode(Qt3DCore::QNode *node, Qt3DCore::QBackendNode *backend,
bool firstTime) const override
{
Q_UNUSED(backend);
auto q = q_func();
if (firstTime)
q->allNodes.insert(node->id(), node);
}
Q_DECLARE_PUBLIC(TestAspect)
};
TestAspect::TestAspect(QObject *parent) : TestAspect(*new TestAspectPrivate, parent)
{
registerBackendType<Qt3DCore::QEntity>(QSharedPointer<TestFunctor>::create(this));
registerBackendType<MyQEntity>(QSharedPointer<TestFunctor>::create(this));
registerBackendType<MyQNode>(QSharedPointer<TestFunctor>::create(this));
registerBackendType<Qt3DCore::QNode>(QSharedPointer<TestFunctor>::create(this));
}
TestAspect::TestAspect(TestAspectPrivate &dd, QObject *parent)
: Qt3DCore::QAbstractAspect(dd, parent)
{
setObjectName(QStringLiteral("Test Aspect"));
}
namespace {
void verifyChildrenCreatedBeforeParents(Qt3DCore::QNode *root, TestAspect *aspect)
{
QSet<Qt3DCore::QNodeId> processedNodes;
processedNodes.insert(root->id());
for (const auto &nodeId : aspect->filteredEvents(TestAspect::Creation)) {
const auto node = aspect->allNodes.value(nodeId);
Q_ASSERT(node);
const auto parentNode = node->parentNode();
QVERIFY(parentNode == nullptr || processedNodes.contains(parentNode->id()));
processedNodes.insert(nodeId);
}
}
}
void tst_Nodes::defaultNodeConstruction()
{
// GIVEN
QScopedPointer<MyQNode> node(new MyQNode());
// THEN
QVERIFY(node != nullptr);
QVERIFY(node->children().isEmpty());
// GIVEN
MyQNode *node2 = new MyQNode(node.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(node2->parent() == node.data());
QVERIFY(!node->children().isEmpty());
QVERIFY(node2->children().isEmpty());
}
void tst_Nodes::defaultComponentConstruction()
{
// GIVEN
QScopedPointer<MyQComponent> comp(new MyQComponent());
MyQComponent *comp2 = new MyQComponent(comp.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(comp != nullptr);
QCOMPARE(comp2->parent(), comp.data());
}
void tst_Nodes::defaultEntityConstrution()
{
// GIVEN
QScopedPointer<Qt3DCore::QEntity> entity(new Qt3DCore::QEntity());
Qt3DCore::QEntity *entity2 = new Qt3DCore::QEntity(entity.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(entity->components().isEmpty());
QVERIFY(entity2->components().isEmpty());
QCOMPARE(entity2->parent(), entity.data());
}
void tst_Nodes::appendSingleChildNodeToNodeNoSceneExplicitParenting()
{
// Check nodes added when no scene is set
// GIVEN
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
// WHEN
QScopedPointer<MyQNode> child(new MyQNode());
// THEN
QVERIFY(child->parent() == nullptr);
// WHEN
child->setParent(node.data());
// THEN
QVERIFY(child->parent() == node.data());
QVERIFY(child->parentNode() == node.data());
QCOMPARE(node->children().size(), 1);
// Events are only sent when a scene is set on the root node
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
void tst_Nodes::appendSingleChildNodeToNodeNoSceneImplicitParenting()
{
// Check nodes added when no scene is set
// GIVEN
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
// WHEN
QScopedPointer<MyQNode> child(new MyQNode(node.data()));
QCoreApplication::processEvents();
// THEN
QVERIFY(child->parent() == node.data());
QVERIFY(child->parentNode() == node.data());
QCOMPARE(node->children().size(), 1);
// Events are only sent when a scene is set on the root node
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
void tst_Nodes::appendMultipleChildNodesToNodeNoScene()
{
// Check multiple nodes added with no scene set
// GIVEN
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() == nullptr);
// WHEN
for (int i = 0; i < 10; i++) {
// WHEN
Qt3DCore::QNode *child = nullptr;
if (i % 2 == 0) {
child = new MyQNode(node.data());
QCoreApplication::processEvents();
} else {
child = new MyQNode();
child->setParent(node.data());
}
// THEN
QVERIFY(child->parent() == node.data());
}
// THEN
QCOMPARE(node->children().size(), 10);
// Events are only sent when a scene is set on the root node
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
void tst_Nodes::appendSingleChildNodeToNodeSceneExplicitParenting()
{
// Check nodes added when scene is set
// GIVEN
Qt3DCore::QScene scene;
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
// WHEN
node->setArbiterAndScene(&arbiter, &scene);
node->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
// WHEN
QScopedPointer<MyQNode> child(new MyQNode());
// THEN
QVERIFY(child->parent() == nullptr);
QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() == nullptr);
// WHEN
child->setParent(node.data());
// THEN
QVERIFY(child->parent() == node.data());
QVERIFY(child->parentNode() == node.data());
QCOMPARE(arbiter.dirtyNodes().size(), 1); // Child Added
QCOMPARE(node->children().size(), 1);
QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
}
void tst_Nodes::appendSingleChildNodeToNodeSceneImplicitParenting()
{
// Check nodes added when scene is set
// GIVEN
Qt3DCore::QScene scene;
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
// WHEN
node->setArbiterAndScene(&arbiter, &scene);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
// WHEN
QScopedPointer<MyQNode> child(new MyQNode(node.data()));
QCoreApplication::processEvents();
// THEN
QVERIFY(child->parent() == node.data());
QVERIFY(child->parentNode() == node.data());
QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(node->children().size(), 1);
}
void tst_Nodes::appendMultipleChildNodesToNodeScene()
{
// Check nodes added when scene is set
// GIVEN
Qt3DCore::QScene scene;
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
// WHEN
node->setArbiterAndScene(&arbiter, &scene);
node->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(node.data())->scene() != nullptr);
// WHEN
const auto childCount = 10;
for (int i = 0; i < childCount; i++) {
// WHEN
Qt3DCore::QNode *child = nullptr;
if (i % 2 == 0) {
child = new MyQNode(node.data());
} else {
child = new MyQNode();
child->setParent(node.data());
}
// THEN parent and scene should be set synchronously
QVERIFY(child->parent() == node.data());
QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() == Qt3DCore::QNodePrivate::get(node.data())->m_scene);
}
// THEN
QCOMPARE(node->children().size(), 10);
// WHEN
QCoreApplication::processEvents();
// THEN backend is notified after the event loop spins. Only the parent is notified
QCOMPARE(arbiter.dirtyNodes().size(), 1);
}
void tst_Nodes::checkParentChangeToNull()
{
// GIVEN
Qt3DCore::QScene scene;
TestArbiter arbiter;
QScopedPointer<MyQNode> root(new MyQNode());
// WHEN
root->setArbiterAndScene(&arbiter, &scene);
QScopedPointer<Qt3DCore::QNode> child(new MyQNode(root.data()));
QCoreApplication::processEvents();
// THEN
QVERIFY(child->parent() == root.data());
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(root->children().size(), 1);
// WHEN
arbiter.clear();
child->setParent(Q_NODE_NULLPTR);
// THEN
QVERIFY(child->parent() == nullptr);
QCOMPARE(root->children().size(), 0);
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
void tst_Nodes::checkParentChangeToOtherParent()
{
// GIVEN
Qt3DCore::QScene scene;
TestArbiter arbiter;
QScopedPointer<MyQNode> root(new MyQNode());
root->setArbiterAndScene(&arbiter, &scene);
QScopedPointer<MyQNode> parent1(new MyQNode(root.data()));
QScopedPointer<MyQNode> parent2(new MyQNode(root.data()));
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1); // only parent node has changed
// WHEN
arbiter.clear();
QScopedPointer<Qt3DCore::QNode> child(new MyQNode(parent1.data()));
QCoreApplication::processEvents();
// THEN
QVERIFY(child->parent() == parent1.data());
QCOMPARE(parent1->children().size(), 1);
QCOMPARE(parent2->children().size(), 0);
QVERIFY(Qt3DCore::QNodePrivate::get(child.data())->scene() != nullptr);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 node added to children change
// WHEN
arbiter.clear();
child->setParent(parent2.data());
// THEN
QVERIFY(child->parent() == parent2.data());
QCOMPARE(parent1->children().size(), 0);
QCOMPARE(parent2->children().size(), 1);
QCOMPARE(arbiter.dirtyNodes().size(), 1);
// CHECK event 1 is a Node Removed event
// this no longer generated because nodes don't have backends yet
QVERIFY(!Qt3DCore::QNodePrivate::get(child.data())->m_hasBackendNode);
}
void tst_Nodes::checkParentChangeFromExistingBackendParentToNewlyCreatedParent()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
engine.setRunMode(Qt3DCore::QAspectEngine::Manual);
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
auto aspect = new TestAspect;
engine.registerAspect(aspect);
MyQNode *child(new MyQNode(root.data()));
MyQNode *child2(new MyQNode(root.data()));
QCoreApplication::processEvents();
engine.processFrame();
// Due to the way we create root, it has a backend
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode == true);
QCOMPARE(aspect->events.size(), 2);
QCOMPARE(aspect->events[0].type, TestAspect::Creation);
QCOMPARE(aspect->events[0].nodeId, child->id());
QCOMPARE(aspect->events[1].type, TestAspect::Creation);
QCOMPARE(aspect->events[1].nodeId, child2->id());
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1); // only parent node has changed
// WHEN -> Reparenting child with backend node to new parent with no backend yet
aspect->clearNodes();
arbiter.clear();
QScopedPointer<Qt3DCore::QNode> newParent(new MyQNode(root.data()));
child->setParent(newParent.data());
// THEN
QVERIFY(child->parent() == newParent.data());
QCOMPARE(newParent->childNodes().size(), 1);
QCOMPARE(child2->childNodes().size(), 0);
QCOMPARE(root->childNodes().size(), 2);
QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->m_hasBackendNode == false);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == false);
QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->scene() != nullptr);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() != nullptr);
// WHEN
QCoreApplication::processEvents();
engine.processFrame();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1); // only parent node has changed
QVERIFY(Qt3DCore::QNodePrivate::get(newParent.data())->m_hasBackendNode == true);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == true);
// WHEN -> Changing parent to node with existing backend
aspect->clearNodes();
child->setParent(child2);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 3); // old parent, new parent, and child have all changed
arbiter.clear();
QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == true);
QCOMPARE(root->childNodes().size(), 2);
QCOMPARE(child2->childNodes().size(), 1);
QCOMPARE(newParent->childNodes().size(), 0);
// WHEN -> Changing to parent which has no backend
QScopedPointer<Qt3DCore::QNode> newParent2(new MyQNode(root.data()));
child->setParent(newParent2.data());
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(newParent2.data())->m_hasBackendNode == false);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == false);
QVERIFY(Qt3DCore::QNodePrivate::get(newParent2.data())->scene() != nullptr);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->scene() != nullptr);
// WHEN
QCoreApplication::processEvents();
engine.processFrame();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 2);
}
//Test creation changes happen for an entire subtree at once, starting at the top
// so that parents are always created before their children. Even if the front-end
// nodes are constructed in a different order.
void tst_Nodes::checkBackendNodesCreatedFromTopDown()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
auto aspect = new TestAspect;
engine.registerAspect(aspect);
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
QScopedPointer<Qt3DCore::QNode> parentWithBackend(new MyQNode(root.data()));
// create parent backend node
QCoreApplication::processEvents();
QVERIFY(Qt3DCore::QNodePrivate::get(parentWithBackend.get())->m_hasBackendNode);
// WHEN -> creating 3 nodes and setting one as a property on the other
// child2 is set as property on child1, but is created AFTER child1
arbiter.clear();
MyQNode *dummyParent(new MyQNode(parentWithBackend.data()));
MyQNode *child1(new MyQNode(parentWithBackend.data()));
MyQNode *child2(new MyQNode(dummyParent));
child1->setNodeProperty(child2);
// THEN - we should have no events because the new nodes have no backend yet
QCOMPARE(arbiter.dirtyNodes().size(), 0);
// WHEN - create the backend nodes
QCoreApplication::processEvents();
// THEN
QVERIFY(dummyParent->parent() == parentWithBackend.data());
QVERIFY(child1->parent() == parentWithBackend.data());
QVERIFY(child2->parent() == dummyParent);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1); // only parent node has changed
{
QVERIFY(Qt3DCore::QNodePrivate::get(dummyParent)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(child1)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(child2)->m_hasBackendNode);
verifyChildrenCreatedBeforeParents(root.get(), aspect);
}
}
void tst_Nodes::checkBackendNodesCreatedFromTopDownWithReparenting()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
auto aspect = new TestAspect;
engine.registerAspect(aspect);
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
QScopedPointer<Qt3DCore::QNode> parentWithBackend(new MyQNode(root.data()));
// create parent backend node
QCoreApplication::processEvents();
QCoreApplication::processEvents();
QVERIFY(Qt3DCore::QNodePrivate::get(parentWithBackend.get())->m_hasBackendNode);
// WHEN -> creating a node with a parent with backend, then reparenting it to another
// parent with backend created after it.
arbiter.clear();
auto node1 = new MyQNode(parentWithBackend.data());
auto parent1 = new MyQNode(parentWithBackend.data());
node1->setParent(parent1);
QCoreApplication::processEvents();
// THEN
QVERIFY(node1->parent() == parent1);
QVERIFY(parent1->parent() == parentWithBackend.data());
verifyChildrenCreatedBeforeParents(root.get(), aspect);
// WHEN -> creating 2 nodes with no parent and reparenting the first to the second
auto node2 = new MyQNode(nullptr);
auto parent2 = new MyQNode(nullptr);
node2->setParent(parent2);
parent2->setParent(parentWithBackend.get());
QCoreApplication::processEvents();
// THEN
QVERIFY(node2->parent() == parent2);
QVERIFY(parent2->parent() == parentWithBackend.data());
verifyChildrenCreatedBeforeParents(root.get(), aspect);
}
void tst_Nodes::removingSingleChildNodeFromNode()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
QScopedPointer<Qt3DCore::QNode> child(new MyQNode());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
child->setParent(root.data());
// Clear any creation event
arbiter.clear();
// THEN
QVERIFY(root->children().size() == 1);
QVERIFY(child->parentNode() == root.data());
// WHEN
child->setParent(Q_NODE_NULLPTR);
// THEN
QVERIFY(child->parent() == nullptr);
QVERIFY(root->children().size() == 0);
QCOMPARE(arbiter.dirtyNodes().size(), 1);
}
void tst_Nodes::checkAllBackendCreationDoneInSingleFrame()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
engine.setRunMode(Qt3DCore::QAspectEngine::Manual);
auto aspect = new TestAspect;
engine.registerAspect(aspect);
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
QCoreApplication::processEvents();
// THEN
// Due to the way we create root, it has a backend
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode == true);
QCOMPARE(aspect->events.size(), 1);
QCOMPARE(aspect->events[0].type, TestAspect::Creation);
QCOMPARE(aspect->events[0].nodeId, root->id());
// WHEN -> create 2 children:
// 1. a child with parent with backend node
// 2. a child with no parent that is then reparented to a parent with backend node
aspect->clearNodes();
auto child1 = new MyQNode(root.data());
auto child2 = new MyQNode;
child2->setParent(root.data());
// THEN - reparented child should have a backend node, but other child should
// still be waiting
QCOMPARE(child1->parent(), root.data());
QCOMPARE(child2->parent(), root.data());
QVERIFY(Qt3DCore::QNodePrivate::get(child1)->m_hasBackendNode == false);
QVERIFY(Qt3DCore::QNodePrivate::get(child2)->m_hasBackendNode == true);
// WHEN
QCoreApplication::processEvents();
engine.processFrame();
// THEN - both children have their backend nodes actually created.
QCOMPARE(aspect->events.size(), 2);
QCOMPARE(aspect->events[0].type, TestAspect::Creation);
QCOMPARE(aspect->events[0].nodeId, child2->id());
QCOMPARE(aspect->events[1].type, TestAspect::Creation);
QCOMPARE(aspect->events[1].nodeId, child1->id());
}
void tst_Nodes::removingMultipleChildNodesFromNode()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN
Qt3DCore::QNodeIdVector childIds;
for (int i = 0; i < 10; i++) {
auto child = new MyQNode(root.data());
childIds.push_back(child->id());
}
QCoreApplication::processEvents();
// THEN
QCOMPARE(root->children().size(), 10);
QCOMPARE(arbiter.dirtyNodes().size(), 1);
// WHEN
arbiter.clear();
auto cl = root->children();
qDeleteAll(cl);
// THEN
QVERIFY(root->children().size() == 0);
QCOMPARE(arbiter.dirtyNodes().size(), 0); // since all nodes are deleted, there's no backend to notify
}
void tst_Nodes::appendingChildEntitiesToNode()
{
// GIVEN
QScopedPointer<MyQNode> root(new MyQNode());
// WHEN
Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(root.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(root->children().first() == childEntity);
QVERIFY(childEntity->parentEntity() == nullptr);
QVERIFY(childEntity->parentNode() == root.data());
}
void tst_Nodes::removingChildEntitiesFromNode()
{
// GIVEN
QScopedPointer<MyQNode> root(new MyQNode());
// WHEN
Qt3DCore::QEntity *childEntity = new Qt3DCore::QEntity(root.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(root->children().first() == childEntity);
QVERIFY(childEntity->parentEntity() == nullptr);
QVERIFY(childEntity->parentNode() == root.data());
// WHEN
childEntity->setParent(Q_NODE_NULLPTR);
// THEN
QVERIFY(root->children().isEmpty());
QVERIFY(childEntity->parentNode() == nullptr);
QVERIFY(childEntity->parent() == nullptr);
}
void tst_Nodes::checkConstructionSetParentMix()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
auto aspect = new TestAspect;
engine.registerAspect(aspect);
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN
Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
for (int i = 0; i < 100; ++i) {
Qt3DCore::QEntity *child = new Qt3DCore::QEntity();
child->setParent(subTreeRoot);
}
// THEN
QCoreApplication::processEvents();
QCOMPARE(root->children().size(), 1);
QCOMPARE(subTreeRoot->children().size(), 100);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 child added (subTree to root)
// Ensure first event is subTreeRoot
verifyChildrenCreatedBeforeParents(root.data(), aspect);
}
void tst_Nodes::checkParentingQEntityToQNode()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN
auto subTreeRoot = new Qt3DCore::QEntity(root.data());
auto childEntity = new Qt3DCore::QEntity(subTreeRoot);
auto childNode = new Qt3DCore::QNode(subTreeRoot);
// THEN
QCoreApplication::processEvents();
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(subTreeRoot)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(childEntity)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(childNode)->m_hasBackendNode);
// WHEN we reparent the childEntity to the childNode (QNode)
arbiter.clear();
childEntity->setParent(childNode);
QCoreApplication::processEvents();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 3);
}
void tst_Nodes::checkConstructionWithParent()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN we create a child and then set it as a Node* property
auto *node = new MyQNode(root.data());
root->setNodeProperty(node);
// THEN we should get one child added change
QCoreApplication::processEvents();
QCOMPARE(root->children().size(), 1);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 child added change
QCOMPARE(arbiter.dirtyNodes().front(), root.data());
}
void tst_Nodes::checkConstructionWithNonRootParent()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
QScopedPointer<MyQNode> parent(new MyQNode(root.data()));
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
QVERIFY(Qt3DCore::QNodePrivate::get(parent.data())->scene() != nullptr);
// WHEN we create a child and then set it as a Node* property
auto *child = new MyQNode(parent.data());
root->setNodeProperty(child);
// THEN we should get
// - one creation change for parent,
// - one creation change for child,
// - one child added change for root->parent,
// - and one property change event,
// in that order.
QCoreApplication::processEvents();
QCOMPARE(root->children().size(), 1);
QCOMPARE(parent->children().size(), 1);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 child added changes
QCOMPARE(arbiter.dirtyNodes().front(), root.data());
}
void tst_Nodes::checkConstructionAsListElement()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
QScopedPointer<MyQEntity> root(new MyQEntity());
// WHEN
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN we create a child and then set it as a Node* property
auto *node = new MyQNode(root.data());
root->addAttribute(node);
// THEN we should get one creation change, one child added change
// and one property change event, in that order.
QCoreApplication::processEvents();
QCOMPARE(root->children().size(), 1);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 property change
}
void tst_Nodes::checkSceneIsSetOnConstructionWithParent()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QScene scene;
QScopedPointer<MyQNode> root(new MyQNode());
// WHEN
root->setArbiterAndScene(&arbiter, &scene);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN
Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
QList<Qt3DCore::QEntity *> children;
QList<Qt3DCore::QComponent *> cmps;
children.reserve(5);
cmps.reserve(5);
for (int i = 0; i < 5; ++i) {
const auto cmp = new MyQComponent(subTreeRoot);
cmps << cmp;
children << new Qt3DCore::QEntity(cmp);
// When cmp is full created, it will also send the creation change for its child entity
}
QCoreApplication::processEvents();
QCOMPARE(root->children().size(), 1);
QCOMPARE(subTreeRoot->children().size(), 5);
QCOMPARE(arbiter.dirtyNodes().size(), 1); // 1 child added (subTree to root)
arbiter.clear();
// Add component in a separate loop to ensure components are added after
// entities have been fully created
for (int i = 0; i < 5; ++i) {
children.at(i)->addComponent(cmps.at(i));
}
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 5);
}
void tst_Nodes::checkSubNodePostConstructIsCalledWhenReferincingNodeProperty()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
Qt3DCore::QScene scene(&engine);
QScopedPointer<MyQNode> root(new MyQNode());
// WHEN
root->setArbiterAndScene(&arbiter, &scene);
root->setSimulateBackendCreated(true);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(root.data())->scene() != nullptr);
// WHEN
Qt3DCore::QEntity *subTreeRoot = new Qt3DCore::QEntity(root.data());
QCoreApplication::processEvents();
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(subTreeRoot)->m_hasBackendNode);
// WHEN
MyFakeMaterial *material = new MyFakeMaterial(subTreeRoot);
subTreeRoot->addComponent(material);
// THEN
QVERIFY(Qt3DCore::QNodePrivate::get(material)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(material->m_effect)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(material->m_technique)->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(material->m_renderPass)->m_hasBackendNode);
}
void tst_Nodes::appendingParentlessComponentToEntityWithoutScene()
{
// GIVEN
TestArbiter arbiter;
{
QScopedPointer<MyQEntity> entity(new MyQEntity());
entity->setArbiterAndScene(&arbiter);
entity->setSimulateBackendCreated(true);
MyQComponent *comp = new MyQComponent();
comp->setArbiter(&arbiter);
// THEN
QVERIFY(entity->parentNode() == nullptr);
QVERIFY(entity->children().size() == 0);
QVERIFY(entity->components().empty());
QVERIFY(comp->parentNode() == nullptr);
// WHEN
entity->addComponent(comp);
// THEN
QVERIFY(entity->components().size() == 1);
QVERIFY(entity->components().first() == comp);
QVERIFY(comp->parentNode() == entity.data());
QCOMPARE(arbiter.dirtyNodes().size(), 1);
// Note: since QEntity has no scene in this test case, we only have the
// ComponentAdded event In theory we should also get the NodeCreated event
// when setting the parent but that doesn't happen since no scene is
// actually set on the entity and that QNodePrivate::_q_addChild will
// return early in such a case.
}
}
void tst_Nodes::appendingParentlessComponentToNonRootEntity()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
{
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
root->setSimulateBackendCreated(true);
QCoreApplication::processEvents();
QScopedPointer<MyQEntity> entity(new MyQEntity(root.data()));
MyQComponent *comp = new MyQComponent();
// THEN
QVERIFY(root->parentNode() == nullptr);
QVERIFY(root->children().size() == 1);
QVERIFY(root->components().empty());
QVERIFY(entity->parentNode() == root.data());
QVERIFY(entity->children().size() == 0);
QVERIFY(entity->components().empty());
QVERIFY(comp->parentNode() == nullptr);
// WHEN
entity->addComponent(comp);
QCoreApplication::processEvents();
// THEN
QVERIFY(entity->components().size() == 1);
QVERIFY(entity->components().first() == comp);
QVERIFY(comp->parentNode() == entity.data());
QCOMPARE(arbiter.dirtyNodes().size(), 2);
QCOMPARE(arbiter.dirtyNodes().at(0), root.data());
QCOMPARE(arbiter.dirtyNodes().at(1), entity.data());
QVERIFY(Qt3DCore::QNodePrivate::get(entity.data())->m_hasBackendNode);
QVERIFY(Qt3DCore::QNodePrivate::get(comp)->m_hasBackendNode);
}
}
void tst_Nodes::appendingParentlessComponentToEntityWithScene()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
{
QScopedPointer<MyQEntity> entity(new MyQEntity());
entity->setArbiterAndEngine(&arbiter, &engine);
entity->setSimulateBackendCreated(true);
QCoreApplication::processEvents();
MyQComponent *comp = new MyQComponent();
// THEN
QVERIFY(entity->parentNode() == nullptr);
QVERIFY(entity->children().size() == 0);
QVERIFY(entity->components().empty());
QVERIFY(comp->parentNode() == nullptr);
// WHEN
entity->addComponent(comp);
QCoreApplication::processEvents();
// THEN
QVERIFY(entity->components().size() == 1);
QVERIFY(entity->components().first() == comp);
QVERIFY(comp->parentNode() == entity.data());
QCOMPARE(arbiter.dirtyNodes().size(), 1);
}
}
void tst_Nodes::appendingComponentToEntity()
{
// GIVEN
TestArbiter arbiter;
{
QScopedPointer<MyQEntity> entity(new MyQEntity());
entity->setArbiterAndScene(&arbiter);
MyQComponent *comp = new MyQComponent(entity.data());
comp->setArbiter(&arbiter);
QCoreApplication::processEvents();
// THEN
QVERIFY(entity->parentNode() == nullptr);
QVERIFY(entity->children().size() == 1);
QVERIFY(entity->components().empty());
QVERIFY(comp->parentNode() == entity.data());
// WHEN
entity->addComponent(comp);
// THEN
QVERIFY(entity->components().size() == 1);
QVERIFY(entity->components().first() == comp);
QVERIFY(comp->parentNode() == entity.data());
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(arbiter.dirtyNodes().first(), entity.data());
}
}
void tst_Nodes::removingComponentFromEntity()
{
// GIVEN
TestArbiter arbiter;
{
QScopedPointer<MyQEntity> entity(new MyQEntity());
entity->setArbiterAndScene(&arbiter);
MyQComponent *comp = new MyQComponent();
comp->setArbiter(&arbiter);
// WHEN
entity->addComponent(comp);
// THEN
QVERIFY(entity->components().size() == 1);
QCOMPARE(entity->children().size(), 1);
QVERIFY(comp->parent() == entity.data());
// WHEN
arbiter.clear();
entity->removeComponent(comp);
// THEN
QVERIFY(entity->components().size() == 0);
QVERIFY(comp->parent() == entity.data());
QVERIFY(entity->children().size() == 1);
QCOMPARE(arbiter.dirtyNodes().size(), 1);
}
}
void tst_Nodes::changeCustomProperty()
{
// GIVEN
TestArbiter arbiter;
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter);
// WHEN
node->setCustomProperty(QStringLiteral("foo"));
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(arbiter.dirtyNodes().front(), node.data());
}
void tst_Nodes::checkDestruction()
{
// GIVEN
QScopedPointer<MyQNode> root(new MyQNode());
Qt3DCore::QEntity *entity = new Qt3DCore::QEntity(root.data());
QCoreApplication::processEvents();
MyQComponent *comp1 = new MyQComponent();
MyQComponent *comp2 = new MyQComponent();
MyQComponent *comp3 = new MyQComponent();
entity->addComponent(comp1);
entity->addComponent(comp2);
entity->addComponent(comp3);
// THEN
QVERIFY(!root->children().isEmpty());
// WHEN
delete entity;
// THEN
QVERIFY(root->children().isEmpty());
}
void tst_Nodes::checkDefaultConstruction()
{
// GIVEN
Qt3DCore::QNode node;
// THEN
QCOMPARE(node.parentNode(), nullptr);
QCOMPARE(node.isEnabled(), true);
}
void tst_Nodes::checkPropertyChanges()
{
// GIVEN
Qt3DCore::QNode parentNode;
Qt3DCore::QNode node;
{
// WHEN
QSignalSpy spy(&node, SIGNAL(parentChanged(QObject*)));
Qt3DCore::QNode *newValue = &parentNode;
node.setParent(newValue);
// THEN
QVERIFY(spy.isValid());
QCOMPARE(node.parentNode(), newValue);
QCOMPARE(spy.size(), 1);
// WHEN
spy.clear();
node.setParent(newValue);
// THEN
QCOMPARE(node.parentNode(), newValue);
QCOMPARE(spy.size(), 0);
}
{
// WHEN
QSignalSpy spy(&node, SIGNAL(enabledChanged(bool)));
const bool newValue = false;
node.setEnabled(newValue);
// THEN
QVERIFY(spy.isValid());
QCOMPARE(node.isEnabled(), newValue);
QCOMPARE(spy.size(), 1);
// WHEN
spy.clear();
node.setEnabled(newValue);
// THEN
QCOMPARE(node.isEnabled(), newValue);
QCOMPARE(spy.size(), 0);
}
}
void tst_Nodes::checkEnabledUpdate()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QNode node;
arbiter.setArbiterOnNode(&node);
{
// WHEN
node.setEnabled(false);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(arbiter.dirtyNodes().front(), &node);
arbiter.clear();
}
{
// WHEN
node.setEnabled(false);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
}
void tst_Nodes::checkNodeRemovedFromDirtyListOnDestruction()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QScene scene;
{
// GIVEN
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter, &scene);
// WHEN
node->setEnabled(false);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 1);
QCOMPARE(arbiter.dirtyNodes().front(), node.data());
// WHEN
// scene should be unset and node removed from arbiter dirtyList
node.reset();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
{
// GIVEN
QScopedPointer<MyQNode> node(new MyQNode());
node->setArbiterAndScene(&arbiter, &scene);
Qt3DCore::QNode *child = new Qt3DCore::QNode(node.data());
// Wait for deferred initialization of child node
QCoreApplication::processEvents();
// WHEN
child->setEnabled(false);
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 2);
QCOMPARE(arbiter.dirtyNodes().at(0), node.data());
QCOMPARE(arbiter.dirtyNodes().at(1), child);
// WHEN
// scene should be unset and child node removed from arbiter dirtyList
node.reset();
// THEN
QCOMPARE(arbiter.dirtyNodes().size(), 0);
}
}
void tst_Nodes::checkNodeDestroyedBeforeBackendCreation()
{
// GIVEN
TestArbiter arbiter;
Qt3DCore::QAspectEngine engine;
engine.setRunMode(Qt3DCore::QAspectEngine::Manual);
auto aspect = new TestAspect;
engine.registerAspect(aspect);
QScopedPointer<MyQEntity> root(new MyQEntity());
root->setArbiterAndEngine(&arbiter, &engine);
QCoreApplication::processEvents();
engine.processFrame();
// GIVEN a node whose backend node has been created
auto parent = new MyQNode(root.data());
QCoreApplication::processEvents();
engine.processFrame();
QVERIFY(Qt3DCore::QNodePrivate::get(parent)->m_hasBackendNode == true);
aspect->clearNodes();
// WHEN a child is created, and its parent destroyed before the event loop spins
auto child = new MyQNode(parent);
QVERIFY(Qt3DCore::QNodePrivate::get(child)->m_hasBackendNode == false);
delete parent;
// THEN the child, destroyed along with its parent, must no longer be queued for
// post-construction init (used to be a use-after-free)
QCoreApplication::processEvents();
// THEN the aspects must not be asked to destroy a backend node the child never had
// (used to assert in QAbstractAspectPrivate::mapperForNode)
engine.processFrame();
// THEN only the parent had a backend node to destroy, and the child was never created
QCOMPARE(aspect->filteredEvents(TestAspect::Creation).size(), 0);
QCOMPARE(aspect->filteredEvents(TestAspect::Destruction).size(), 1);
}
void tst_Nodes::checkBookkeepingSingleNode()
{
// GIVEN
MyQNode root;
// THEN
QVERIFY(root.nodeProperty() == nullptr);
{
// WHEN
MyQNode node;
root.setNodeProperty(&node);
// THEN
QCOMPARE(root.nodeProperty(), &node);
}
// WHEN -> node destroyed
// THEN
QVERIFY(root.nodeProperty() == nullptr);
}
void tst_Nodes::checkBookkeeping_QVector_OfNode()
{
// GIVEN
MyQNode root;
// THEN
QVERIFY(root.attributes().size() == 0);
{
// WHEN
MyQNode node1;
root.addAttribute(&node1);
// THEN
QVERIFY(root.attributes().size() == 1);
// WHEN
{
MyQNode node2;
root.addAttribute(&node2);
// THEN
QVERIFY(root.attributes().size() == 2);
QCOMPARE(root.attributes().first(), &node1);
QCOMPARE(root.attributes().last(), &node2);
}
// THEN
QVERIFY(root.attributes().size() == 1);
QCOMPARE(root.attributes().first(), &node1);
}
// THEN
QVERIFY(root.attributes().size() == 0);
}
void tst_Nodes::checkBookkeeping_stdvector_OfNode()
{
// GIVEN
MyQNodeVec root;
// THEN
QVERIFY(root.attributes().size() == 0U);
{
// WHEN
MyQNode node1;
root.addAttribute(&node1);
// THEN
QVERIFY(root.attributes().size() == 1U);
// WHEN
{
MyQNode node2;
root.addAttribute(&node2);
// THEN
QVERIFY(root.attributes().size() == 2U);
QCOMPARE(root.attributes().front(), &node1);
QCOMPARE(root.attributes().back(), &node2);
}
// THEN
QVERIFY(root.attributes().size() == 1U);
QCOMPARE(root.attributes().front(), &node1);
}
// THEN
QVERIFY(root.attributes().size() == 0U);
}
QTEST_MAIN(tst_Nodes)
#include "tst_nodes.moc"