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 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | namespace 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 | */ |
78 | QChangeArbiter::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 | |
93 | QChangeArbiter::~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 | |
101 | void 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 | |
110 | void 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 | |
143 | QThreadStorage<QChangeArbiter::QChangeQueue *> *QChangeArbiter::tlsChangeQueue() |
144 | { |
145 | return &(m_tlsChangeQueue); |
146 | } |
147 | |
148 | void QChangeArbiter::appendChangeQueue(QChangeArbiter::QChangeQueue *queue) |
149 | { |
150 | const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
151 | m_changeQueues.append(t: queue); |
152 | } |
153 | |
154 | void QChangeArbiter::removeChangeQueue(QChangeArbiter::QChangeQueue *queue) |
155 | { |
156 | const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
157 | m_changeQueues.removeOne(t: queue); |
158 | } |
159 | |
160 | void QChangeArbiter::appendLockingChangeQueue(QChangeArbiter::QChangeQueue *queue) |
161 | { |
162 | const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
163 | m_lockingChangeQueues.append(t: queue); |
164 | } |
165 | |
166 | void QChangeArbiter::removeLockingChangeQueue(QChangeArbiter::QChangeQueue *queue) |
167 | { |
168 | const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
169 | m_lockingChangeQueues.removeOne(t: queue); |
170 | } |
171 | |
172 | void 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 | |
191 | void QChangeArbiter::setScene(QScene *scene) |
192 | { |
193 | m_scene = scene; |
194 | } |
195 | |
196 | QAbstractPostman *QChangeArbiter::postman() const |
197 | { |
198 | return m_postman; |
199 | } |
200 | |
201 | QScene *QChangeArbiter::scene() const |
202 | { |
203 | return m_scene; |
204 | } |
205 | |
206 | void 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 | |
215 | void 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 | |
230 | void 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 | |
243 | void QChangeArbiter::sceneChangeEventWithLock(const QSceneChangePtr &e) |
244 | { |
245 | const std::lock_guard<QRecursiveMutex> locker(m_mutex);; |
246 | sceneChangeEvent(e); |
247 | } |
248 | |
249 | void 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 | |
259 | void QChangeArbiter::addDirtyFrontEndNode(QNode *node) |
260 | { |
261 | if (!m_dirtyFrontEndNodes.contains(t: node)) { |
262 | m_dirtyFrontEndNodes += node; |
263 | emit receivedChange(); |
264 | } |
265 | } |
266 | |
267 | void 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 | |
273 | void 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 | |
281 | QVector<QNode *> QChangeArbiter::takeDirtyFrontEndNodes() |
282 | { |
283 | return std::move(m_dirtyFrontEndNodes); |
284 | } |
285 | |
286 | QVector<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. |
293 | void QChangeArbiter::setPostman(QAbstractPostman *postman) |
294 | { |
295 | if (m_postman != postman) { |
296 | // Unregister old postman here if needed |
297 | m_postman = postman; |
298 | } |
299 | } |
300 | |
301 | void 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 | |
315 | void 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 | |
328 | void 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 | |
342 | void 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 | |
356 | QT_END_NAMESPACE |
357 | |