Sunshine latest
Self-hosted game stream host for Moonlight.
thread_safe.h
Go to the documentation of this file.
1
5#pragma once
6
7// standard includes
8#include <array>
9#include <atomic>
10#include <condition_variable>
11#include <functional>
12#include <map>
13#include <mutex>
14#include <vector>
15
16// local includes
17#include "utility.h"
18
19namespace safe {
23 template<class T>
24 class event_t {
25 public:
30
36 template<class... Args>
37 void raise(Args &&...args) {
38 std::lock_guard lg {_lock};
39 if (!_continue) {
40 return;
41 }
42
43 if constexpr (std::is_same_v<std::optional<T>, status_t>) {
44 _status = std::make_optional<T>(std::forward<Args>(args)...);
45 } else {
46 _status = status_t {std::forward<Args>(args)...};
47 }
48
49 _cv.notify_all();
50 }
51
58 std::lock_guard lg {_lock};
59 if (!_status) {
60 return util::false_v<status_t>;
61 }
62 auto val = std::move(_status);
63 _status = util::false_v<status_t>;
64 return val;
65 }
66
67 // pop and view should not be used interchangeably
74 std::unique_lock ul {_lock};
75
76 if (!_continue) {
77 return util::false_v<status_t>;
78 }
79
80 while (!_status) {
81 _cv.wait(ul);
82
83 if (!_continue) {
84 return util::false_v<status_t>;
85 }
86 }
87
88 auto val = std::move(_status);
89 _status = util::false_v<status_t>;
90 return val;
91 }
92
93 // pop and view should not be used interchangeably
100 template<typename Rep, typename Period>
101 status_t pop(std::chrono::duration<Rep, Period> delay) {
102 std::unique_lock ul {_lock};
103
104 if (bool success = _cv.wait_for(ul, delay, [this] {
105 return (bool) _status || !_continue;
106 });
107 !success || !_continue) {
108 return util::false_v<status_t>;
109 }
110
111 auto val = std::move(_status);
112 _status = util::false_v<status_t>;
113 return val;
114 }
115
116 // pop and view should not be used interchangeably
123 std::unique_lock ul {_lock};
124
125 if (!_continue) {
126 return util::false_v<status_t>;
127 }
128
129 while (!_status) {
130 _cv.wait(ul);
131
132 if (!_continue) {
133 return util::false_v<status_t>;
134 }
135 }
136
137 return _status;
138 }
139
140 // pop and view should not be used interchangeably
147 template<class Rep, class Period>
148 status_t view(std::chrono::duration<Rep, Period> delay) {
149 std::unique_lock ul {_lock};
150
151 if (!_continue) {
152 return util::false_v<status_t>;
153 }
154
155 while (!_status) {
156 if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
157 return util::false_v<status_t>;
158 }
159 }
160
161 return _status;
162 }
163
169 bool peek() {
170 return _continue && (bool) _status;
171 }
172
176 void stop() {
177 std::lock_guard lg {_lock};
178
179 _continue = false;
180
181 _cv.notify_all();
182 }
183
187 void reset() {
188 std::lock_guard lg {_lock};
189
190 _continue = true;
191
192 _status = util::false_v<status_t>;
193 }
194
200 [[nodiscard]] bool running() const {
201 return _continue;
202 }
203
204 private:
205 bool _continue {true};
206 status_t _status {util::false_v<status_t>};
207
208 std::condition_variable _cv;
209 std::mutex _lock;
210 };
211
215 template<class T>
217 public:
222
228 void ring(const status_t &status) {
229 std::lock_guard lg(_lock);
230
231 _status = status;
232 _rang = true;
233 _cv.notify_one();
234 }
235
242 std::lock_guard lg(_lock);
243
244 _status = std::move(status);
245 _rang = true;
246 _cv.notify_one();
247 }
248
255 template<class Rep, class Period>
256 auto wait_for(const std::chrono::duration<Rep, Period> &rel_time) {
257 std::unique_lock ul(_lock);
258
259 return _cv.wait_for(ul, rel_time, [this]() {
260 return _rang;
261 });
262 }
263
271 template<class Rep, class Period, class Pred>
272 auto wait_for(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
273 std::unique_lock ul(_lock);
274
275 return _cv.wait_for(ul, rel_time, [this, &pred]() {
276 return _rang || pred();
277 });
278 }
279
286 template<class Rep, class Period>
287 auto wait_until(const std::chrono::duration<Rep, Period> &rel_time) {
288 std::unique_lock ul(_lock);
289
290 return _cv.wait_until(ul, rel_time, [this]() {
291 return _rang;
292 });
293 }
294
302 template<class Rep, class Period, class Pred>
303 auto wait_until(const std::chrono::duration<Rep, Period> &rel_time, Pred &&pred) {
304 std::unique_lock ul(_lock);
305
306 return _cv.wait_until(ul, rel_time, [this, &pred]() {
307 return _rang || pred();
308 });
309 }
310
316 auto wait() {
317 std::unique_lock ul(_lock);
318 _cv.wait(ul, [this]() {
319 return _rang;
320 });
321 }
322
329 template<class Pred>
330 auto wait(Pred &&pred) {
331 std::unique_lock ul(_lock);
332 _cv.wait(ul, [this, &pred]() {
333 return _rang || pred();
334 });
335 }
336
342 const status_t &status() const {
343 return _status;
344 }
345
352 return _status;
353 }
354
358 void reset() {
359 _status = status_t {};
360 _rang = false;
361 }
362
363 private:
364 std::mutex _lock;
365 std::condition_variable _cv;
366
367 status_t _status {util::false_v<status_t>};
368 bool _rang {false};
369 };
370
374 template<class T>
375 using alarm_t = std::shared_ptr<alarm_raw_t<T>>;
376
382 template<class T>
384 return std::make_shared<alarm_raw_t<T>>();
385 }
386
390 template<class T>
391 class queue_t {
392 public:
397
401 enum class overflow_policy_e {
402 drop_oldest,
403 reject
404 };
405
412 explicit queue_t(std::uint32_t max_elements = 32, overflow_policy_e overflow = overflow_policy_e::drop_oldest):
413 _max_elements {max_elements},
414 _overflow_policy {overflow} {
415 }
416
424 template<class... Args>
425 bool raise(Args &&...args) {
426 std::lock_guard ul {_lock};
427
428 if (!_continue) {
429 return false;
430 }
431
432 if (_queue.size() >= _max_elements) {
433 if (_overflow_policy == overflow_policy_e::reject) {
434 return false;
435 }
436 _queue.clear();
437 }
438
439 _queue.emplace_back(std::forward<Args>(args)...);
440 _cv.notify_all();
441 return true;
442 }
443
449 bool peek() {
450 return _continue && !_queue.empty();
451 }
452
459 template<class Rep, class Period>
460 status_t pop(std::chrono::duration<Rep, Period> delay) {
461 std::unique_lock ul {_lock};
462
463 if (!_continue) {
464 return util::false_v<status_t>;
465 }
466
467 while (_queue.empty()) {
468 if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
469 return util::false_v<status_t>;
470 }
471 }
472
473 auto val = std::move(_queue.front());
474 _queue.erase(std::begin(_queue));
475
476 return val;
477 }
478
485 std::unique_lock ul {_lock};
486
487 if (!_continue) {
488 return util::false_v<status_t>;
489 }
490
491 while (_queue.empty()) {
492 _cv.wait(ul);
493
494 if (!_continue) {
495 return util::false_v<status_t>;
496 }
497 }
498
499 auto val = std::move(_queue.front());
500 _queue.erase(std::begin(_queue));
501
502 return val;
503 }
504
510 std::vector<T> &unsafe() {
511 return _queue;
512 }
513
517 void stop() {
518 std::lock_guard lg {_lock};
519
520 _continue = false;
521
522 _cv.notify_all();
523 }
524
530 [[nodiscard]] bool running() const {
531 return _continue;
532 }
533
534 private:
535 bool _continue {true};
536 std::uint32_t _max_elements;
537
538 std::mutex _lock;
539 std::condition_variable _cv;
540
541 std::vector<T> _queue;
542 overflow_policy_e _overflow_policy;
543 };
544
548 template<class T>
549 class shared_t {
550 public:
554 using element_type = T;
555
559 using construct_f = std::function<int(element_type &)>;
563 using destruct_f = std::function<void(element_type &)>;
564
568 struct ptr_t {
570
571 ptr_t():
572 owner {nullptr} {
573 }
574
580 explicit ptr_t(shared_t *owner):
581 owner {owner} {
582 }
583
589 ptr_t(ptr_t &&ptr) noexcept:
590 owner {ptr.owner} {
591 ptr.owner = nullptr;
592 }
593
599 ptr_t(const ptr_t &ptr) noexcept:
600 owner {ptr.owner} {
601 if (!owner) {
602 return;
603 }
604
605 auto tmp = ptr.owner->ref();
606 tmp.owner = nullptr;
607 }
608
615 ptr_t &operator=(const ptr_t &ptr) noexcept {
616 if (!ptr.owner) {
617 release();
618
619 return *this;
620 }
621
622 return *this = std::move(*ptr.owner->ref());
623 }
624
631 ptr_t &operator=(ptr_t &&ptr) noexcept {
632 if (owner) {
633 release();
634 }
635
636 std::swap(owner, ptr.owner);
637
638 return *this;
639 }
640
641 ~ptr_t() {
642 if (owner) {
643 release();
644 }
645 }
646
650 operator bool() const {
651 return owner != nullptr;
652 }
653
657 void release() {
658 std::lock_guard lg {owner->_lock};
659
660 if (!--owner->_count) {
661 owner->_destruct(*get());
662 (*this)->~element_type();
663 }
664
665 owner = nullptr;
666 }
667
673 element_type *get() const {
674 return reinterpret_cast<element_type *>(owner->_object_buf.data());
675 }
676
683 return reinterpret_cast<element_type *>(owner->_object_buf.data());
684 }
685 };
686
693 template<class FC, class FD>
694 shared_t(FC &&fc, FD &&fd):
695 _construct {std::forward<FC>(fc)},
696 _destruct {std::forward<FD>(fd)} {
697 }
698
704 [[nodiscard]] ptr_t ref() {
705 std::lock_guard lg {_lock};
706
707 if (!_count) {
708 new (_object_buf.data()) element_type;
709 if (_construct(*reinterpret_cast<element_type *>(_object_buf.data())) != 0) {
710 return ptr_t {nullptr};
711 }
712 }
713
714 ++_count;
715
716 return ptr_t {this};
717 }
718
719 private:
720 construct_f _construct;
721 destruct_f _destruct;
722
723 std::array<std::uint8_t, sizeof(element_type)> _object_buf;
724
725 std::uint32_t _count;
726 std::mutex _lock;
727 };
728
736 template<class T, class F_Construct, class F_Destruct>
737 auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
738 return shared_t<T> {
739 std::forward<F_Construct>(fc),
740 std::forward<F_Destruct>(fd)
741 };
742 }
743
748
749 class mail_raw_t;
753 using mail_t = std::shared_ptr<mail_raw_t>;
754
760 void cleanup(mail_raw_t *);
761
765 template<class T>
766 class post_t: public T {
767 public:
774 template<class... Args>
775 post_t(mail_t mail, Args &&...args):
776 T(std::forward<Args>(args)...),
777 mail {std::move(mail)} {
778 }
779
781
782 ~post_t() {
783 cleanup(mail.get());
784 }
785 };
786
793 template<class T>
794 inline auto lock(const std::weak_ptr<void> &wp) {
795 return std::reinterpret_pointer_cast<typename T::element_type>(wp.lock());
796 }
797
801 class mail_raw_t: public std::enable_shared_from_this<mail_raw_t> {
802 public:
806 template<class T>
807 using event_t = std::shared_ptr<post_t<event_t<T>>>;
808
812 template<class T>
813 using queue_t = std::shared_ptr<post_t<queue_t<T>>>;
814
821 template<class T>
822 event_t<T> event(const std::string_view &id) {
823 std::lock_guard lg {mutex};
824
825 auto it = id_to_post.find(id);
826 if (it != std::end(id_to_post)) {
827 return lock<event_t<T>>(it->second);
828 }
829
830 auto post = std::make_shared<typename event_t<T>::element_type>(shared_from_this());
831 id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> {std::string {id}, post});
832
833 return post;
834 }
835
842 template<class T>
843 queue_t<T> queue(const std::string_view &id) {
844 std::lock_guard lg {mutex};
845
846 auto it = id_to_post.find(id);
847 if (it != std::end(id_to_post)) {
848 return lock<queue_t<T>>(it->second);
849 }
850
851 auto post = std::make_shared<typename queue_t<T>::element_type>(shared_from_this(), 32);
852 id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> {std::string {id}, post});
853
854 return post;
855 }
856
860 void cleanup() {
861 std::lock_guard lg {mutex};
862
863 for (auto it = std::begin(id_to_post); it != std::end(id_to_post); ++it) {
864 auto &weak = it->second;
865
866 if (weak.expired()) {
867 id_to_post.erase(it);
868
869 return;
870 }
871 }
872 }
873
874 std::mutex mutex;
875
876 std::map<std::string, std::weak_ptr<void>, std::less<>> id_to_post;
877 };
878
882 inline void cleanup(mail_raw_t *mail) {
883 mail->cleanup();
884 }
885} // namespace safe
Alarm primitive used to wait for and raise shutdown-style signals.
Definition thread_safe.h:216
void reset()
Reset the object to its initial empty state.
Definition thread_safe.h:358
status_t & status()
Return or update the current status value.
Definition thread_safe.h:351
auto wait_for(const std::chrono::duration< Rep, Period > &rel_time)
Wait for for.
Definition thread_safe.h:256
auto wait()
Block until the event is raised.
Definition thread_safe.h:316
auto wait_until(const std::chrono::duration< Rep, Period > &rel_time, Pred &&pred)
Wait for until.
Definition thread_safe.h:303
auto wait_for(const std::chrono::duration< Rep, Period > &rel_time, Pred &&pred)
Wait for for.
Definition thread_safe.h:272
void ring(const status_t &status)
Mark the alarm as rung and wake waiting threads.
Definition thread_safe.h:228
auto wait(Pred &&pred)
Block until the event is raised and the predicate accepts the state.
Definition thread_safe.h:330
util::optional_t< T > status_t
Status value carried by the alarm.
Definition thread_safe.h:221
const status_t & status() const
Return or update the current status value.
Definition thread_safe.h:342
void ring(status_t &&status)
Mark the alarm as rung and wake waiting threads.
Definition thread_safe.h:241
auto wait_until(const std::chrono::duration< Rep, Period > &rel_time)
Wait for until.
Definition thread_safe.h:287
Thread-safe event value that blocks readers until a value is raised.
Definition thread_safe.h:24
status_t pop()
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:73
util::optional_t< T > status_t
Status value stored in the event.
Definition thread_safe.h:29
status_t view(std::chrono::duration< Rep, Period > delay)
Read the current value without removing it from the queue.
Definition thread_safe.h:148
bool running() const
Return whether the queue is still running.
Definition thread_safe.h:200
status_t view()
Read the current value without removing it from the queue.
Definition thread_safe.h:122
status_t try_pop()
Try to remove and return the next queued item without any further wait.
Definition thread_safe.h:57
void reset()
Reset the object to its initial empty state.
Definition thread_safe.h:187
bool peek()
Inspect the next queued value without popping it.
Definition thread_safe.h:169
void stop()
Raise the alarm and wake all waiting threads.
Definition thread_safe.h:176
void raise(Args &&...args)
Notify waiters that a new event value is available.
Definition thread_safe.h:37
status_t pop(std::chrono::duration< Rep, Period > delay)
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:101
Mailbox backing store for events, queues, and posted cleanup objects.
Definition thread_safe.h:801
queue_t< T > queue(const std::string_view &id)
Create a typed queue channel from the raw mailbox.
Definition thread_safe.h:843
std::map< std::string, std::weak_ptr< void >, std::less<> > id_to_post
Posted objects keyed by cleanup identifier.
Definition thread_safe.h:876
void cleanup()
Run cleanup for completed asynchronous work.
Definition thread_safe.h:860
std::mutex mutex
Mutex protecting the map of live posted objects.
Definition thread_safe.h:874
std::shared_ptr< post_t< queue_t< T > > > queue_t
Mailbox-backed queue wrapper for typed messages.
Definition thread_safe.h:813
event_t< T > event(const std::string_view &id)
Create a typed event channel from the raw mailbox.
Definition thread_safe.h:822
std::shared_ptr< post_t< event_t< T > > > event_t
Mailbox-backed event wrapper for a typed value.
Definition thread_safe.h:807
post_t(mail_t mail, Args &&...args)
Construct a message-posting wrapper around an existing type.
Definition thread_safe.h:775
void stop()
Shut down the queue and wake blocked consumers.
Definition thread_safe.h:517
overflow_policy_e
Behavior when raise() is called with the queue already at its bound.
Definition thread_safe.h:401
@ reject
Refuse the new item, leaving existing queued items intact.
Definition thread_safe.h:403
@ drop_oldest
Clear the queue and accept the new item (current default behavior).
Definition thread_safe.h:402
status_t pop(std::chrono::duration< Rep, Period > delay)
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:460
util::optional_t< T > status_t
Value type stored in the queue.
Definition thread_safe.h:396
bool raise(Args &&...args)
Notify waiters that a new event value may be available.
Definition thread_safe.h:425
bool peek()
Inspect the next queued value without popping it.
Definition thread_safe.h:449
queue_t(std::uint32_t max_elements=32, overflow_policy_e overflow=overflow_policy_e::drop_oldest)
Construct a bounded blocking queue.
Definition thread_safe.h:412
status_t pop()
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:484
std::vector< T > & unsafe()
Return the wrapped pointer without synchronization checks.
Definition thread_safe.h:510
bool running() const
Return whether the queue is still running.
Definition thread_safe.h:530
Shared object storage with custom construction and destruction hooks.
Definition thread_safe.h:549
std::function< int(element_type &)> construct_f
Definition thread_safe.h:559
std::function< void(element_type &)> destruct_f
Definition thread_safe.h:563
audio_ctx_t element_type
Definition thread_safe.h:554
shared_t(FC &&fc, FD &&fd)
Construct a shared protected object with custom close and destruction hooks.
Definition thread_safe.h:694
ptr_t ref()
Return a shared reference to the protected object.
Definition thread_safe.h:704
Functions for handling command line arguments.
Definition entry_handler.cpp:41
Handles process-wide communication.
Definition globals.h:34
Pointer wrapper state protected by synchronization.
Definition thread_safe.h:568
element_type * operator->()
Access the shared protected object.
Definition thread_safe.h:682
ptr_t(shared_t *owner)
Construct a reference bound to a shared protected object.
Definition thread_safe.h:580
ptr_t(const ptr_t &ptr) noexcept
Copy a shared-object reference without locking the protected object.
Definition thread_safe.h:599
void release()
Release the COM or platform reference owned by the pointer.
Definition thread_safe.h:657
ptr_t(ptr_t &&ptr) noexcept
Move a shared-object reference without locking the protected object.
Definition thread_safe.h:589
element_type * get() const
Return the currently wrapped value or handle.
Definition thread_safe.h:673
shared_t * owner
Shared object that owns the protected value.
Definition thread_safe.h:569
ptr_t & operator=(const ptr_t &ptr) noexcept
Assign state from another instance while preserving ownership semantics.
Definition thread_safe.h:615
ptr_t & operator=(ptr_t &&ptr) noexcept
Assign state from another instance while preserving ownership semantics.
Definition thread_safe.h:631
std::shared_ptr< mail_raw_t > mail_t
Shared mailbox handle used by event and queue wrappers.
Definition thread_safe.h:753
auto make_shared(F_Construct &&fc, F_Destruct &&fd)
Create a shared object or message.
Definition thread_safe.h:737
alarm_t< T > make_alarm()
Create a alarm object or message.
Definition thread_safe.h:383
std::shared_ptr< alarm_raw_t< T > > alarm_t
Shared alarm primitive used for cross-thread shutdown signals.
Definition thread_safe.h:375
event_t< bool > signal_t
Boolean alarm used as a simple signal.
Definition thread_safe.h:747
auto lock(const std::weak_ptr< void > &wp)
Acquire the underlying lock or keyed mutex.
Definition thread_safe.h:794
void cleanup(mail_raw_t *)
Run cleanup for completed asynchronous work.
Definition thread_safe.h:882
Declarations for utility functions.
either_t<(std::is_same_v< T, bool >||is_pointer_v< T >), T, std::optional< T > > optional_t
Optional trait value used by endian serialization helpers.
Definition utility.h:1390