Revision control
Copy as Markdown
// Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qaspectengine.h"
#include "qaspectengine_p.h"
#include <Qt3DCore/qabstractaspect.h>
#include <Qt3DCore/qcomponent.h>
#include <Qt3DCore/qentity.h>
#include <Qt3DCore/qnode.h>
#include <QtCore/QMetaObject>
#include <Qt3DCore/private/corelogging_p.h>
#include <Qt3DCore/private/qaspectmanager_p.h>
#include <Qt3DCore/private/qchangearbiter_p.h>
#include <Qt3DCore/private/qnode_p.h>
#include <Qt3DCore/private/qnodevisitor_p.h>
#include <Qt3DCore/private/qscene_p.h>
#include <Qt3DCore/private/qservicelocator_p.h>
#include <Qt3DCore/private/qsysteminformationservice_p.h>
#include <Qt3DCore/qt3dcore-config.h>
QT_BEGIN_NAMESPACE
namespace{
QList<Qt3DCore::QNode *> getNodesForCreation(Qt3DCore::QNode *root)
{
using namespace Qt3DCore;
QList<QNode *> nodes;
QNodeVisitor visitor;
visitor.traverse(root, [&nodes](QNode *node) {
nodes.append(node);
// Store the metaobject of the node in the QNode so that we have it available
// to us during destruction in the QNode destructor. This allows us to send
// the QNodeId and the metaobject as typeinfo to the backend aspects so they
// in turn can find the correct QBackendNodeMapper object to handle the destruction
// of the corresponding backend nodes.
QNodePrivate *d = QNodePrivate::get(node);
d->m_typeInfo = const_cast<QMetaObject*>(QNodePrivate::findStaticMetaObject(node->metaObject()));
// Mark this node as having been handled for creation so that it is picked up
d->m_hasBackendNode = true;
});
return nodes;
}
QList<Qt3DCore::QNode *> getNodesForRemoval(Qt3DCore::QNode *root)
{
using namespace Qt3DCore;
QList<QNode *> nodes;
QNodeVisitor visitor;
visitor.traverse(root, [&nodes](QNode *node) {
QNodePrivate *d = QNodePrivate::get(node);
if (d->m_hasBackendNode)
nodes.append(node);
// Mark this node as having been handled for destruction so we don't
// repeat it unnecessarily in an O(n^2) manner
d->m_hasBackendNode = false;
});
return nodes;
}
}
namespace Qt3DCore {
QAspectEnginePrivate *QAspectEnginePrivate::get(QAspectEngine *q)
{
return q->d_func();
}
QAspectEnginePrivate::QAspectEnginePrivate()
: QObjectPrivate()
, m_aspectManager(nullptr)
, m_scene(nullptr)
, m_initialized(false)
, m_runMode(QAspectEngine::Automatic)
{
qRegisterMetaType<Qt3DCore::QAbstractAspect *>();
qRegisterMetaType<Qt3DCore::QNode *>();
qRegisterMetaType<Qt3DCore::QEntity *>();
qRegisterMetaType<Qt3DCore::QScene *>();
}
QAspectEnginePrivate::~QAspectEnginePrivate()
{
qDeleteAll(m_aspects);
}
/*!
* \internal
*
* Used to init the scene tree when the Qt3D aspect is first started. Basically,
* sets the scene/change arbiter on the items and stores the entity - component
* pairs in the scene
*/
void QAspectEnginePrivate::initNode(QNode *node)
{
m_scene->addObservable(node);
QNodePrivate::get(node)->setScene(m_scene);
}
void QAspectEnginePrivate::initEntity(QEntity *entity)
{
const auto components = entity->components();
for (QComponent *comp : components) {
if (!m_scene->hasEntityForComponent(comp->id(), entity->id())) {
if (!comp->isShareable() && !m_scene->entitiesForComponent(comp->id()).isEmpty())
qWarning() << "Trying to assign a non shareable component to more than one Entity";
m_scene->addEntityForComponent(comp->id(), entity->id());
}
}
}
void QAspectEnginePrivate::addNode(QNode *node)
{
m_aspectManager->addNodes(getNodesForCreation(node));
}
void QAspectEnginePrivate::removeNode(QNode *node)
{
m_aspectManager->removeNodes(getNodesForRemoval(node));
}
/*!
* \class Qt3DCore::QAspectEngine
* \inheaderfile Qt3DCore/QAspectEngine
* \inherits QObject
* \inmodule Qt3DCore
*
* \brief Responsible for handling all the QAbstractAspect subclasses that have
* been registered with the scene.
*
* The Qt3D run loop is controlled by the Qt3DRender::QAspectEngine.
*
* Qt3DCore::QAbstractAspect subclasses can be registered by calling
* Qt3DCore::QAspectEngine::registerAspect() which will take care of registering
* the aspect and in turn that will call Qt3DCore::QAbstractAspect::onRegistered();
*
* The simulation loop is launched as soon as a root Qt3DCore::QEntity
* is set on the Qt3DCore::QAspectEngine. This is followed by a call to
* onEngineStartup() on each aspect so that they can start their simulation
* work.
*
* The simulation loop is stopped when the root entity is set to
* Qt3DCore::QEntityPtr(). This calls onEngineShutdown() on each aspect so
* that they can stop performing their simulation work.
*
* Setting a new valid root entity would restart the simulation loop again.
*/
/*!
* \internal
* This loop is executed in a separate thread called the AspectThread in
* Qt3DCore::QAspectThread. This thread is started when the
* Qt3DCore::QAspectEngine is created and provides the
* Qt3DCore::QAspectManager which lives in this thread for as long as it's
* running.
*
* Once the AspectThread is running, it starts the run loop and waits for
* aspects to be registered.
*
* Destroying the Qt3DCore::QAspectEngine instance stops the AspectThread and
* properly terminates the run loop.
*/
/*!
* \typedef Qt3DCore::QEntityPtr
* \relates Qt3DCore::QAspectEngine
*
* A shared pointer for QEntity.
*/
/*!
* Constructs a new QAspectEngine with \a parent.
*/
QAspectEngine::QAspectEngine(QObject *parent)
: QObject(*new QAspectEnginePrivate, parent)
{
qCDebug(Aspects) << Q_FUNC_INFO;
Q_D(QAspectEngine);
d->m_scene = new QScene(this);
d->m_aspectManager = new QAspectManager(this);
}
/*!
* Destroys the engine.
*/
QAspectEngine::~QAspectEngine()
{
Q_D(QAspectEngine);
// Shutdown the simulation loop by setting an empty scene
// Note: this sets an atomic which allows the AspectThread to break out of
// the inner simulation loop in the AspectThread::exec function
setRootEntity(QEntityPtr());
// Unregister all aspects and exit the main loop
const auto aspects = d->m_aspects;
for (auto aspect : aspects)
unregisterAspect(aspect);
delete d->m_scene;
}
void QAspectEnginePrivate::initNodeTree(QNode *node)
{
// Set the root entity on the scene
m_scene->setRootNode(node);
QNodeVisitor visitor;
visitor.traverse(node, this, &QAspectEnginePrivate::initNode, &QAspectEnginePrivate::initEntity);
}
void QAspectEnginePrivate::initialize()
{
m_aspectManager->initialize();
QChangeArbiter *arbiter = m_aspectManager->changeArbiter();
m_scene->setArbiter(arbiter);
arbiter->setScene(m_scene);
m_initialized = true;
m_aspectManager->setPostConstructorInit(m_scene->postConstructorInit());
}
/*!
* \internal
*
* Called when we unset the root entity. Causes the QAspectManager's simulation
* loop to be exited. The main loop should keep processing events ready
* to start up the simulation again with a new root entity.
*/
void QAspectEnginePrivate::shutdown()
{
qCDebug(Aspects) << Q_FUNC_INFO;
// Exit the simulation loop. Waits for this to be completed on the aspect
// thread before returning
exitSimulationLoop();
// Cleanup the scene before quitting the backend
m_scene->setArbiter(nullptr);
m_initialized = false;
}
void QAspectEnginePrivate::exitSimulationLoop()
{
if (m_aspectManager != nullptr)
m_aspectManager->exitSimulationLoop();
}
QNode *QAspectEnginePrivate::lookupNode(QNodeId id) const
{
return m_scene ? m_scene->lookupNode(id) : nullptr;
}
QList<QNode *> QAspectEnginePrivate::lookupNodes(const QList<QNodeId> &ids) const
{
return m_scene ? m_scene->lookupNodes(ids) : QList<QNode *>{};
}
/*!
Registers a new \a aspect to the AspectManager. The QAspectEngine takes
ownership of the aspect and will delete it when the aspect is unregistered.
//! Called in the main thread
*/
void QAspectEngine::registerAspect(QAbstractAspect *aspect)
{
Q_D(QAspectEngine);
const QStringList dependencies = aspect->dependencies();
for (const auto &name: dependencies) {
if (!d->m_namedAspects.contains(name))
registerAspect(name);
}
d->m_aspects << aspect;
d->m_aspectManager->registerAspect(aspect);
}
/*!
* Registers a new aspect to the AspectManager based on its \a name
* Uses the currently set aspect factory to create the actual aspect
* instance.
*/
void QAspectEngine::registerAspect(const QString &name)
{
Q_D(QAspectEngine);
QAbstractAspect *aspect = d->m_factory.createAspect(QLatin1String(name.toUtf8()));
if (aspect) {
registerAspect(aspect);
d->m_namedAspects.insert(name, aspect);
}
}
/*!
* Unregisters and deletes the given \a aspect.
*/
void QAspectEngine::unregisterAspect(QAbstractAspect *aspect)
{
Q_D(QAspectEngine);
if (!d->m_aspects.contains(aspect)) {
qWarning() << "Attempting to unregister an aspect that is not registered";
return;
}
// Cleanly shutdown this aspect by setting its root entity to null which
// will cause its onEngineShutdown() virtual to be called to allow it to
// cleanup any resources. Then remove it from the QAspectManager's list
// of aspects.
// TODO: Implement this once we are able to cleanly shutdown
// Tell the aspect manager to give the aspect a chance to do some cleanup
// in its QAbstractAspect::onUnregistered() virtual
d->m_aspectManager->unregisterAspect(aspect);
// Remove from our collection of named aspects (if present)
const auto it = std::find_if(d->m_namedAspects.begin(), d->m_namedAspects.end(),
[aspect](QAbstractAspect *v) { return v == aspect; });
if (it != d->m_namedAspects.end())
d->m_namedAspects.erase(it);
// Finally, scheduly the aspect for deletion. Do this via the event loop
// in case we are unregistering the aspect in response to a signal from it.
aspect->deleteLater();
d->m_aspects.removeOne(aspect);
}
/*!
* Unregisters and deletes the aspect with the given \a name.
*/
void QAspectEngine::unregisterAspect(const QString &name)
{
Q_D(QAspectEngine);
if (!d->m_namedAspects.contains(name)) {
qWarning() << "Attempting to unregister an aspect that is not registered";
return;
}
// Delegate unregistering and removal to the overload
QAbstractAspect *aspect = d->m_namedAspects.value(name);
unregisterAspect(aspect);
}
/*!
* \return the aspects owned by the aspect engine.
*/
QList<QAbstractAspect *> QAspectEngine::aspects() const
{
Q_D(const QAspectEngine);
return d->m_aspects;
}
/*!
* \return the asepect matching the \a name
*
* \note Required that the aspect was registered by name
*/
QAbstractAspect *QAspectEngine::aspect(const QString &name) const
{
Q_D(const QAspectEngine);
return d->m_namedAspects.value(name, nullptr);
}
/*!
* Executes the given \a command on aspect engine. Valid commands are:
* \list
* \li "list aspects"
* \endlist
*
* \return the reply for the command.
*/
QVariant QAspectEngine::executeCommand(const QString &command)
{
Q_D(QAspectEngine);
if (command == QLatin1String("list aspects")) {
if (d->m_aspects.isEmpty())
return QLatin1String("No loaded aspect");
const QStringList names = d->m_aspectManager->serviceLocator()->systemInformation()->aspectNames();
return names.join(QLatin1String("\n"));
}
if (command == QLatin1String("dump jobs")) {
d->m_aspectManager->dumpJobsOnNextFrame();
return QLatin1String("Dump in next frame in working directory");
}
QStringList args = command.split(QLatin1Char(' '));
QString aspectName = args.takeFirst();
for (QAbstractAspect *aspect : std::as_const(d->m_aspects)) {
if (aspectName == d->m_factory.aspectName(aspect))
return aspect->executeCommand(args);
}
return QVariant();
}
/*!
* If using the manual run mode, this function executes the jobs for each aspect.
* It is blocking and won't return until all jobs have been completed.
*
* If you are using the QRenderAspect,
*/
void QAspectEngine::processFrame()
{
Q_D(QAspectEngine);
Q_ASSERT(d->m_runMode == QAspectEngine::Manual);
d->m_aspectManager->processFrame();
}
QNode *QAspectEngine::lookupNode(QNodeId id) const
{
Q_D(const QAspectEngine);
return d->lookupNode(id);
}
QList<QNode *> QAspectEngine::lookupNodes(const QList<QNodeId> &ids) const
{
Q_D(const QAspectEngine);
return d->lookupNodes(ids);
}
/*!
* Sets the \a root entity for the aspect engine.
*/
void QAspectEngine::setRootEntity(QEntityPtr root)
{
qCDebug(Aspects) << Q_FUNC_INFO << "root =" << root;
Q_D(QAspectEngine);
if (d->m_root == root)
return;
const bool shutdownNeeded = d->m_root && d->m_initialized;
// Set the new root object. This will cause the old tree to be deleted
// and the deletion of the old frontend tree will cause the backends to
// free any related resources
d->m_root = root;
if (shutdownNeeded)
d->shutdown();
// Do we actually have a new scene?
if (!d->m_root)
return;
// Set postman/scene/arbiter ...
d->initialize();
// The aspect engine takes ownership of the scene root. We also set the
// parent of the scene root to be the engine
static_cast<QObject *>(d->m_root.data())->setParent(this);
// Prepare the frontend tree for use by giving each node a pointer to the
// scene object and adding each node to the scene
// TODO: We probably need a call symmetric to this one above in order to
// deregister the nodes from the scene
d->initNodeTree(root.data());
const QList<QNode *> nodes = getNodesForCreation(root.data());
// Specify if the AspectManager should be driving the simulation loop or not
d->m_aspectManager->setRunMode(d->m_runMode);
// Finally, tell the aspects about the new scene object tree. This is done
// in a blocking manner to allow the aspects to get synchronized before the
// main thread starts triggering potentially more notifications
// TODO: Pass the creation changes via the arbiter rather than relying upon
// an invokeMethod call.
qCDebug(Aspects) << "Begin setting scene root on aspect manager";
d->m_aspectManager->setRootEntity(root.data(), nodes);
qCDebug(Aspects) << "Done setting scene root on aspect manager";
d->m_aspectManager->enterSimulationLoop();
}
/*!
* \return the root entity of the aspect engine.
*/
QEntityPtr QAspectEngine::rootEntity() const
{
Q_D(const QAspectEngine);
return d->m_root;
}
void QAspectEngine::setRunMode(QAspectEngine::RunMode mode)
{
Q_D(QAspectEngine);
d->m_runMode = mode;
if (d->m_aspectManager)
d->m_aspectManager->setRunMode(mode);
}
QAspectEngine::RunMode QAspectEngine::runMode() const
{
Q_D(const QAspectEngine);
return d->m_runMode;
}
} // namespace Qt3DCore
QT_END_NAMESPACE
#include "moc_qaspectengine.cpp"