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#include <Qt3DCore/private/qsceneobserverinterface_p.h>
53
54#include <mutex>
55
56QT_BEGIN_NAMESPACE
57
58namespace Qt3DCore {
59
60/* !\internal
61 \class Qt3DCore::QChangeArbiter
62 \inmodule Qt3DCore
63 \since 5.5
64
65 \brief Acts as a message router between observables and observers.
66
67 Observables can be of two types: QNode observables and \l {QObservableInterface}s.
68 QNode notifications are sent from the frontend QNode and delivered to the backend observers.
69 QObservableInterface notifications are sent from backend nodes to backend observers and/or to the
70 registered QPostman, which in turn delivers the notifications to the target frontend QNode.
71
72 QNode observables are registered automatically. However, QObservableInterface object have to be registered manually
73 by providing the QNodeId of the corresponding frontend QNode.
74
75 Observers can be registered to receive messages from a QObservableInterface/QNode observable by providing a QNode NodeUuid.
76 When a notification from a QObservableInterface is received, it is then sent to all observers observing the
77 QNode NodeUuid as well as the QPostman to update the frontend QNode.
78*/
79QChangeArbiter::QChangeArbiter(QObject *parent)
80 : QObject(parent)
81 , m_jobManager(nullptr)
82 , m_postman(nullptr)
83 , m_scene(nullptr)
84{
85 // The QMutex has to be recursive to handle the case where :
86 // 1) SyncChanges is called, mutex is locked
87 // 2) Changes are distributed
88 // 3) An observer decides to register a new observable upon receiving notification
89 // 4) registerObserver locks the mutex once again -> we need recursion otherwise deadlock
90 // 5) Mutex is unlocked - leaving registerObserver
91 // 6) Mutex is unlocked - leaving SyncChanges
92}
93
94QChangeArbiter::~QChangeArbiter()
95{
96 if (m_jobManager != nullptr)
97 m_jobManager->waitForPerThreadFunction(QChangeArbiter::destroyThreadLocalChangeQueue, this);
98 m_lockingChangeQueues.clear();
99 m_changeQueues.clear();
100}
101
102void QChangeArbiter::initialize(QAbstractAspectJobManager *jobManager)
103{
104 Q_CHECK_PTR(jobManager);
105 m_jobManager = jobManager;
106
107 // Init TLS for the change queues
108 m_jobManager->waitForPerThreadFunction(QChangeArbiter::createThreadLocalChangeQueue, this);
109}
110
111void QChangeArbiter::distributeQueueChanges(QChangeQueue *changeQueue)
112{
113 for (int i = 0, n = int(changeQueue->size()); i < n; i++) {
114 QSceneChangePtr& change = (*changeQueue)[i];
115 // Lookup which observers care about the subject this change came from
116 // and distribute the change to them
117 if (change.isNull())
118 continue;
119
120 if (change->type() == NodeCreated) {
121 for (QSceneObserverInterface *observer : qAsConst(m_sceneObservers))
122 observer->sceneNodeAdded(change);
123 } else if (change->type() == NodeDeleted) {
124 for (QSceneObserverInterface *observer : qAsConst(m_sceneObservers))
125 observer->sceneNodeRemoved(change);
126 }
127
128 const QNodeId nodeId = change->subjectId();
129 const auto it = m_nodeObservations.constFind(nodeId);
130 if (it != m_nodeObservations.cend()) {
131 const QObserverList &observers = it.value();
132 for (const QObserverPair &observer : observers) {
133 if ((change->type() & observer.first) &&
134 (change->deliveryFlags() & QSceneChange::BackendNodes))
135 observer.second->sceneChangeEvent(change);
136 }
137 // Also send change to the postman
138 if (change->deliveryFlags() & QSceneChange::Nodes) {
139 // Check if QNode actually cares about the change
140 if (m_postman->shouldNotifyFrontend(change))
141 m_postman->sceneChangeEvent(change);
142 }
143 }
144 }
145 changeQueue->clear();
146}
147
148QThreadStorage<QChangeArbiter::QChangeQueue *> *QChangeArbiter::tlsChangeQueue()
149{
150 return &(m_tlsChangeQueue);
151}
152
153void QChangeArbiter::appendChangeQueue(QChangeArbiter::QChangeQueue *queue)
154{
155 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
156 m_changeQueues.append(queue);
157}
158
159void QChangeArbiter::removeChangeQueue(QChangeArbiter::QChangeQueue *queue)
160{
161 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
162 m_changeQueues.removeOne(queue);
163}
164
165void QChangeArbiter::appendLockingChangeQueue(QChangeArbiter::QChangeQueue *queue)
166{
167 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
168 m_lockingChangeQueues.append(queue);
169}
170
171void QChangeArbiter::removeLockingChangeQueue(QChangeArbiter::QChangeQueue *queue)
172{
173 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
174 m_lockingChangeQueues.removeOne(queue);
175}
176
177void QChangeArbiter::syncChanges()
178{
179 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
180 for (QChangeArbiter::QChangeQueue *changeQueue : qAsConst(m_changeQueues))
181 distributeQueueChanges(changeQueue);
182
183 for (QChangeQueue *changeQueue : qAsConst(m_lockingChangeQueues))
184 distributeQueueChanges(changeQueue);
185}
186
187void QChangeArbiter::setScene(QScene *scene)
188{
189 m_scene = scene;
190}
191
192QAbstractPostman *QChangeArbiter::postman() const
193{
194 return m_postman;
195}
196
197QScene *QChangeArbiter::scene() const
198{
199 return m_scene;
200}
201
202void QChangeArbiter::registerObserver(QObserverInterface *observer,
203 QNodeId nodeId,
204 ChangeFlags changeFlags)
205{
206 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
207 QObserverList &observerList = m_nodeObservations[nodeId];
208 observerList.append(QObserverPair(changeFlags, observer));
209}
210
211// Called from the QAspectThread context, no need to lock
212void QChangeArbiter::registerSceneObserver(QSceneObserverInterface *observer)
213{
214 if (!m_sceneObservers.contains(observer))
215 m_sceneObservers << observer;
216}
217
218void QChangeArbiter::unregisterObserver(QObserverInterface *observer, QNodeId nodeId)
219{
220 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
221 const auto it = m_nodeObservations.find(nodeId);
222 if (it != m_nodeObservations.end()) {
223 QObserverList &observers = it.value();
224 for (int i = observers.count() - 1; i >= 0; i--) {
225 if (observers[i].second == observer)
226 observers.removeAt(i);
227 }
228 if (observers.isEmpty())
229 m_nodeObservations.erase(it);
230 }
231}
232
233// Called from the QAspectThread context, no need to lock
234void QChangeArbiter::unregisterSceneObserver(QSceneObserverInterface *observer)
235{
236 if (observer != nullptr)
237 m_sceneObservers.removeOne(observer);
238}
239
240void QChangeArbiter::sceneChangeEvent(const QSceneChangePtr &e)
241{
242 // qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
243
244 // Add the change to the thread local storage queue - no locking required => yay!
245 QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData();
246 localChangeQueue->push_back(e);
247
248 emit receivedChange();
249
250 // qCDebug(ChangeArbiter) << "Change queue for thread" << QThread::currentThread() << "now contains" << localChangeQueue->count() << "items";
251}
252
253void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangePtr &e)
254{
255 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
256 sceneChangeEvent(e);
257}
258
259void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangeList &e)
260{
261 const std::lock_guard<QRecursiveMutex> locker(m_mutex);;
262 QChangeQueue *localChangeQueue = m_tlsChangeQueue.localData();
263 qCDebug(ChangeArbiter) << Q_FUNC_INFO << "Handles " << e.size() << " changes at once";
264 localChangeQueue->insert(localChangeQueue->end(), e.begin(), e.end());
265
266 emit receivedChange();
267}
268
269// Either we have the postman or we could make the QChangeArbiter agnostic to the postman
270// but that would require adding it to every QObserverList in m_aspectObservations.
271void QChangeArbiter::setPostman(QAbstractPostman *postman)
272{
273 if (m_postman != postman) {
274 // Unregister old postman here if needed
275 m_postman = postman;
276 }
277}
278
279void QChangeArbiter::createUnmanagedThreadLocalChangeQueue(void *changeArbiter)
280{
281 Q_ASSERT(changeArbiter);
282
283 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
284
285 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
286 if (!arbiter->tlsChangeQueue()->hasLocalData()) {
287 QChangeQueue *localChangeQueue = new QChangeQueue;
288 arbiter->tlsChangeQueue()->setLocalData(localChangeQueue);
289 arbiter->appendLockingChangeQueue(localChangeQueue);
290 }
291}
292
293void QChangeArbiter::destroyUnmanagedThreadLocalChangeQueue(void *changeArbiter)
294{
295 Q_ASSERT(changeArbiter);
296
297 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
298 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
299 if (arbiter->tlsChangeQueue()->hasLocalData()) {
300 QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData();
301 arbiter->removeLockingChangeQueue(localChangeQueue);
302 arbiter->tlsChangeQueue()->setLocalData(nullptr);
303 }
304}
305
306void QChangeArbiter::createThreadLocalChangeQueue(void *changeArbiter)
307{
308 Q_CHECK_PTR(changeArbiter);
309
310 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
311
312 qCDebug(ChangeArbiter) << Q_FUNC_INFO << QThread::currentThread();
313 if (!arbiter->tlsChangeQueue()->hasLocalData()) {
314 QChangeQueue *localChangeQueue = new QChangeQueue;
315 arbiter->tlsChangeQueue()->setLocalData(localChangeQueue);
316 arbiter->appendChangeQueue(localChangeQueue);
317 }
318}
319
320void QChangeArbiter::destroyThreadLocalChangeQueue(void *changeArbiter)
321{
322 // TODO: Implement me!
323 Q_UNUSED(changeArbiter);
324 QChangeArbiter *arbiter = static_cast<QChangeArbiter *>(changeArbiter);
325 if (arbiter->tlsChangeQueue()->hasLocalData()) {
326 QChangeQueue *localChangeQueue = arbiter->tlsChangeQueue()->localData();
327 arbiter->removeChangeQueue(localChangeQueue);
328 arbiter->tlsChangeQueue()->setLocalData(nullptr);
329 }
330}
331
332} // namespace Qt3DCore
333
334QT_END_NAMESPACE
335