| /**************************************************************************** |
| ** |
| ** 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 "qchangearbiter_p.h" |
| |
| #include <Qt3DCore/qcomponent.h> |
| #include <QtCore/QMutexLocker> |
| #include <QtCore/QReadLocker> |
| #include <QtCore/QThread> |
| #include <QtCore/QWriteLocker> |
| |
| #include <Qt3DCore/private/corelogging_p.h> |
| #include <Qt3DCore/private/qabstractaspectjobmanager_p.h> |
| #include <Qt3DCore/private/qpostman_p.h> |
| #include <Qt3DCore/private/qscene_p.h> |
| |
| #include <mutex> |
| |
| QT_BEGIN_NAMESPACE |
| |
| namespace Qt3DCore { |
| |
| /* !\internal |
| \class Qt3DCore::QChangeArbiter |
| \inmodule Qt3DCore |
| \since 5.5 |
| |
| \brief Acts as a message router between observables and observers. |
| |
| Observables can be of two types: QNode observables and \l {QObservableInterface}s. |
| QNode notifications are sent from the frontend QNode and delivered to the backend observers. |
| QObservableInterface notifications are sent from backend nodes to backend observers and/or to the |
| registered QPostman, which in turn delivers the notifications to the target frontend QNode. |
| |
| QNode observables are registered automatically. However, QObservableInterface object have to be registered manually |
| by providing the QNodeId of the corresponding frontend QNode. |
| |
| Observers can be registered to receive messages from a QObservableInterface/QNode observable by providing a QNode NodeUuid. |
| When a notification from a QObservableInterface is received, it is then sent to all observers observing the |
| QNode NodeUuid as well as the QPostman to update the frontend QNode. |
| */ |
| QChangeArbiter::QChangeArbiter(QObject *parent) |
| : QObject(parent) |
| , m_jobManager(nullptr) |
| , m_postman(nullptr) |
| , m_scene(nullptr) |
| { |
| // The QMutex has to be recursive to handle the case where : |
| // 1) SyncChanges is called, mutex is locked |
| // 2) Changes are distributed |
| // 3) An observer decides to register a new observable upon receiving notification |
| // 4) registerObserver locks the mutex once again -> we need recursion otherwise deadlock |
| // 5) Mutex is unlocked - leaving registerObserver |
| // 6) Mutex is unlocked - leaving SyncChanges |
| } |
| |
| QChangeArbiter::~QChangeArbiter() |
| { |
| if (m_jobManager != nullptr) |
| m_jobManager->waitForPerThreadFunction(QChangeArbiter::destroyThreadLocalChangeQueue, this); |
| m_lockingChangeQueues.clear(); |
| m_changeQueues.clear(); |
| } |
| |
| void QChangeArbiter::initialize(QAbstractAspectJobManager *jobManager) |
| { |
| Q_CHECK_PTR(jobManager); |
| m_jobManager = jobManager; |
| |
| // Init TLS for the change queues |
| m_jobManager->waitForPerThreadFunction(QChangeArbiter::createThreadLocalChangeQueue, this); |
| } |
| |
| void QChangeArbiter::distributeQueueChanges(QChangeQueue *changeQueue) |
| { |
| for (int i = 0, n = int(changeQueue->size()); i < n; i++) { |
| QSceneChangePtr& change = (*changeQueue)[i]; |
| // Lookup which observers care about the subject this change came from |
| // and distribute the change to them |
| if (change.isNull()) |
| continue; |
| |
| if (change->type() == NodeCreated || change->type() == NodeDeleted) { |
| Q_ASSERT(false); // messages no longer used |
| } |
| |
| const QNodeId nodeId = change->subjectId(); |
| const auto it = m_nodeObservations.constFind(nodeId); |
| if (it != m_nodeObservations.cend()) { |
| const QObserverList &observers = it.value(); |
| for (const QObserverPair &observer : observers) { |
| if ((change->type() & observer.first) && |
| (change->deliveryFlags() & QSceneChange::BackendNodes)) |
| observer.second->sceneChangeEvent(change); |
| } |
| // Also send change to the postman |
| if (change->deliveryFlags() & QSceneChange::Nodes) { |
| // Check if QNode actually cares about the change |
| if (m_postman->shouldNotifyFrontend(change)) |
| m_postman->sceneChangeEvent(change); |
| } |
| } |
| } |
| changeQueue->clear(); |
| } |
| |
| QThreadStorage<QChangeArbiter::QChangeQueue *> *QChangeArbiter::tlsChangeQueue() |
| { |
| return &(m_tlsChangeQueue); |
| } |
| |
| void QChangeArbiter::appendChangeQueue(QChangeArbiter::QChangeQueue *queue) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| m_changeQueues.append(queue); |
| } |
| |
| void QChangeArbiter::removeChangeQueue(QChangeArbiter::QChangeQueue *queue) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| m_changeQueues.removeOne(queue); |
| } |
| |
| void QChangeArbiter::appendLockingChangeQueue(QChangeArbiter::QChangeQueue *queue) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| m_lockingChangeQueues.append(queue); |
| } |
| |
| void QChangeArbiter::removeLockingChangeQueue(QChangeArbiter::QChangeQueue *queue) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| m_lockingChangeQueues.removeOne(queue); |
| } |
| |
| void QChangeArbiter::syncChanges() |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| for (QChangeArbiter::QChangeQueue *changeQueue : qAsConst(m_changeQueues)) |
| distributeQueueChanges(changeQueue); |
| |
| for (QChangeQueue *changeQueue : qAsConst(m_lockingChangeQueues)) |
| distributeQueueChanges(changeQueue); |
| } |
| |
| void QChangeArbiter::setScene(QScene *scene) |
| { |
| m_scene = scene; |
| } |
| |
| QAbstractPostman *QChangeArbiter::postman() const |
| { |
| return m_postman; |
| } |
| |
| QScene *QChangeArbiter::scene() const |
| { |
| return m_scene; |
| } |
| |
| void QChangeArbiter::registerObserver(QObserverInterface *observer, |
| QNodeId nodeId, |
| ChangeFlags changeFlags) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| QObserverList &observerList = m_nodeObservations[nodeId]; |
| observerList.append(QObserverPair(changeFlags, observer)); |
| } |
| |
| void QChangeArbiter::unregisterObserver(QObserverInterface *observer, QNodeId nodeId) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| const auto it = m_nodeObservations.find(nodeId); |
| if (it != m_nodeObservations.end()) { |
| QObserverList &observers = it.value(); |
| for (int i = observers.count() - 1; i >= 0; i--) { |
| if (observers[i].second == observer) |
| observers.removeAt(i); |
| } |
| if (observers.isEmpty()) |
| m_nodeObservations.erase(it); |
| } |
| } |
| |
| void QChangeArbiter::sceneChangeEvent(const QSceneChangePtr &e) |
| { |
| // qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread(); |
| |
| // Add the change to the thread local storage queue - no locking required => yay! |
| QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData(); |
| localChangeQueue->push_back(e); |
| |
| emit receivedChange(); |
| |
| // qCDebug(ChangeArbiter) << "Change queue for thread" << QThread::currentThread() << "now contains" << localChangeQueue->count() << "items"; |
| } |
| |
| void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangePtr &e) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| sceneChangeEvent(e); |
| } |
| |
| void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangeList &e) |
| { |
| const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
| QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData(); |
| qCDebug(ChangeArbiter) << Q_FUNC_INFO << "Handles " << e.size() << " changes at once"; |
| localChangeQueue->insert(localChangeQueue->end(), e.begin(), e.end()); |
| |
| emit receivedChange(); |
| } |
| |
| void QChangeArbiter::addDirtyFrontEndNode(QNode *node) |
| { |
| if (!m_dirtyFrontEndNodes.contains(node)) { |
| m_dirtyFrontEndNodes += node; |
| emit receivedChange(); |
| } |
| } |
| |
| void QChangeArbiter::addDirtyFrontEndNode(QNode *node, QNode *subNode, const char *property, ChangeFlag change) |
| { |
| addDirtyFrontEndNode(node); |
| m_dirtySubNodeChanges.push_back({node, subNode, change, property}); |
| } |
| |
| void QChangeArbiter::removeDirtyFrontEndNode(QNode *node) |
| { |
| m_dirtyFrontEndNodes.removeOne(node); |
| m_dirtySubNodeChanges.erase(std::remove_if(m_dirtySubNodeChanges.begin(), m_dirtySubNodeChanges.end(), [node](const NodeRelationshipChange &elt) { |
| return elt.node == node || elt.subNode == node; |
| }), m_dirtySubNodeChanges.end()); |
| } |
| |
| QVector<QNode *> QChangeArbiter::takeDirtyFrontEndNodes() |
| { |
| return std::move(m_dirtyFrontEndNodes); |
| } |
| |
| QVector<NodeRelationshipChange> QChangeArbiter::takeDirtyFrontEndSubNodes() |
| { |
| return std::move(m_dirtySubNodeChanges); |
| } |
| |
| // Either we have the postman or we could make the QChangeArbiter agnostic to the postman |
| // but that would require adding it to every QObserverList in m_aspectObservations. |
| void QChangeArbiter::setPostman(QAbstractPostman *postman) |
| { |
| if (m_postman != postman) { |
| // Unregister old postman here if needed |
| m_postman = postman; |
| } |
| } |
| |
| void QChangeArbiter::createUnmanagedThreadLocalChangeQueue(void *changeArbiter) |
| { |
| Q_ASSERT(changeArbiter); |
| |
| QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter); |
| |
| qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread(); |
| if (!arbiter->tlsChangeQueue()->hasLocalData()) { |
| QChangeQueue *localChangeQueue = new QChangeQueue; |
| arbiter->tlsChangeQueue()->setLocalData(localChangeQueue); |
| arbiter->appendLockingChangeQueue(localChangeQueue); |
| } |
| } |
| |
| void QChangeArbiter::destroyUnmanagedThreadLocalChangeQueue(void *changeArbiter) |
| { |
| Q_ASSERT(changeArbiter); |
| |
| QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter); |
| qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread(); |
| if (arbiter->tlsChangeQueue()->hasLocalData()) { |
| QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData(); |
| arbiter->removeLockingChangeQueue(localChangeQueue); |
| arbiter->tlsChangeQueue()->setLocalData(nullptr); |
| } |
| } |
| |
| void QChangeArbiter::createThreadLocalChangeQueue(void *changeArbiter) |
| { |
| Q_CHECK_PTR(changeArbiter); |
| |
| QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter); |
| |
| qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread(); |
| if (!arbiter->tlsChangeQueue()->hasLocalData()) { |
| QChangeQueue *localChangeQueue = new QChangeQueue; |
| arbiter->tlsChangeQueue()->setLocalData(localChangeQueue); |
| arbiter->appendChangeQueue(localChangeQueue); |
| } |
| } |
| |
| void QChangeArbiter::destroyThreadLocalChangeQueue(void *changeArbiter) |
| { |
| // TODO: Implement me! |
| Q_UNUSED(changeArbiter); |
| QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter); |
| if (arbiter->tlsChangeQueue()->hasLocalData()) { |
| QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData(); |
| arbiter->removeChangeQueue(localChangeQueue); |
| arbiter->tlsChangeQueue()->setLocalData(nullptr); |
| } |
| } |
| |
| } // namespace Qt3DCore |
| |
| QT_END_NAMESPACE |