1/****************************************************************************
2**
3** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB).
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt3D module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qchangearbiter_p.h"
41
42#include <Qt3DCore/qcomponent.h>
43#include <QtCore/QMutexLocker>
44#include <QtCore/QReadLocker>
45#include <QtCore/QThread>
46#include <QtCore/QWriteLocker>
47
48#include <Qt3DCore/private/corelogging_p.h>
49#include <Qt3DCore/private/qabstractaspectjobmanager_p.h>
50#include <Qt3DCore/private/qpostman_p.h>
51#include <Qt3DCore/private/qscene_p.h>
52
53#include <mutex>
54
55QT_BEGIN_NAMESPACE
56
57namespace Qt3DCore {
58
59/* !\internal
60 \class Qt3DCore::QChangeArbiter
61 \inmodule Qt3DCore
62 \since 5.5
63
64 \brief Acts as a message router between observables and observers.
65
66 Observables can be of two types: QNode observables and \l {QObservableInterface}s.
67 QNode notifications are sent from the frontend QNode and delivered to the backend observers.
68 QObservableInterface notifications are sent from backend nodes to backend observers and/or to the
69 registered QPostman, which in turn delivers the notifications to the target frontend QNode.
70
71 QNode observables are registered automatically. However, QObservableInterface object have to be registered manually
72 by providing the QNodeId of the corresponding frontend QNode.
73
74 Observers can be registered to receive messages from a QObservableInterface/QNode observable by providing a QNode NodeUuid.
75 When a notification from a QObservableInterface is received, it is then sent to all observers observing the
76 QNode NodeUuid as well as the QPostman to update the frontend QNode.
77*/
78QChangeArbiter::QChangeArbiter(QObject *parent)
79 : QObject(parent)
80 , m_jobManager(nullptr)
81 , m_postman(nullptr)
82 , m_scene(nullptr)
83{
84 // The QMutex has to be recursive to handle the case where :
85 // 1) SyncChanges is called, mutex is locked
86 // 2) Changes are distributed
87 // 3) An observer decides to register a new observable upon receiving notification
88 // 4) registerObserver locks the mutex once again -> we need recursion otherwise deadlock
89 // 5) Mutex is unlocked - leaving registerObserver
90 // 6) Mutex is unlocked - leaving SyncChanges
91}
92
93QChangeArbiter::~QChangeArbiter()
94{
95 if (m_jobManager != nullptr)
96 m_jobManager->waitForPerThreadFunction(func: QChangeArbiter::destroyThreadLocalChangeQueue, arg: this);
97 m_lockingChangeQueues.clear();
98 m_changeQueues.clear();
99}
100
101void QChangeArbiter::initialize(QAbstractAspectJobManager *jobManager)
102{
103 Q_CHECK_PTR(jobManager);
104 m_jobManager = jobManager;
105
106 // Init TLS for the change queues
107 m_jobManager->waitForPerThreadFunction(func: QChangeArbiter::createThreadLocalChangeQueue, arg: this);
108}
109
110void QChangeArbiter::distributeQueueChanges(QChangeQueue *changeQueue)
111{
112 for (int i = 0, n = int(changeQueue->size()); i < n; i++) {
113 QSceneChangePtr& change = (*changeQueue)[i];
114 // Lookup which observers care about the subject this change came from
115 // and distribute the change to them
116 if (change.isNull())
117 continue;
118
119 if (change->type() == NodeCreated || change->type() == NodeDeleted) {
120 Q_ASSERT(false); // messages no longer used
121 }
122
123 const QNodeId nodeId = change->subjectId();
124 const auto it = m_nodeObservations.constFind(akey: nodeId);
125 if (it != m_nodeObservations.cend()) {
126 const QObserverList &observers = it.value();
127 for (const QObserverPair &observer : observers) {
128 if ((change->type() & observer.first) &&
129 (change->deliveryFlags() & QSceneChange::BackendNodes))
130 observer.second->sceneChangeEvent(e: change);
131 }
132 // Also send change to the postman
133 if (change->deliveryFlags() & QSceneChange::Nodes) {
134 // Check if QNode actually cares about the change
135 if (m_postman->shouldNotifyFrontend(change))
136 m_postman->sceneChangeEvent(e: change);
137 }
138 }
139 }
140 changeQueue->clear();
141}
142
143QThreadStorage<QChangeArbiter::QChangeQueue *> *QChangeArbiter::tlsChangeQueue()
144{
145 return &(m_tlsChangeQueue);
146}
147
148void QChangeArbiter::appendChangeQueue(QChangeArbiter::QChangeQueue *queue)
149{
150 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
151 m_changeQueues.append(t: queue);
152}
153
154void QChangeArbiter::removeChangeQueue(QChangeArbiter::QChangeQueue *queue)
155{
156 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
157 m_changeQueues.removeOne(t: queue);
158}
159
160void QChangeArbiter::appendLockingChangeQueue(QChangeArbiter::QChangeQueue *queue)
161{
162 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
163 m_lockingChangeQueues.append(t: queue);
164}
165
166void QChangeArbiter::removeLockingChangeQueue(QChangeArbiter::QChangeQueue *queue)
167{
168 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
169 m_lockingChangeQueues.removeOne(t: queue);
170}
171
172void QChangeArbiter::syncChanges()
173{
174 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
175
176 bool hasChanges = false;
177 for (QChangeArbiter::QChangeQueue *changeQueue : qAsConst(t&: m_changeQueues)) {
178 hasChanges |= !changeQueue->empty();
179 distributeQueueChanges(changeQueue);
180 }
181
182 for (QChangeQueue *changeQueue : qAsConst(t&: m_lockingChangeQueues)) {
183 hasChanges |= !changeQueue->empty();
184 distributeQueueChanges(changeQueue);
185 }
186
187 if (hasChanges)
188 emit syncedChanges();
189}
190
191void QChangeArbiter::setScene(QScene *scene)
192{
193 m_scene = scene;
194}
195
196QAbstractPostman *QChangeArbiter::postman() const
197{
198 return m_postman;
199}
200
201QScene *QChangeArbiter::scene() const
202{
203 return m_scene;
204}
205
206void QChangeArbiter::registerObserver(QObserverInterface *observer,
207 QNodeId nodeId,
208 ChangeFlags changeFlags)
209{
210 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
211 QObserverList &observerList = m_nodeObservations[nodeId];
212 observerList.append(t: QObserverPair(changeFlags, observer));
213}
214
215void QChangeArbiter::unregisterObserver(QObserverInterface *observer, QNodeId nodeId)
216{
217 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
218 const auto it = m_nodeObservations.find(akey: nodeId);
219 if (it != m_nodeObservations.end()) {
220 QObserverList &observers = it.value();
221 for (int i = observers.count() - 1; i >= 0; i--) {
222 if (observers[i].second == observer)
223 observers.removeAt(i);
224 }
225 if (observers.isEmpty())
226 m_nodeObservations.erase(it);
227 }
228}
229
230void QChangeArbiter::sceneChangeEvent(const QSceneChangePtr &e)
231{
232 // qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
233
234 // Add the change to the thread local storage queue - no locking required => yay!
235 QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData();
236 localChangeQueue->push_back(x: e);
237
238 emit receivedChange();
239
240 // qCDebug(ChangeArbiter) << "Change queue for thread" << QThread::currentThread() << "now contains" << localChangeQueue->count() << "items";
241}
242
243void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangePtr &e)
244{
245 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
246 sceneChangeEvent(e);
247}
248
249void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangeList &e)
250{
251 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
252 QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData();
253 qCDebug(ChangeArbiter) << Q_FUNC_INFO << "Handles " << e.size() << " changes at once";
254 localChangeQueue->insert(position: localChangeQueue->end(), first: e.begin(), last: e.end());
255
256 emit receivedChange();
257}
258
259void QChangeArbiter::addDirtyFrontEndNode(QNode *node)
260{
261 if (!m_dirtyFrontEndNodes.contains(t: node)) {
262 m_dirtyFrontEndNodes += node;
263 emit receivedChange();
264 }
265}
266
267void QChangeArbiter::addDirtyFrontEndNode(QNode *node, QNode *subNode, const char *property, ChangeFlag change)
268{
269 addDirtyFrontEndNode(node);
270 m_dirtySubNodeChanges.push_back(t: {.node: node, .subNode: subNode, .change: change, .property: property});
271}
272
273void QChangeArbiter::removeDirtyFrontEndNode(QNode *node)
274{
275 m_dirtyFrontEndNodes.removeOne(t: node);
276 m_dirtySubNodeChanges.erase(abegin: std::remove_if(first: m_dirtySubNodeChanges.begin(), last: m_dirtySubNodeChanges.end(), pred: [node](const NodeRelationshipChange &elt) {
277 return elt.node == node || elt.subNode == node;
278 }), aend: m_dirtySubNodeChanges.end());
279}
280
281QVector<QNode *> QChangeArbiter::takeDirtyFrontEndNodes()
282{
283 return std::move(m_dirtyFrontEndNodes);
284}
285
286QVector<NodeRelationshipChange> QChangeArbiter::takeDirtyFrontEndSubNodes()
287{
288 return std::move(m_dirtySubNodeChanges);
289}
290
291// Either we have the postman or we could make the QChangeArbiter agnostic to the postman
292// but that would require adding it to every QObserverList in m_aspectObservations.
293void QChangeArbiter::setPostman(QAbstractPostman *postman)
294{
295 if (m_postman != postman) {
296 // Unregister old postman here if needed
297 m_postman = postman;
298 }
299}
300
301void QChangeArbiter::createUnmanagedThreadLocalChangeQueue(void *changeArbiter)
302{
303 Q_ASSERT(changeArbiter);
304
305 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
306
307 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
308 if (!arbiter->tlsChangeQueue()->hasLocalData()) {
309 QChangeQueue *localChangeQueue = new QChangeQueue;
310 arbiter->tlsChangeQueue()->setLocalData(localChangeQueue);
311 arbiter->appendLockingChangeQueue(queue: localChangeQueue);
312 }
313}
314
315void QChangeArbiter::destroyUnmanagedThreadLocalChangeQueue(void *changeArbiter)
316{
317 Q_ASSERT(changeArbiter);
318
319 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
320 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
321 if (arbiter->tlsChangeQueue()->hasLocalData()) {
322 QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData();
323 arbiter->removeLockingChangeQueue(queue: localChangeQueue);
324 arbiter->tlsChangeQueue()->setLocalData(nullptr);
325 }
326}
327
328void QChangeArbiter::createThreadLocalChangeQueue(void *changeArbiter)
329{
330 Q_CHECK_PTR(changeArbiter);
331
332 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
333
334 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
335 if (!arbiter->tlsChangeQueue()->hasLocalData()) {
336 QChangeQueue *localChangeQueue = new QChangeQueue;
337 arbiter->tlsChangeQueue()->setLocalData(localChangeQueue);
338 arbiter->appendChangeQueue(queue: localChangeQueue);
339 }
340}
341
342void QChangeArbiter::destroyThreadLocalChangeQueue(void *changeArbiter)
343{
344 // TODO: Implement me!
345 Q_UNUSED(changeArbiter);
346 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
347 if (arbiter->tlsChangeQueue()->hasLocalData()) {
348 QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData();
349 arbiter->removeChangeQueue(queue: localChangeQueue);
350 arbiter->tlsChangeQueue()->setLocalData(nullptr);
351 }
352}
353
354} // namespace Qt3DCore
355
356QT_END_NAMESPACE
357

source code of qt3d/src/core/qchangearbiter.cpp