summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/changes/qpropertynodeaddedchange.cpp2
-rw-r--r--src/core/nodes/qnode.cpp79
-rw-r--r--src/core/nodes/qnode_p.h18
-rw-r--r--src/core/qscene.cpp8
-rw-r--r--src/core/qscene_p.h3
-rw-r--r--tests/auto/core/nodes/tst_nodes.cpp80
6 files changed, 187 insertions, 3 deletions
diff --git a/src/core/changes/qpropertynodeaddedchange.cpp b/src/core/changes/qpropertynodeaddedchange.cpp
index 865de2731..347d7f188 100644
--- a/src/core/changes/qpropertynodeaddedchange.cpp
+++ b/src/core/changes/qpropertynodeaddedchange.cpp
@@ -85,7 +85,7 @@ QPropertyNodeAddedChange::QPropertyNodeAddedChange(QNodeId subjectId, QNode *nod
// loop will still be blocked. So force it here and we catch this
// eventuality in the _q_postConstructorInit() function so that we
// do not repeat the creation and new child scene change events.
- QNodePrivate::get(node)->_q_postConstructorInit();
+ QNodePrivate::get(node)->_q_ensureBackendNodeCreated();
}
/*! \internal */
diff --git a/src/core/nodes/qnode.cpp b/src/core/nodes/qnode.cpp
index c2373b805..900c3f8ce 100644
--- a/src/core/nodes/qnode.cpp
+++ b/src/core/nodes/qnode.cpp
@@ -101,7 +101,7 @@ void QNodePrivate::init(QNode *parent)
Q_Q(QNode);
if (m_scene) {
// schedule the backend notification and scene registering -> set observers through scene
- QMetaObject::invokeMethod(q, "_q_postConstructorInit", Qt::QueuedConnection);
+ m_scene->postConstructorInit()->addNode(q);
}
}
@@ -165,7 +165,7 @@ void QNodePrivate::notifyDestructionChangesAndRemoveFromScene()
*
* Sends a QNodeCreatedChange event to the aspects and then also notifies the
* parent backend node of its new child. This is called in a deferred manner
- * by the QNodePrivate::init() method to notify the backend of newly created
+ * by NodePostConstructorInit::processNodes to notify the backend of newly created
* nodes with a parent that is already part of the scene.
*
* Also notify the scene of this node, so it may set it's change arbiter.
@@ -873,6 +873,12 @@ void QNode::setParent(QNode *parent)
if (parentNode() == parent &&
((parent != nullptr && d->m_parentId == parentNode()->id()) || parent == nullptr))
return;
+
+ // remove ourself from postConstructorInit queue. The call to _q_setParentHelper
+ // will take care of creating the backend node if necessary depending on new parent.
+ if (d->m_scene)
+ d->m_scene->postConstructorInit()->removeNode(this);
+
d->_q_setParentHelper(parent);
// Block notifications as we want to let the _q_setParentHelper
@@ -1132,6 +1138,75 @@ const QMetaObject *QNodePrivate::findStaticMetaObject(const QMetaObject *metaObj
return lastStaticMetaobject;
}
+/*!
+ * \internal
+ *
+ * NodePostConstructorInit handles calling QNode::_q_postConstructorInit for
+ * all nodes. By keeping track of nodes that need initialization we can
+ * create them all together ensuring they get sent to the backend in a single
+ * batch.
+ */
+NodePostConstructorInit::NodePostConstructorInit(QObject *parent)
+ : QObject(parent)
+ , m_requestedProcessing(false)
+{
+}
+
+NodePostConstructorInit::~NodePostConstructorInit() {}
+
+/*!
+ * \internal
+ *
+ * Add a node to the list of nodes needing a call to _q_postConstructorInit
+ * We only add the node if it does not have an ancestor already in the queue
+ * because initializing the ancestor will initialize all it's children.
+ * This ensures that all backend nodes are created from the top-down, with
+ * all parents created before their children
+ *
+ */
+void NodePostConstructorInit::addNode(QNode *node)
+{
+ Q_ASSERT(node);
+ QNode *nextNode = node;
+ while (nextNode != nullptr && !m_nodesToConstruct.contains(QNodePrivate::get(nextNode)))
+ nextNode = nextNode->parentNode();
+
+ if (!nextNode) {
+ m_nodesToConstruct.append(QNodePrivate::get(node));
+ if (!m_requestedProcessing){
+ QMetaObject::invokeMethod(this, "processNodes", Qt::QueuedConnection);
+ m_requestedProcessing = true;
+ }
+ }
+}
+
+/*!
+ * \internal
+ *
+ * Remove a node from the queue. This will ensure none of its
+ * children get initialized
+ */
+void NodePostConstructorInit::removeNode(QNode *node)
+{
+ Q_ASSERT(node);
+ m_nodesToConstruct.removeAll(QNodePrivate::get(node));
+}
+
+/*!
+ * \internal
+ *
+ * call _q_postConstructorInit for all nodes in the queue
+ * and clear the queue
+ */
+void NodePostConstructorInit::processNodes()
+{
+ m_requestedProcessing = false;
+ while (!m_nodesToConstruct.empty()) {
+ auto node = m_nodesToConstruct.takeFirst();
+ node->_q_postConstructorInit();
+ }
+}
+
} // namespace Qt3DCore
QT_END_NAMESPACE
diff --git a/src/core/nodes/qnode_p.h b/src/core/nodes/qnode_p.h
index 6ffb19ce8..8b43c2695 100644
--- a/src/core/nodes/qnode_p.h
+++ b/src/core/nodes/qnode_p.h
@@ -60,6 +60,7 @@
#include <Qt3DCore/private/qobservableinterface_p.h>
#include <Qt3DCore/private/qt3dcore_global_p.h>
#include <QtCore/private/qobject_p.h>
+#include <QQueue>
QT_BEGIN_NAMESPACE
@@ -174,6 +175,23 @@ private:
QHash<QNode *, QMetaObject::Connection> m_destructionConnections;
};
+class NodePostConstructorInit : public QObject
+{
+ Q_OBJECT
+public:
+ NodePostConstructorInit(QObject *parent = nullptr);
+ virtual ~NodePostConstructorInit();
+ void removeNode(QNode *node);
+ void addNode(QNode *node);
+
+private Q_SLOTS:
+ void processNodes();
+
+private:
+ QQueue<QNodePrivate *> m_nodesToConstruct;
+ bool m_requestedProcessing;
+};
+
} // namespace Qt3DCore
QT_END_NAMESPACE
diff --git a/src/core/qscene.cpp b/src/core/qscene.cpp
index b5895c7aa..9624aec64 100644
--- a/src/core/qscene.cpp
+++ b/src/core/qscene.cpp
@@ -58,6 +58,7 @@ public:
: m_engine(engine)
, m_arbiter(nullptr)
, m_rootNode(nullptr)
+ , m_postConstructorInit(new NodePostConstructorInit)
{
}
@@ -68,6 +69,7 @@ public:
QHash<QObservableInterface *, QNodeId> m_observableToUuid;
QHash<QNodeId, QScene::NodePropertyTrackData> m_nodePropertyTrackModeLookupTable;
QLockableObserverInterface *m_arbiter;
+ QScopedPointer<NodePostConstructorInit> m_postConstructorInit;
mutable QReadWriteLock m_lock;
mutable QReadWriteLock m_nodePropertyTrackModeLock;
QNode *m_rootNode;
@@ -247,6 +249,12 @@ void QScene::removePropertyTrackDataForNode(QNodeId nodeId)
d->m_nodePropertyTrackModeLookupTable.remove(nodeId);
}
+NodePostConstructorInit *QScene::postConstructorInit() const
+{
+ Q_D(const QScene);
+ return d->m_postConstructorInit.get();
+}
+
void QScene::setRootNode(QNode *root)
{
Q_D(QScene);
diff --git a/src/core/qscene_p.h b/src/core/qscene_p.h
index aaa8d9e92..1de7e3d4d 100644
--- a/src/core/qscene_p.h
+++ b/src/core/qscene_p.h
@@ -64,6 +64,7 @@ namespace Qt3DCore {
class QScenePrivate;
class QAspectEngine;
+class NodePostConstructorInit;
typedef QList<QObservableInterface *> QObservableList;
@@ -106,6 +107,8 @@ public:
void setPropertyTrackDataForNode(QNodeId id, const NodePropertyTrackData &data);
void removePropertyTrackDataForNode(QNodeId id);
+ NodePostConstructorInit* postConstructorInit() const;
+
private:
Q_DECLARE_PRIVATE(QScene)
QScopedPointer<QScenePrivate> d_ptr;
diff --git a/tests/auto/core/nodes/tst_nodes.cpp b/tests/auto/core/nodes/tst_nodes.cpp
index 193d88c83..dad66c5d5 100644
--- a/tests/auto/core/nodes/tst_nodes.cpp
+++ b/tests/auto/core/nodes/tst_nodes.cpp
@@ -73,6 +73,7 @@ private slots:
void checkParentChangeToNull();
void checkParentChangeToOtherParent();
void checkParentChangeFromExistingBackendParentToNewlyCreatedParent();
+ void checkBackendNodesCreatedFromTopDown(); //QTBUG-74106
void removingSingleChildNodeFromNode();
void removingMultipleChildNodesFromNode();
@@ -902,6 +903,85 @@ void tst_Nodes::checkParentChangeFromExistingBackendParentToNewlyCreatedParent()
}
}
+//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
+ Qt3DCore::QScene scene;
+ ObserverSpy spy;
+ QScopedPointer<MyQNode> root(new MyQNode());
+ root->setArbiterAndScene(&spy, &scene);
+ 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
+ spy.events.clear();
+ MyQNode *dummyParent(new MyQNode(parentWithBackend.data()));
+ MyQNode *child1(new MyQNode(parentWithBackend.data()));
+ MyQNode *child2(new MyQNode(dummyParent));
+ child2->setNodeProperty(child1);
+
+ // THEN - we should have no events because the new nodes have no backend yet
+ QCOMPARE(spy.events.count(), 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(spy.events.size(), 5);
+ // 2 node creation change for dummyParent subtree (dummyParent and child2)
+ // 1 node added to children change (dummyParent to parent)
+ // 1 node created change for child1
+ // 1 node added to children change (child1 to parent)
+
+ {
+ // 1st event: dummyParent creation
+ const auto event1 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!event1.isNull());
+ QCOMPARE(event1->type(), Qt3DCore::NodeCreated);
+ QCOMPARE(event1->parentId(), parentWithBackend->id());
+ QCOMPARE(event1->subjectId(), dummyParent->id());
+
+ // 2nd event: child2 creation (even though we constructed child1 first)
+ const auto event2 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!event2.isNull());
+ QCOMPARE(event2->type(), Qt3DCore::NodeCreated);
+ QCOMPARE(event2->parentId(), dummyParent->id());
+ QCOMPARE(event2->subjectId(), child2->id());
+
+ // 3rd event: dummyParent added to parent
+ const auto event3 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
+ QCOMPARE(event3->type(), Qt3DCore::PropertyValueAdded);
+ QCOMPARE(event3->addedNodeId(), dummyParent->id());
+ QCOMPARE(event3->subjectId(), parentWithBackend->id());
+
+ // 4th event: child1 creation
+ const auto event4 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QNodeCreatedChangeBase>();
+ QVERIFY(!event4.isNull());
+ QCOMPARE(event4->type(), Qt3DCore::NodeCreated);
+ QCOMPARE(event4->parentId(), parentWithBackend->id());
+ QCOMPARE(event4->subjectId(), child1->id());
+
+ // 5th event: child 1 added to parent
+ const auto event5 = spy.events.takeFirst().change().dynamicCast<Qt3DCore::QPropertyNodeAddedChange>();
+ QCOMPARE(event5->type(), Qt3DCore::PropertyValueAdded);
+ QCOMPARE(event5->addedNodeId(), child1->id());
+ QCOMPARE(event5->subjectId(), parentWithBackend->id());
+ }
+}
+
+
void tst_Nodes::removingSingleChildNodeFromNode()
{
// GIVEN