JPL Spatial
Sound spatialization and propagation library
Loading...
Searching...
No Matches
RealtimeObject.h
Go to the documentation of this file.
1//
2// ██╗██████╗ ██╗ ██╗██████╗ ███████╗
3// ██║██╔══██╗ ██║ ██║██╔══██╗██╔════╝ ** JPLSpatial **
4// ██║██████╔╝ ██║ ██║██████╔╝███████╗
5// ██ ██║██╔═══╝ ██║ ██║██╔══██╗╚════██║ https://github.com/Jaytheway/JPLSpatial
6// ╚█████╔╝██║ ███████╗██║██████╔╝███████║
7// ╚════╝ ╚═╝ ╚══════╝╚═╝╚═════╝ ╚══════╝
8//
9// Copyright Jaroslav Pevno, JPLSpatial is offered under the terms of the ISC license:
10//
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or
12// without fee is hereby granted, provided that the above copyright notice and this permission
13// notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
14// WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16// CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
17// WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
18// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19
20#pragma once
21
22#include <atomic>
23#include <cassert>
24#include <memory_resource>
25#include <type_traits>
26
27namespace JPL
28{
29//==============================================================================
30enum class ThreadType
31{
34};
35
41
42namespace detail
43{
44 // Forward declaration
45 template<typename T>
47
48 template<typename T>
50}
51
52// By default we remove lock in non-realtime access.
53// The object assumed to be only used by a single non-realtime thread.
54#ifndef JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
55#define JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK 0
56#endif
57
58//==============================================================================
67template <typename T, RealtimeObjectOptions Options>
69{
70public:
71 using allocator_type = std::pmr::polymorphic_allocator<>;
72
73public:
75 RealtimeObject() = default;
76
78 explicit RealtimeObject(const T& obj, const allocator_type& allocator = {}) : mImpl(obj, allocator) {}
79
81 explicit RealtimeObject(T&& obj, const allocator_type& allocator = {}) : mImpl(std::move(obj), allocator) {}
82
83 ~RealtimeObject() = default;
84
85 //==============================================================================
86 using RealtimeAcquireReturnType = std::conditional_t<Options == RealtimeObjectOptions::nonRealtimeMutatable, const T, T>;
87 using NonRealtimeAcquireReturnType = std::conditional_t<Options == RealtimeObjectOptions::realtimeMutatable, const T, T>;
88
89 //==============================================================================
98 RealtimeAcquireReturnType& realtimeAcquire() noexcept { return mImpl.realtimeAcquire(); }
99
104 void realtimeRelease() noexcept { mImpl.realtimeRelease(); }
105
109 template <typename... Args>
111 {
112 mImpl.realtimeReplace(std::forward<Args>(args)...);
113 }
114
115 //==============================================================================
124 NonRealtimeAcquireReturnType& nonRealtimeAcquire() { return mImpl.nonRealtimeAcquire(); }
125
131 void nonRealtimeRelease() { mImpl.nonRealtimeRelease(); }
132
136 template <typename... Args>
138 {
139 mImpl.nonRealtimeReplace(std::forward<Args>(args)...);
140 }
141
142 //==============================================================================
147 template <ThreadType threadType>
148 class ScopedAccess : public std::conditional_t<Options == RealtimeObjectOptions::realtimeMutatable,
149 detail::RealtimeMutatable<T>,
150 detail::NonRealtimeMutatable<T>>
151 ::template ScopedAccess<threadType == ThreadType::realtime>
152 {
153 public:
156
157#if defined(DOXYGEN) and DOXYGEN
158 // Various ways to get access to the underlying object.
159 // Non-const method are only supported on the realtime
160 // or non-realtime thread as indicated by the Options
161 // template argument
162 T* get() noexcept;
163 const T* get() const noexcept;
164 T& operator *() noexcept;
166 T* operator->() noexcept;
168#endif
169
170 //==============================================================================
171 ScopedAccess(const ScopedAccess&) = delete;
175 };
176private:
177 using Impl = std::conditional_t<Options == RealtimeObjectOptions::realtimeMutatable,
180 Impl mImpl;
181};
182
183} // namespace JPL
184
185//==============================================================================
186//
187// Code beyond this point is implementation detail...
188//
189//==============================================================================
190
191namespace JPL
192{
193 namespace detail
194 {
195 //======================================================================
196 // Forward declaration
197 template <typename, bool>
198 class NRMScopedAccessImpl;
199
200 template<typename T>
202 {
203 public:
204 using allocator_type = std::pmr::polymorphic_allocator<>;
205
206 public:
208 : mAllocator(allocator)
209 , storage(mAllocator.new_object<T>())
210 , pointer(storage)
211 {
212 }
213
215 : mAllocator(allocator)
216 , storage(mAllocator.new_object<T>(obj))
217 , pointer(storage)
218 {
219 }
220
222 : mAllocator(allocator)
223 , storage(mAllocator.new_object<T>(std::move(obj)))
224 , pointer(storage)
225 {
226 }
227
229 {
230 assert(pointer.load() != nullptr); // <- never delete this object while the realtime thread is holding the lock
231
232 // Spin util realtime-thread releases the object!
233 while (pointer.load() == nullptr);
234
235 if (storage)
236 mAllocator.delete_object(storage);
237 if (copy)
238 mAllocator.delete_object(copy);
239
240#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
241 auto accquired = nonRealtimeLock.try_lock();
242
243 ((void)(accquired));
244 assert(accquired); // <- you didn't call release on one of the non-realtime threads before deleting this object
245
246 nonRealtimeLock.unlock();
247#endif
248 }
249
250 template <typename... Args>
252 {
253 return RealtimeObject(T(std::forward<Args>(args)...));
254 }
255
257 {
258 assert(pointer.load() != nullptr); // <- You didn't balance your acquire and release calls!
259 currentObj = pointer.exchange(nullptr);
260 return *currentObj;
261 }
262
264 {
265 // You didn't balance your acquire and release calls
266 assert(pointer.load() == nullptr);
267
268 // replace back
269 pointer.store(currentObj);
270 }
271
273 {
274#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
275 nonRealtimeLock.lock();
276#endif
277 if (copy)
278 mAllocator.delete_object(copy);
279 // TODO: should we just destruct -> in-place construct?
280 copy = mAllocator.new_object<T>(*storage);
281
282 return *copy;
283 }
284
286 {
287 T* ptr;
288
289 // block until realtime thread is done using the object
290 do
291 {
292 ptr = storage;
293 } while (!pointer.compare_exchange_weak(ptr, copy));
294
295 // transfrer copy to storage
296 mAllocator.delete_object(storage);
297 storage = copy;
298 copy = nullptr;
299
300#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
301 nonRealtimeLock.unlock();
302#endif
303 }
304
305 template <typename... Args>
307 {
308#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
309 nonRealtimeLock.lock();
310#endif
311 if (copy)
312 mAllocator.delete_object(copy);
313 copy = mAllocator.new_object<T>(std::forward<Args>(args)...);
314
316 }
317
318 template <bool isRealtimeThread>
319 class ScopedAccess : public NRMScopedAccessImpl<T, isRealtimeThread>
320 {
321 public:
324 ScopedAccess(const ScopedAccess&) = delete;
328 };
329 private:
330 friend class NRMScopedAccessImpl<T, true>;
331 friend class NRMScopedAccessImpl<T, false>;
332 //explicit NonRealtimeMutatable(std::unique_ptr<T>&& u);
333
334 allocator_type mAllocator;
335
336 T* storage = nullptr;
337 std::atomic<T*> pointer;
338
339#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
340 std::mutex nonRealtimeLock;
341#endif
342 T* copy = nullptr;
343
344 // only accessed by realtime thread
345 T* currentObj = nullptr;
346 };
347
348 template <typename T, bool>
350 {
351 protected:
353 : p(parent), currentValue(&p.nonRealtimeAcquire())
354 {
355 }
356 ~NRMScopedAccessImpl() { p.nonRealtimeRelease(); }
357 public:
358 T* get() noexcept { return currentValue; }
359 const T* get() const noexcept { return currentValue; }
360 T& operator *() noexcept { return *currentValue; }
361 const T& operator *() const noexcept { return *currentValue; }
362 T* operator->() noexcept { return currentValue; }
363 const T* operator->() const noexcept { return currentValue; }
364 private:
366 T* currentValue;
367 };
368
369 template <typename T>
371 {
372 protected:
374 : p(parent), currentValue(&p.realtimeAcquire())
375 {
376 }
377 ~NRMScopedAccessImpl() noexcept { p.realtimeRelease(); }
378 public:
379 const T* get() const noexcept { return currentValue; }
380 const T& operator *() const noexcept { return *currentValue; }
381 const T* operator->() const noexcept { return currentValue; }
382 private:
384 const T* currentValue;
385 };
386
387 //======================================================================
388 template <typename, bool> class RMScopedAccessImpl;
389 template <typename T> class RealtimeMutatable
390 {
391 public:
392 RealtimeMutatable() = default;
393
394 explicit RealtimeMutatable(const T& obj) : data({ obj, obj }), realtimeCopy(obj) {}
395
397 {
398 assert((control.load() & BUSY_BIT) == 0); // <- never delete this object while the realtime thread is still using it
399
400 // Spin!
401 while ((control.load() & BUSY_BIT) == 1);
402
403#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
404 auto accquired = nonRealtimeLock.try_lock();
405
406 ((void)(accquired));
407 assert(accquired); // <- you didn't call release on one of the non-realtime threads before deleting this object
408
409 nonRealtimeLock.unlock();
410#endif
411 }
412
413 template <typename... Args>
415 {
416 return RealtimeObject(false, std::forward<Args>(args)...);
417 }
418
420 {
421 return realtimeCopy;
422 }
423
425 {
426 auto idx = acquireIndex();
427 data[idx] = realtimeCopy;
428 releaseIndex(idx);
429 }
430
431 template <typename... Args>
433 {
434 T obj(std::forward<Args>(args)...);
435
436 auto idx = acquireIndex();
437 data[idx] = std::move(obj);
438 releaseIndex(idx);
439 }
440
442 {
443#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
444 nonRealtimeLock.lock();
445#endif
446 auto current = control.load(std::memory_order_acquire);
447
448 // there is new data so flip the indices around atomically ensuring we are not inside realtimeAssign
449 if ((current & NEWDATA_BIT) != 0)
450 {
451 int newValue;
452
453 do
454 {
455 // expect the realtime thread not to be inside the realtime-assign
457
458 // realtime thread should flip index value and clear the newdata bit
459 newValue = (current ^ INDEX_BIT) & INDEX_BIT;
460 } while (!control.compare_exchange_weak(current, newValue, std::memory_order_acq_rel));
461
463 }
464
465 // flip the index bit as we always use the index that the realtime thread is currently NOT using
466 auto nonRealtimeIndex = (current & INDEX_BIT) ^ 1;
467
468 return data[nonRealtimeIndex];
469 }
470
472 {
473#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
474 nonRealtimeLock.unlock();
475#endif
476 }
477
478 template <bool isRealtimeThread>
479 class ScopedAccess : public RMScopedAccessImpl<T, isRealtimeThread>
480 {
481 public:
483 ScopedAccess(const ScopedAccess&) = delete;
487 };
488 private:
489 friend class RMScopedAccessImpl<T, false>;
490 friend class RMScopedAccessImpl<T, true>;
491
492 template <typename... Args>
493 explicit RealtimeMutatable(bool, Args &&... args)
494 : data({ T(std::forward(args)...), T(std::forward(args)...) }), realtimeCopy(std::forward(args)...)
495 {
496 }
497
498 enum
499 {
500 INDEX_BIT = (1 << 0),
501 BUSY_BIT = (1 << 1),
502 NEWDATA_BIT = (1 << 2)
503 };
504
505 int acquireIndex() noexcept
506 {
507 return control.fetch_or(BUSY_BIT, std::memory_order_acquire) & INDEX_BIT;
508 }
509
510 void releaseIndex(int idx) noexcept
511 {
512 control.store((idx & INDEX_BIT) | NEWDATA_BIT, std::memory_order_release);
513 }
514
515 std::atomic<int> control = { 0 };
516
517 std::array<T, 2> data;
518 T realtimeCopy;
519#if JPL_RT_OBJECT_NON_REALTIME_ACCESS_LOCK
520 std::mutex nonRealtimeLock;
521#endif
522 };
523
524 template <typename T, bool>
526 {
527 protected:
529 : p(parent),
530 currentValue(&p.realtimeAcquire())
531 {
532 }
533
535 {
536 p.realtimeRelease();
537 }
538 public:
539 T* get() noexcept { return currentValue; }
540 const T* get() const noexcept { return currentValue; }
541 T& operator *() noexcept { return *currentValue; }
542 const T& operator *() const noexcept { return *currentValue; }
543 T* operator->() noexcept { return currentValue; }
544 const T* operator->() const noexcept { return currentValue; }
545 private:
547 T* currentValue;
548 };
549
550 template <typename T>
552 {
553 protected:
555 : p(parent), currentValue(&p.nonRealtimeAcquire())
556 {
557 }
558 ~RMScopedAccessImpl() noexcept { p.nonRealtimeRelease(); }
559 public:
560 const T* get() const noexcept { return currentValue; }
561 const T& operator *() const noexcept { return *currentValue; }
562 const T* operator->() const noexcept { return currentValue; }
563 private:
565 const T* currentValue;
566 };
567 } // namespace detail
568} // namespace JPL
Definition RealtimeObject.h:152
ScopedAccess & operator=(ScopedAccess &&)=delete
ScopedAccess(RealtimeObject &parent)
Definition RealtimeObject.h:154
ScopedAccess(const ScopedAccess &)=delete
ScopedAccess(ScopedAccess &&)=delete
ScopedAccess & operator=(const ScopedAccess &)=delete
Definition RealtimeObject.h:69
RealtimeObject()=default
void realtimeReplace(Args &&... args) noexcept
Definition RealtimeObject.h:110
RealtimeAcquireReturnType & realtimeAcquire() noexcept
Definition RealtimeObject.h:98
RealtimeObject(T &&obj, const allocator_type &allocator={})
Definition RealtimeObject.h:81
std::conditional_t< Options==RealtimeObjectOptions::realtimeMutatable, const T, T > NonRealtimeAcquireReturnType
Definition RealtimeObject.h:87
std::pmr::polymorphic_allocator<> allocator_type
Definition RealtimeObject.h:71
void nonRealtimeReplace(Args &&... args)
Definition RealtimeObject.h:137
~RealtimeObject()=default
void nonRealtimeRelease()
Definition RealtimeObject.h:131
NonRealtimeAcquireReturnType & nonRealtimeAcquire()
Definition RealtimeObject.h:124
void realtimeRelease() noexcept
Definition RealtimeObject.h:104
RealtimeObject(const T &obj, const allocator_type &allocator={})
Definition RealtimeObject.h:78
std::conditional_t< Options==RealtimeObjectOptions::nonRealtimeMutatable, const T, T > RealtimeAcquireReturnType
Definition RealtimeObject.h:86
~NRMScopedAccessImpl() noexcept
Definition RealtimeObject.h:377
NRMScopedAccessImpl(NonRealtimeMutatable< T > &parent) noexcept
Definition RealtimeObject.h:373
const T * operator->() const noexcept
Definition RealtimeObject.h:381
const T * get() const noexcept
Definition RealtimeObject.h:379
Definition RealtimeObject.h:350
const T * operator->() const noexcept
Definition RealtimeObject.h:363
T * operator->() noexcept
Definition RealtimeObject.h:362
T & operator*() noexcept
Definition RealtimeObject.h:360
NRMScopedAccessImpl(NonRealtimeMutatable< T > &parent)
Definition RealtimeObject.h:352
T * get() noexcept
Definition RealtimeObject.h:358
const T * get() const noexcept
Definition RealtimeObject.h:359
~NRMScopedAccessImpl()
Definition RealtimeObject.h:356
ScopedAccess & operator=(ScopedAccess &&)=delete
ScopedAccess & operator=(const ScopedAccess &)=delete
ScopedAccess(const ScopedAccess &)=delete
ScopedAccess(NonRealtimeMutatable &parent)
Definition RealtimeObject.h:322
Definition RealtimeObject.h:202
NonRealtimeMutatable(const T &obj, const allocator_type &allocator={})
Definition RealtimeObject.h:214
const T & realtimeAcquire() noexcept
Definition RealtimeObject.h:256
~NonRealtimeMutatable()
Definition RealtimeObject.h:228
void nonRealtimeRelease()
Definition RealtimeObject.h:285
void nonRealtimeReplace(Args &&... args)
Definition RealtimeObject.h:306
NonRealtimeMutatable(T &&obj, const allocator_type &allocator={})
Definition RealtimeObject.h:221
void realtimeRelease() noexcept
Definition RealtimeObject.h:263
T & nonRealtimeAcquire()
Definition RealtimeObject.h:272
NonRealtimeMutatable(const allocator_type &allocator={})
Definition RealtimeObject.h:207
static NonRealtimeMutatable create(Args &&... args)
Definition RealtimeObject.h:251
std::pmr::polymorphic_allocator<> allocator_type
Definition RealtimeObject.h:204
RMScopedAccessImpl(RealtimeMutatable< T > &parent) noexcept
Definition RealtimeObject.h:554
const T * operator->() const noexcept
Definition RealtimeObject.h:562
const T * get() const noexcept
Definition RealtimeObject.h:560
~RMScopedAccessImpl() noexcept
Definition RealtimeObject.h:558
Definition RealtimeObject.h:526
T * operator->() noexcept
Definition RealtimeObject.h:543
T & operator*() noexcept
Definition RealtimeObject.h:541
RMScopedAccessImpl(RealtimeMutatable< T > &parent)
Definition RealtimeObject.h:528
T * get() noexcept
Definition RealtimeObject.h:539
const T * get() const noexcept
Definition RealtimeObject.h:540
~RMScopedAccessImpl()
Definition RealtimeObject.h:534
const T * operator->() const noexcept
Definition RealtimeObject.h:544
Definition RealtimeObject.h:480
ScopedAccess & operator=(ScopedAccess &&)=delete
ScopedAccess & operator=(const ScopedAccess &)=delete
ScopedAccess(const ScopedAccess &)=delete
ScopedAccess(RealtimeMutatable &parent)
Definition RealtimeObject.h:482
Definition RealtimeObject.h:390
void realtimeReplace(Args &&... args)
Definition RealtimeObject.h:432
T & realtimeAcquire() noexcept
Definition RealtimeObject.h:419
void realtimeRelease() noexcept
Definition RealtimeObject.h:424
const T & nonRealtimeAcquire()
Definition RealtimeObject.h:441
RealtimeMutatable(const T &obj)
Definition RealtimeObject.h:394
~RealtimeMutatable()
Definition RealtimeObject.h:396
void nonRealtimeRelease()
Definition RealtimeObject.h:471
static RealtimeMutatable create(Args &&... args)
Definition RealtimeObject.h:414
Definition AcousticMaterial.h:36
ThreadType
Definition RealtimeObject.h:31
RealtimeObjectOptions
Definition RealtimeObject.h:37
JPL_INLINE simd min(const simd &a, const simd &b) noexcept
Element-wise min.
Definition SIMD.h:1813
Definition ChannelMap.h:268