1// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qentity.h"
5#include "qentity_p.h"
6
7#include <Qt3DCore/qcomponent.h>
8#include <QtCore/QMetaObject>
9#include <QtCore/QMetaProperty>
10
11#include <Qt3DCore/private/corelogging_p.h>
12#include <Qt3DCore/private/qcomponent_p.h>
13#include <Qt3DCore/private/qscene_p.h>
14
15#include <QQueue>
16
17QT_BEGIN_NAMESPACE
18
19namespace {
20
21QString dumpNode(const Qt3DCore::QEntity *n) {
22 auto formatNode = [](const Qt3DCore::QNode *n) {
23 QString res = QString(QLatin1String("%1{%2}"))
24 .arg(a: QLatin1String(n->metaObject()->className()))
25 .arg(a: n->id().id());
26 if (!n->objectName().isEmpty())
27 res += QString(QLatin1String(" (%1)")).arg(a: n->objectName());
28 if (!n->isEnabled())
29 res += QLatin1String(" [D]");
30 return res;
31 };
32
33 QString res = formatNode(n);
34 const auto &components = n->components();
35 if (components.size()) {
36 QStringList componentNames;
37 for (const auto &c : components)
38 componentNames += formatNode(c);
39 res += QString(QLatin1String(" [ %1 ]")).arg(a: componentNames.join(sep: QLatin1String(", ")));
40 }
41
42 return res;
43}
44
45QStringList dumpSG(const Qt3DCore::QNode *n, int level = 0)
46{
47 QStringList reply;
48 const auto *entity = qobject_cast<const Qt3DCore::QEntity *>(object: n);
49 if (entity != nullptr) {
50 QString res = dumpNode(n: entity);
51 reply += res.rightJustified(width: res.size() + level * 2, fill: QLatin1Char(' '));
52 level++;
53 }
54
55 const auto children = n->childNodes();
56 for (auto *child: children)
57 reply += dumpSG(n: child, level);
58
59 return reply;
60}
61
62}
63
64namespace Qt3DCore {
65
66/*!
67 \class Qt3DCore::QEntity
68 \inmodule Qt3DCore
69 \inherits Qt3DCore::QNode
70 \since 5.5
71
72 \brief Qt3DCore::QEntity is a Qt3DCore::QNode subclass that can aggregate several
73 Qt3DCore::QComponent instances that will specify its behavior.
74
75 By itself a Qt3DCore::QEntity is an empty shell. The behavior of a Qt3DCore::QEntity
76 object is defined by the Qt3DCore::QComponent objects it references. Each Qt3D
77 backend aspect will be able to interpret and process an Entity by
78 recognizing which components it is made up of. One aspect may decide to only
79 process entities composed of a single Qt3DCore::QTransform component whilst
80 another may focus on Qt3DInput::QMouseHandler.
81
82 \sa Qt3DCore::QComponent, Qt3DCore::QTransform
83 */
84
85/*!
86 \fn template<typename T> QList<T *> Qt3DCore::QEntity::componentsOfType() const
87
88 Returns all the components added to this entity that can be cast to
89 type T or an empty vector if there are no such components.
90*/
91
92/*! \internal */
93QEntityPrivate::QEntityPrivate()
94 : QNodePrivate()
95 , m_parentEntityId()
96 , m_dirty(false)
97{}
98
99/*! \internal */
100QEntityPrivate::~QEntityPrivate()
101{
102}
103
104/*! \internal */
105QEntityPrivate *QEntityPrivate::get(QEntity *q)
106{
107 return q->d_func();
108}
109
110/*! \internal */
111void QEntityPrivate::removeDestroyedComponent(QComponent *comp)
112{
113 // comp is actually no longer a QComponent, just a QObject
114
115 Q_CHECK_PTR(comp);
116 qCDebug(Nodes) << Q_FUNC_INFO << comp;
117
118 updateComponentRelationShip(component: comp, change: ComponentRelationshipChange::Removed);
119 m_components.removeOne(t: comp);
120 m_dirty = true;
121
122 // Remove bookkeeping connection
123 unregisterDestructionHelper(node: comp);
124}
125
126/*!
127 Constructs a new Qt3DCore::QEntity instance with \a parent as parent.
128 */
129QEntity::QEntity(QNode *parent)
130 : QEntity(*new QEntityPrivate, parent) {}
131
132/*! \internal */
133QEntity::QEntity(QEntityPrivate &dd, QNode *parent)
134 : QNode(dd, parent)
135{
136 connect(sender: this, signal: &QNode::parentChanged, context: this, slot: &QEntity::onParentChanged);
137}
138
139QEntity::~QEntity()
140{
141 // remove all component aggregations
142 Q_D(const QEntity);
143 // to avoid hammering m_components by repeated removeComponent()
144 // calls below, move all contents out, so the removeOne() calls in
145 // removeComponent() don't actually remove something:
146 const auto components = std::move(d->m_components);
147 for (QComponent *comp : components)
148 removeComponent(comp);
149}
150
151
152/*!
153 \typedef Qt3DCore::QComponentVector
154 \relates Qt3DCore::QEntity
155
156 List of QComponent pointers.
157 */
158
159/*!
160 Returns the list of Qt3DCore::QComponent instances the entity is referencing.
161 */
162QComponentVector QEntity::components() const
163{
164 Q_D(const QEntity);
165 return d->m_components;
166}
167
168/*!
169 Adds a new reference to the component \a comp.
170
171 \note If the Qt3DCore::QComponent has no parent, the Qt3DCore::QEntity will set
172 itself as its parent thereby taking ownership of the component.
173 */
174void QEntity::addComponent(QComponent *comp)
175{
176 Q_D(QEntity);
177 Q_CHECK_PTR( comp );
178 qCDebug(Nodes) << Q_FUNC_INFO << comp;
179
180 // A Component can only be aggregated once
181 if (d->m_components.count(t: comp) != 0)
182 return ;
183
184 // We need to add it as a child of the current node if it has been declared inline
185 // Or not previously added as a child of the current node so that
186 // 1) The backend gets notified about it's creation
187 // 2) When the current node is destroyed, it gets destroyed as well
188 if (!comp->parent())
189 comp->setParent(this);
190
191 QNodePrivate::get(q: comp)->_q_ensureBackendNodeCreated();
192
193 d->m_components.append(t: comp);
194 d->m_dirty = true;
195
196 // Ensures proper bookkeeping
197 d->registerPrivateDestructionHelper(node: comp, func: &QEntityPrivate::removeDestroyedComponent);
198
199 d->updateComponentRelationShip(component: comp, change: ComponentRelationshipChange::Added);
200 static_cast<QComponentPrivate *>(QComponentPrivate::get(q: comp))->addEntity(entity: this);
201}
202
203/*!
204 Removes the reference to \a comp.
205 */
206void QEntity::removeComponent(QComponent *comp)
207{
208 Q_CHECK_PTR(comp);
209 qCDebug(Nodes) << Q_FUNC_INFO << comp;
210 Q_D(QEntity);
211
212 static_cast<QComponentPrivate *>(QComponentPrivate::get(q: comp))->removeEntity(entity: this);
213
214 d->updateComponentRelationShip(component: comp, change: ComponentRelationshipChange::Removed);
215
216 d->m_components.removeOne(t: comp);
217 d->m_dirty = true;
218
219 // Remove bookkeeping connection
220 d->unregisterDestructionHelper(node: comp);
221}
222
223/*!
224 Returns the parent Qt3DCore::QEntity instance of this entity. If the
225 immediate parent isn't a Qt3DCore::QEntity, this function traverses up the
226 scene hierarchy until a parent Qt3DCore::QEntity is found. If no
227 Qt3DCore::QEntity parent can be found, returns null.
228 */
229QEntity *QEntity::parentEntity() const
230{
231 Q_D(const QEntity);
232 QNode *parentNode = QNode::parentNode();
233 QEntity *parentEntity = qobject_cast<QEntity *>(object: parentNode);
234
235 while (parentEntity == nullptr && parentNode != nullptr) {
236 parentNode = parentNode->parentNode();
237 parentEntity = qobject_cast<QEntity*>(object: parentNode);
238 }
239 if (!parentEntity) {
240 if (!d->m_parentEntityId.isNull())
241 d->m_parentEntityId = QNodeId();
242 } else {
243 if (d->m_parentEntityId != parentEntity->id())
244 d->m_parentEntityId = parentEntity->id();
245 }
246 return parentEntity;
247}
248
249/*
250 \internal
251
252 Returns the Qt3DCore::QNodeId id of the parent Qt3DCore::QEntity instance of the
253 current Qt3DCore::QEntity object. The QNodeId isNull method will return true if
254 there is no Qt3DCore::QEntity parent of the current Qt3DCore::QEntity in the scene
255 hierarchy.
256 */
257QNodeId QEntityPrivate::parentEntityId() const
258{
259 Q_Q(const QEntity);
260 if (m_parentEntityId.isNull())
261 q->parentEntity();
262 return m_parentEntityId;
263}
264
265QString QEntityPrivate::dumpSceneGraph() const
266{
267 Q_Q(const QEntity);
268 return dumpSG(n: q).join(sep: QLatin1Char('\n'));
269}
270
271void QEntityPrivate::updateComponentRelationShip(QComponent *component, ComponentRelationshipChange::RelationShip change)
272{
273 if (m_changeArbiter) {
274 // Ensure node has its postConstructorInit called if we reach this
275 // point, we could otherwise endup referencing a node that has yet
276 // to be created in the backend
277 QNodePrivate::get(q: component)->_q_ensureBackendNodeCreated();
278
279 Q_Q(QEntity);
280 m_changeArbiter->addDirtyEntityComponentNodes(entity: q, component, change);
281 }
282}
283
284void QEntity::onParentChanged(QObject *)
285{
286 Q_D(QEntity);
287 if (!d->m_hasBackendNode)
288 return;
289
290 d->update();
291}
292
293} // namespace Qt3DCore
294
295QT_END_NAMESPACE
296
297#include "moc_qentity.cpp"
298

source code of qt3d/src/core/nodes/qentity.cpp