/**************************************************************************** ** ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt3D module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qabstractaspect.h" #include "qabstractaspect_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE namespace Qt3DCore { QAbstractAspectPrivate::QAbstractAspectPrivate() : QObjectPrivate() , m_root(nullptr) , m_rootId() , m_aspectManager(nullptr) , m_jobManager(nullptr) , m_arbiter(nullptr) { } QAbstractAspectPrivate::~QAbstractAspectPrivate() { } QAbstractAspectPrivate *QAbstractAspectPrivate::get(QAbstractAspect *aspect) { return aspect->d_func(); } /*! * \internal * Called in the context of the main thread */ void QAbstractAspectPrivate::onEngineAboutToShutdown() { } /*! \internal */ void QAbstractAspectPrivate::unregisterBackendType(const QMetaObject &mo) { m_backendCreatorFunctors.remove(&mo); } /*! * \class Qt3DCore::QAbstractAspect * \inheaderfile Qt3DCore/QAbstractAspect * \inherits QObject * \inmodule Qt3DCore * \brief QAbstractAspect is the base class for aspects that provide a vertical slice of behavior. */ /*! * \fn void Qt3DCore::QAbstractAspect::registerBackendType(const Qt3DCore::QBackendNodeMapperPtr &functor) * Registers backend with \a functor. */ /*! * \macro QT3D_REGISTER_ASPECT(name, AspectType) * \relates Qt3DCore::QAbstractAspect * * Convenience macro for registering \a AspectType for instantiation by the * currently set Qt3DCore::QAspectFactory. This makes it possible to create an * instance of \a AspectType in the aspect thread by later passing \a name to * Qt3DCore::QAspectEngine::registerAspect(const QString &name). * * \note It is also possible to register a new aspect without using this macro * by instead using Qt3DCore::QAspectEngine::registerAspect(QAbstractAspect *aspect) * which will handle moving a previously created aspect instance to the aspect * thread context. * * KDAB has published a few articles about writing custom Qt3D aspects * \l {https://www.kdab.com/writing-custom-qt-3d-aspect/}{on their blog}. These * provide an excellent starting point if you wish to learn more about it. */ /*! * Constructs a new QAbstractAspect with \a parent */ QAbstractAspect::QAbstractAspect(QObject *parent) : QAbstractAspect(*new QAbstractAspectPrivate, parent) {} /*! * \typedef Qt3DCore::QAspectJobPtr * \relates Qt3DCore::QAbstractAspect * * A shared pointer for QAspectJob. */ /*! * \typedef Qt3DCore::QBackendNodeMapperPtr * \relates Qt3DCore::QAbstractAspect * * A shared pointer for QBackendNodeMapper. */ /*! * \internal */ QAbstractAspect::QAbstractAspect(QAbstractAspectPrivate &dd, QObject *parent) : QObject(dd, parent) { } /*! \internal */ QAbstractAspect::~QAbstractAspect() { } /*! * \return root entity node id. */ QNodeId QAbstractAspect::rootEntityId() const Q_DECL_NOEXCEPT { Q_D(const QAbstractAspect); return d->m_rootId; } /*! * Registers backend with \a obj and \a functor. */ void QAbstractAspect::registerBackendType(const QMetaObject &obj, const QBackendNodeMapperPtr &functor) { Q_D(QAbstractAspect); d->m_backendCreatorFunctors.insert(&obj, {functor, QAbstractAspectPrivate::DefaultMapper}); } void QAbstractAspect::registerBackendType(const QMetaObject &obj, const QBackendNodeMapperPtr &functor, bool supportsSyncing) { Q_D(QAbstractAspect); const auto f = supportsSyncing ? QAbstractAspectPrivate::SupportsSyncing : QAbstractAspectPrivate::DefaultMapper; d->m_backendCreatorFunctors.insert(&obj, {functor, f}); } void QAbstractAspect::unregisterBackendType(const QMetaObject &obj) { Q_D(QAbstractAspect); d->m_backendCreatorFunctors.remove(&obj); } QVariant QAbstractAspect::executeCommand(const QStringList &args) { Q_UNUSED(args) return QVariant(); } QVector QAbstractAspect::jobsToExecute(qint64 time) { Q_UNUSED(time) return QVector(); } QAbstractAspectPrivate::BackendNodeMapperAndInfo QAbstractAspectPrivate::mapperForNode(const QMetaObject *metaObj) const { Q_ASSERT(metaObj); BackendNodeMapperAndInfo info; while (metaObj != nullptr && info.first.isNull()) { info = m_backendCreatorFunctors.value(metaObj); metaObj = metaObj->superClass(); } return info; } void QAbstractAspectPrivate::syncDirtyFrontEndNodes(const QVector &nodes) { for (auto node: qAsConst(nodes)) { const QMetaObject *metaObj = QNodePrivate::get(node)->m_typeInfo; const BackendNodeMapperAndInfo backendNodeMapperInfo = mapperForNode(metaObj); const QBackendNodeMapperPtr backendNodeMapper = backendNodeMapperInfo.first; if (!backendNodeMapper) continue; QBackendNode *backend = backendNodeMapper->get(node->id()); if (!backend) continue; const bool supportsSyncing = backendNodeMapperInfo.second & SupportsSyncing; if (supportsSyncing) syncDirtyFrontEndNode(node, backend, false); else sendPropertyMessages(node, backend); } } void QAbstractAspectPrivate::syncDirtyFrontEndSubNodes(const QVector &nodes) { for (const auto &nodeChange: qAsConst(nodes)) { auto getBackend = [this](QNode *node) -> std::tuple { const QMetaObject *metaObj = QNodePrivate::get(node)->m_typeInfo; if (!metaObj) return {}; const BackendNodeMapperAndInfo backendNodeMapperInfo = mapperForNode(metaObj); const QBackendNodeMapperPtr backendNodeMapper = backendNodeMapperInfo.first; if (!backendNodeMapper) return {}; QBackendNode *backend = backendNodeMapper->get(node->id()); if (!backend) return {}; const bool supportsSyncing = backendNodeMapperInfo.second & SupportsSyncing; return std::tuple(backend, supportsSyncing); }; auto nodeInfo = getBackend(nodeChange.node); if (!std::get<0>(nodeInfo)) continue; auto subNodeInfo = getBackend(nodeChange.subNode); if (!std::get<0>(subNodeInfo)) continue; switch (nodeChange.change) { case PropertyValueAdded: { if (std::get<1>(nodeInfo)) break; // do nothing as the node will be dirty anyway QPropertyValueAddedChange change(nodeChange.node->id()); change.setPropertyName(nodeChange.property); change.setAddedValue(QVariant::fromValue(nodeChange.subNode->id())); QPropertyValueAddedChangePtr pChange(&change, [](QPropertyValueAddedChange *) { }); std::get<0>(nodeInfo)->sceneChangeEvent(pChange); } break; case PropertyValueRemoved: { if (std::get<1>(nodeInfo)) break; // do nothing as the node will be dirty anyway QPropertyValueRemovedChange change(nodeChange.node->id()); change.setPropertyName(nodeChange.property); change.setRemovedValue(QVariant::fromValue(nodeChange.subNode->id())); QPropertyValueRemovedChangePtr pChange(&change, [](QPropertyValueRemovedChange *) { }); std::get<0>(nodeInfo)->sceneChangeEvent(pChange); } break; case ComponentAdded: { // let the entity know it has a new component if (std::get<1>(nodeInfo)) { QBackendNodePrivate::get(std::get<0>(nodeInfo))->componentAdded(nodeChange.subNode); } else { QComponentAddedChange change(qobject_cast(nodeChange.subNode), qobject_cast(nodeChange.node)); QComponentAddedChangePtr pChange(&change, [](QComponentAddedChange *) { }); std::get<0>(nodeInfo)->sceneChangeEvent(pChange); } // let the component know it was added to an entity if (std::get<1>(subNodeInfo)) { QBackendNodePrivate::get(std::get<0>(subNodeInfo))->addedToEntity(nodeChange.node); } else { QComponentAddedChange change(qobject_cast(nodeChange.subNode), qobject_cast(nodeChange.node)); QComponentAddedChangePtr pChange(&change, [](QComponentAddedChange *) { }); std::get<0>(subNodeInfo)->sceneChangeEvent(pChange); } } break; case ComponentRemoved: { // let the entity know a component was removed if (std::get<1>(nodeInfo)) { QBackendNodePrivate::get(std::get<0>(nodeInfo))->componentRemoved(nodeChange.subNode); } else { QComponentRemovedChange change(qobject_cast(nodeChange.subNode), qobject_cast(nodeChange.node)); QComponentRemovedChangePtr pChange(&change, [](QComponentRemovedChange *) { }); std::get<0>(nodeInfo)->sceneChangeEvent(pChange); } // let the component know it was removed from an entity if (std::get<1>(subNodeInfo)) { QBackendNodePrivate::get(std::get<0>(subNodeInfo))->removedFromEntity(nodeChange.node); } else { QComponentRemovedChange change(qobject_cast(nodeChange.node), qobject_cast(nodeChange.subNode)); QComponentRemovedChangePtr pChange(&change, [](QComponentRemovedChange *) { }); std::get<0>(nodeInfo)->sceneChangeEvent(pChange); } } break; default: break; } } } void QAbstractAspectPrivate::syncDirtyFrontEndNode(QNode *node, QBackendNode *backend, bool firstTime) const { Q_ASSERT(false); // overload in derived class if (!firstTime) sendPropertyMessages(node, backend); } void QAbstractAspectPrivate::sendPropertyMessages(QNode *node, QBackendNode *backend) const { const int offset = QNode::staticMetaObject.propertyOffset(); const auto metaObj = node->metaObject(); const int count = metaObj->propertyCount(); const auto toBackendValue = [](const QVariant &data) -> QVariant { if (data.canConvert()) { QNode *node = data.value(); // Ensure the node and all ancestors have issued their node creation changes. // We can end up here if a newly created node with a parent is immediately set // as a property on another node. In this case the deferred call to // _q_postConstructorInit() will not have happened yet as the event // loop will still be blocked. We need to do this for all ancestors, // since the subtree of this node otherwise can end up on the backend // with a reference to a non-existent parent. if (node) QNodePrivate::get(node)->_q_ensureBackendNodeCreated(); const QNodeId id = node ? node->id() : QNodeId(); return QVariant::fromValue(id); } return data; }; QPropertyUpdatedChange change(node->id()); QPropertyUpdatedChangePtr pchange(&change, [](QPropertyUpdatedChange *) { }); for (int index = offset; index < count; index++) { const QMetaProperty pro = metaObj->property(index); change.setPropertyName(pro.name()); change.setValue(toBackendValue(pro.read(node))); backend->sceneChangeEvent(pchange); } auto const dynamicProperties = node->dynamicPropertyNames(); for (const QByteArray &name: dynamicProperties) { change.setPropertyName(name.data()); change.setValue(toBackendValue(node->property(name.data()))); backend->sceneChangeEvent(pchange); } } QBackendNode *QAbstractAspectPrivate::createBackendNode(const NodeTreeChange &change) const { const QMetaObject *metaObj = change.metaObj; const BackendNodeMapperAndInfo backendNodeMapperInfo = mapperForNode(metaObj); const QBackendNodeMapperPtr backendNodeMapper = backendNodeMapperInfo.first; if (!backendNodeMapper) return nullptr; QBackendNode *backend = backendNodeMapper->get(change.id); if (backend != nullptr) return backend; QNode *node = change.node; QNodeCreatedChangeBasePtr creationChange; const bool supportsSyncing = backendNodeMapperInfo.second & SupportsSyncing; if (supportsSyncing) { // All objects modified to use syncing should only use the id in the creation functor QNodeCreatedChangeBase changeObj(node); creationChange = QNodeCreatedChangeBasePtr(&changeObj, [](QNodeCreatedChangeBase *) {}); backend = backendNodeMapper->create(creationChange); } else { creationChange = node->createNodeCreationChange(); backend = backendNodeMapper->create(creationChange); } if (!backend) return nullptr; // TODO: Find some place else to do all of this function from the arbiter backend->setPeerId(change.id); // Backend could be null if the user decides that his functor should only // perform some action when encountering a given type of item but doesn't need to // return a QBackendNode pointer. QBackendNodePrivate *backendPriv = QBackendNodePrivate::get(backend); backendPriv->setEnabled(node->isEnabled()); // TO DO: Find a way to specify the changes to observe // Register backendNode with QChangeArbiter if (m_arbiter != nullptr) { // Unit tests may not have the arbiter registered qCDebug(Nodes) << q_func()->objectName() << "Creating backend node for node id" << node->id() << "of type" << QNodePrivate::get(node)->m_typeInfo->className(); m_arbiter->registerObserver(backendPriv, backend->peerId(), AllChanges); if (backend->mode() == QBackendNode::ReadWrite) m_arbiter->scene()->addObservable(backendPriv, backend->peerId()); } if (supportsSyncing) syncDirtyFrontEndNode(node, backend, true); else backend->initializeFromPeer(creationChange); return backend; } void QAbstractAspectPrivate::clearBackendNode(const NodeTreeChange &change) const { const QMetaObject *metaObj = change.metaObj; const BackendNodeMapperAndInfo backendNodeMapperInfo = mapperForNode(metaObj); const QBackendNodeMapperPtr backendNodeMapper = backendNodeMapperInfo.first; if (!backendNodeMapper) return; // Request the mapper to destroy the corresponding backend node QBackendNode *backend = backendNodeMapper->get(change.id); if (backend) { qCDebug(Nodes) << "Deleting backend node for node id" << change.id << "of type" << metaObj->className(); QBackendNodePrivate *backendPriv = QBackendNodePrivate::get(backend); m_arbiter->unregisterObserver(backendPriv, backend->peerId()); if (backend->mode() == QBackendNode::ReadWrite) m_arbiter->scene()->removeObservable(backendPriv, backend->peerId()); backendNodeMapper->destroy(change.id); } } void QAbstractAspectPrivate::setRootAndCreateNodes(QEntity *rootObject, const QVector &nodesChanges) { qCDebug(Aspects) << Q_FUNC_INFO << "rootObject =" << rootObject; if (rootObject == m_root) return; m_root = rootObject; m_rootId = rootObject->id(); for (const NodeTreeChange &change : nodesChanges) createBackendNode(change); } QServiceLocator *QAbstractAspectPrivate::services() const { return m_aspectManager ? m_aspectManager->serviceLocator() : nullptr; } QAbstractAspectJobManager *QAbstractAspectPrivate::jobManager() const { return m_jobManager; } QVector QAbstractAspectPrivate::jobsToExecute(qint64 time) { Q_Q(QAbstractAspect); auto res = q->jobsToExecute(time); { QMutexLocker lock(&m_singleShotMutex); res << m_singleShotJobs; m_singleShotJobs.clear(); } return res; } void QAbstractAspectPrivate::jobsDone() { } void QAbstractAspectPrivate::frameDone() { } /*! * Called in the context of the aspect thread once the aspect has been registered. * This provides an opportunity for the aspect to do any initialization tasks that * require to be in the aspect thread context such as creating QObject subclasses that * must have affinity with this thread. * * \sa onUnregistered */ void QAbstractAspect::onRegistered() { } /*! * Called in the context of the aspect thread during unregistration * of the aspect. This gives the aspect a chance to do any final pieces of * cleanup that it would not do when just changing to a new scene. * * \sa onRegistered */ void QAbstractAspect::onUnregistered() { } /*! * * Called in the QAspectThread context */ void QAbstractAspect::onEngineStartup() { } /*! * * Called in the QAspectThread context */ void QAbstractAspect::onEngineShutdown() { } void QAbstractAspect::scheduleSingleShotJob(const Qt3DCore::QAspectJobPtr &job) { Q_D(QAbstractAspect); QMutexLocker lock(&d->m_singleShotMutex); d->m_singleShotJobs.push_back(job); } namespace Debug { AsynchronousCommandReply::AsynchronousCommandReply(const QString &commandName, QObject *parent) : QObject(parent) , m_commandName(commandName) , m_finished(false) { } void AsynchronousCommandReply::setFinished(bool replyFinished) { m_finished = replyFinished; if (m_finished) emit finished(this); } void AsynchronousCommandReply::setData(const QByteArray &data) { m_data = data; } } // Debug } // of namespace Qt3DCore QT_END_NAMESPACE