1 | // Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QT3DCORE_QABSTRACTRESOURCESMANAGER_H |
5 | #define QT3DCORE_QABSTRACTRESOURCESMANAGER_H |
6 | |
7 | // |
8 | // W A R N I N G |
9 | // ------------- |
10 | // |
11 | // This file is not part of the Qt API. It exists for the convenience |
12 | // of other Qt classes. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <QtCore/QHash> |
19 | #include <QtCore/QMutex> |
20 | #include <QtCore/QReadLocker> |
21 | #include <QtCore/QReadWriteLock> |
22 | #include <QtCore/QtGlobal> |
23 | #include <limits> |
24 | |
25 | #include <Qt3DCore/private/qhandle_p.h> |
26 | #include <Qt3DCore/private/qt3dcore_global_p.h> |
27 | |
28 | // Silence complaints about unreferenced local variables in |
29 | // ArrayAllocatingPolicy::deallocateBuckets() when the compiler |
30 | // inlines the call to the dtor and it is empty. Default warning |
31 | // setting re-enabled at bottom of this file |
32 | #if defined(Q_CC_MSVC) |
33 | #pragma warning(disable : 4189) |
34 | #endif |
35 | |
36 | QT_BEGIN_NAMESPACE |
37 | |
38 | namespace Qt3DCore { |
39 | |
40 | template <class Host> |
41 | struct NonLockingPolicy |
42 | { |
43 | struct ReadLocker |
44 | { |
45 | ReadLocker(const NonLockingPolicy*) {} |
46 | void unlock() {} |
47 | void relock() {} |
48 | }; |
49 | |
50 | struct WriteLocker |
51 | { |
52 | WriteLocker(const NonLockingPolicy*) {} |
53 | void unlock() {} |
54 | void relock() {} |
55 | }; |
56 | |
57 | struct Locker |
58 | { |
59 | Locker(const NonLockingPolicy*) {} |
60 | void unlock() {} |
61 | void relock() {} |
62 | }; |
63 | }; |
64 | |
65 | template <class Host> |
66 | class ObjectLevelLockingPolicy |
67 | { |
68 | public : |
69 | ObjectLevelLockingPolicy() |
70 | {} |
71 | |
72 | class ReadLocker |
73 | { |
74 | public: |
75 | ReadLocker(const ObjectLevelLockingPolicy *host) |
76 | : m_locker(&host->m_readWritelock) |
77 | { } |
78 | |
79 | void unlock() |
80 | { |
81 | m_locker.unlock(); |
82 | } |
83 | |
84 | void relock() |
85 | { |
86 | m_locker.relock(); |
87 | } |
88 | |
89 | private: |
90 | QReadLocker m_locker; |
91 | }; |
92 | |
93 | class WriteLocker |
94 | { |
95 | public: |
96 | WriteLocker(const ObjectLevelLockingPolicy *host) |
97 | : m_locker(&host->m_readWritelock) |
98 | { } |
99 | |
100 | void unlock() |
101 | { |
102 | m_locker.unlock(); |
103 | } |
104 | |
105 | void relock() |
106 | { |
107 | m_locker.relock(); |
108 | } |
109 | |
110 | private: |
111 | QWriteLocker m_locker; |
112 | }; |
113 | |
114 | class Locker |
115 | { |
116 | public: |
117 | Locker(const ObjectLevelLockingPolicy *host) |
118 | : m_locker(&host->m_lock) |
119 | { } |
120 | |
121 | void unlock() |
122 | { |
123 | m_locker.unlock(); |
124 | } |
125 | |
126 | void relock() |
127 | { |
128 | m_locker.relock(); |
129 | } |
130 | |
131 | private: |
132 | QMutexLocker<QMutex> m_locker; |
133 | }; |
134 | |
135 | private: |
136 | friend class Locker; |
137 | friend class ReadLocker; |
138 | friend class WriteLocker; |
139 | mutable QReadWriteLock m_readWritelock; |
140 | mutable QMutex m_lock; |
141 | }; |
142 | |
143 | template <typename T> |
144 | struct QResourceInfo |
145 | { |
146 | static const bool needsCleanup = false; |
147 | }; |
148 | |
149 | enum |
150 | { |
151 | Q_REQUIRES_CLEANUP = 0 |
152 | }; |
153 | |
154 | #define Q_DECLARE_RESOURCE_INFO(TYPE, FLAGS) \ |
155 | namespace Qt3DCore { \ |
156 | template<> \ |
157 | struct QResourceInfo<TYPE > \ |
158 | { \ |
159 | static const bool needsCleanup = (FLAGS & Q_REQUIRES_CLEANUP) == 0;\ |
160 | }; \ |
161 | } // namespace Qt3DCore |
162 | |
163 | template<typename T> |
164 | class QHandleData : public QHandle<T>::Data |
165 | { |
166 | public: |
167 | T data; |
168 | }; |
169 | |
170 | template<typename T> |
171 | inline T *QHandle<T>::operator->() const { return (d && counter == d->counter) ? &static_cast<QHandleData<T> *>(d)->data : nullptr; } |
172 | template<typename T> |
173 | inline T *QHandle<T>::data() const { return (d && counter == d->counter) ? &static_cast<QHandleData<T> *>(d)->data : nullptr; } |
174 | |
175 | |
176 | class Q_3DCORE_PRIVATE_EXPORT AlignedAllocator |
177 | { |
178 | public: |
179 | static void *allocate(uint size); |
180 | static void release(void *p); |
181 | }; |
182 | |
183 | template <typename T> |
184 | class ArrayAllocatingPolicy |
185 | { |
186 | public: |
187 | typedef QHandleData<T> HandleData; |
188 | typedef QHandle<T> Handle; |
189 | ArrayAllocatingPolicy() |
190 | { |
191 | } |
192 | |
193 | ~ArrayAllocatingPolicy() |
194 | { |
195 | m_activeHandles.clear(); |
196 | deallocateBuckets(); |
197 | } |
198 | |
199 | Handle allocateResource() |
200 | { |
201 | if (!freeList) |
202 | allocateBucket(); |
203 | typename Handle::Data *d = freeList; |
204 | freeList = freeList->nextFree; |
205 | d->counter = allocCounter; |
206 | allocCounter += 2; // ensure this will never clash with a pointer in nextFree by keeping the lowest bit set |
207 | Handle handle(d); |
208 | m_activeHandles.push_back(handle); |
209 | return handle; |
210 | } |
211 | |
212 | void releaseResource(const Handle &handle) |
213 | { |
214 | m_activeHandles.erase(std::remove(m_activeHandles.begin(), m_activeHandles.end(), handle), m_activeHandles.end()); |
215 | typename Handle::Data *d = handle.data_ptr(); |
216 | d->nextFree = freeList; |
217 | freeList = d; |
218 | performCleanup(&static_cast<QHandleData<T> *>(d)->data, std::integral_constant<bool, QResourceInfo<T>::needsCleanup>{}); |
219 | } |
220 | |
221 | T *data(Handle h) |
222 | { |
223 | return h.operator->(); |
224 | } |
225 | |
226 | void for_each(std::function<void(T*)> f) |
227 | { |
228 | Bucket *b = firstBucket; |
229 | while (b) { |
230 | for (int idx = 0; idx < Bucket::NumEntries; ++idx) { |
231 | T *t = &b->data[idx].data; |
232 | f(t); |
233 | } |
234 | b = b->header.next; |
235 | } |
236 | } |
237 | |
238 | int count() const { return int(m_activeHandles.size()); } |
239 | const std::vector<Handle> &activeHandles() const { return m_activeHandles; } |
240 | |
241 | private: |
242 | Q_DISABLE_COPY(ArrayAllocatingPolicy) |
243 | struct Bucket |
244 | { |
245 | struct |
246 | { |
247 | Bucket *; |
248 | } ; |
249 | enum { |
250 | Size = 4096, |
251 | NumEntries = (Size - sizeof(Header)) / sizeof(HandleData) |
252 | }; |
253 | HandleData data[NumEntries]; |
254 | }; |
255 | |
256 | Bucket *firstBucket = nullptr; |
257 | std::vector<Handle> m_activeHandles; |
258 | typename Handle::Data *freeList = nullptr; |
259 | int allocCounter = 1; |
260 | |
261 | void allocateBucket() |
262 | { |
263 | // no free handle, allocate a new |
264 | // allocate aligned memory |
265 | Bucket *b = static_cast<Bucket *>(AlignedAllocator::allocate(size: sizeof(Bucket))); |
266 | |
267 | // placement new |
268 | new (b) Bucket; |
269 | |
270 | b->header.next = firstBucket; |
271 | firstBucket = b; |
272 | for (int i = 0; i < Bucket::NumEntries - 1; ++i) { |
273 | b->data[i].nextFree = &b->data[i + 1]; |
274 | } |
275 | b->data[Bucket::NumEntries - 1].nextFree = nullptr; |
276 | freeList = &b->data[0]; |
277 | } |
278 | |
279 | void deallocateBuckets() |
280 | { |
281 | Bucket *b = firstBucket; |
282 | while (b) { |
283 | Bucket *n = b->header.next; |
284 | // Call dtor explicitly |
285 | b->~Bucket(); |
286 | // Release aligned memory |
287 | AlignedAllocator::release(p: b); |
288 | b = n; |
289 | } |
290 | } |
291 | |
292 | template<typename Q = T> |
293 | void performCleanup(Q *r, std::integral_constant<bool, true>) |
294 | { |
295 | r->cleanup(); |
296 | } |
297 | |
298 | template<typename Q = T> |
299 | void performCleanup(Q *, std::integral_constant<bool, false>) |
300 | { |
301 | } |
302 | |
303 | }; |
304 | |
305 | #ifndef QT_NO_DEBUG_STREAM |
306 | template <typename ValueType, typename KeyType, |
307 | template <class> class LockingPolicy |
308 | > |
309 | class QResourceManager; |
310 | |
311 | template <typename ValueType, typename KeyType, |
312 | template <class> class LockingPolicy = NonLockingPolicy |
313 | > |
314 | QDebug operator<<(QDebug dbg, const QResourceManager<ValueType, KeyType, LockingPolicy> &manager); |
315 | #endif |
316 | |
317 | template <typename ValueType, typename KeyType, |
318 | template <class> class LockingPolicy = NonLockingPolicy |
319 | > |
320 | class QResourceManager |
321 | : public ArrayAllocatingPolicy<ValueType> |
322 | , public LockingPolicy< QResourceManager<ValueType, KeyType, LockingPolicy> > |
323 | { |
324 | public: |
325 | typedef ArrayAllocatingPolicy<ValueType> Allocator; |
326 | typedef QHandle<ValueType> Handle; |
327 | |
328 | QResourceManager() : |
329 | Allocator() |
330 | { |
331 | } |
332 | |
333 | ~QResourceManager() |
334 | {} |
335 | |
336 | Handle acquire() |
337 | { |
338 | typename LockingPolicy<QResourceManager>::WriteLocker lock(this); |
339 | return Allocator::allocateResource(); |
340 | } |
341 | |
342 | ValueType* data(const Handle &handle) |
343 | { |
344 | return handle.operator->(); |
345 | } |
346 | |
347 | void release(const Handle &handle) |
348 | { |
349 | typename LockingPolicy<QResourceManager>::WriteLocker lock(this); |
350 | Allocator::releaseResource(handle); |
351 | } |
352 | |
353 | bool contains(const KeyType &id) const |
354 | { |
355 | typename LockingPolicy<QResourceManager>::ReadLocker lock(this); |
356 | return m_keyToHandleMap.contains(id); |
357 | } |
358 | |
359 | Handle getOrAcquireHandle(const KeyType &id) |
360 | { |
361 | typename LockingPolicy<QResourceManager>::ReadLocker lock(this); |
362 | Handle handle = m_keyToHandleMap.value(id); |
363 | if (handle.isNull()) { |
364 | lock.unlock(); |
365 | typename LockingPolicy<QResourceManager>::WriteLocker writeLock(this); |
366 | // Test that the handle hasn't been set (in the meantime between the read unlock and the write lock) |
367 | Handle &handleToSet = m_keyToHandleMap[id]; |
368 | if (handleToSet.isNull()) { |
369 | handleToSet = Allocator::allocateResource(); |
370 | } |
371 | return handleToSet; |
372 | } |
373 | return handle; |
374 | } |
375 | |
376 | Handle lookupHandle(const KeyType &id) |
377 | { |
378 | typename LockingPolicy<QResourceManager>::ReadLocker lock(this); |
379 | return m_keyToHandleMap.value(id); |
380 | } |
381 | |
382 | ValueType *lookupResource(const KeyType &id) |
383 | { |
384 | ValueType* ret = nullptr; |
385 | { |
386 | typename LockingPolicy<QResourceManager>::ReadLocker lock(this); |
387 | Handle handle = m_keyToHandleMap.value(id); |
388 | if (!handle.isNull()) |
389 | ret = Allocator::data(handle); |
390 | } |
391 | return ret; |
392 | } |
393 | |
394 | ValueType *getOrCreateResource(const KeyType &id) |
395 | { |
396 | const Handle handle = getOrAcquireHandle(id); |
397 | return handle.operator->(); |
398 | } |
399 | |
400 | void releaseResource(const KeyType &id) |
401 | { |
402 | typename LockingPolicy<QResourceManager>::WriteLocker lock(this); |
403 | Handle handle = m_keyToHandleMap.take(id); |
404 | if (!handle.isNull()) |
405 | Allocator::releaseResource(handle); |
406 | } |
407 | |
408 | // Releases all resources referenced by a key |
409 | // Resources allocated manually with just a handle aren't releases |
410 | void releaseAllResources() |
411 | { |
412 | typename LockingPolicy<QResourceManager>::WriteLocker lock(this); |
413 | // Make a copy as releaseResource removes the entry in m_activeHanldes |
414 | const std::vector<Handle> activeHandles = Allocator::activeHandles(); |
415 | for (const Handle &h : activeHandles) |
416 | Allocator::releaseResource(h); |
417 | // Clear Key to Handle Map |
418 | m_keyToHandleMap.clear(); |
419 | } |
420 | |
421 | protected: |
422 | QHash<KeyType, Handle > m_keyToHandleMap; |
423 | |
424 | private: |
425 | friend QDebug operator<< <>(QDebug dbg, const QResourceManager<ValueType, KeyType, LockingPolicy> &manager); |
426 | }; |
427 | |
428 | #ifndef QT_NO_DEBUG_STREAM |
429 | template <typename ValueType, typename KeyType, |
430 | template <class> class LockingPolicy |
431 | > |
432 | QDebug operator<<(QDebug dbg, const QResourceManager<ValueType, KeyType, LockingPolicy> &manager) |
433 | { |
434 | QDebugStateSaver saver(dbg); |
435 | dbg << "Contains" << manager.count() << "items" << Qt::endl; |
436 | |
437 | dbg << "Key to Handle Map:" << Qt::endl; |
438 | const auto end = manager.m_keyToHandleMap.cend(); |
439 | for (auto it = manager.m_keyToHandleMap.cbegin(); it != end; ++it) |
440 | dbg << "QNodeId =" << it.key() << "Handle =" << it.value() << Qt::endl; |
441 | |
442 | // dbg << "Resources:" << Qt::endl; |
443 | // dbg << manager.m_handleManager; |
444 | return dbg; |
445 | } |
446 | #endif |
447 | |
448 | }// Qt3D |
449 | |
450 | QT_END_NAMESPACE |
451 | |
452 | #if defined(Q_CC_MSVC) |
453 | #pragma warning(default : 4189) |
454 | #endif |
455 | |
456 | #endif // QT3DCORE_QABSTRACTRESOURCESMANAGER_H |
457 | |