Sunshine master
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
403 queue_t(std::uint32_t max_elements = 32):
404 _max_elements {max_elements} {
405 }
406
412 template<class... Args>
413 void raise(Args &&...args) {
414 std::lock_guard ul {_lock};
415
416 if (!_continue) {
417 return;
418 }
419
420 if (_queue.size() == _max_elements) {
421 _queue.clear();
422 }
423
424 _queue.emplace_back(std::forward<Args>(args)...);
425
426 _cv.notify_all();
427 }
428
434 bool peek() {
435 return _continue && !_queue.empty();
436 }
437
444 template<class Rep, class Period>
445 status_t pop(std::chrono::duration<Rep, Period> delay) {
446 std::unique_lock ul {_lock};
447
448 if (!_continue) {
449 return util::false_v<status_t>;
450 }
451
452 while (_queue.empty()) {
453 if (!_continue || _cv.wait_for(ul, delay) == std::cv_status::timeout) {
454 return util::false_v<status_t>;
455 }
456 }
457
458 auto val = std::move(_queue.front());
459 _queue.erase(std::begin(_queue));
460
461 return val;
462 }
463
470 std::unique_lock ul {_lock};
471
472 if (!_continue) {
473 return util::false_v<status_t>;
474 }
475
476 while (_queue.empty()) {
477 _cv.wait(ul);
478
479 if (!_continue) {
480 return util::false_v<status_t>;
481 }
482 }
483
484 auto val = std::move(_queue.front());
485 _queue.erase(std::begin(_queue));
486
487 return val;
488 }
489
495 std::vector<T> &unsafe() {
496 return _queue;
497 }
498
502 void stop() {
503 std::lock_guard lg {_lock};
504
505 _continue = false;
506
507 _cv.notify_all();
508 }
509
515 [[nodiscard]] bool running() const {
516 return _continue;
517 }
518
519 private:
520 bool _continue {true};
521 std::uint32_t _max_elements;
522
523 std::mutex _lock;
524 std::condition_variable _cv;
525
526 std::vector<T> _queue;
527 };
528
532 template<class T>
533 class shared_t {
534 public:
538 using element_type = T;
539
543 using construct_f = std::function<int(element_type &)>;
547 using destruct_f = std::function<void(element_type &)>;
548
552 struct ptr_t {
554
555 ptr_t():
556 owner {nullptr} {
557 }
558
564 explicit ptr_t(shared_t *owner):
565 owner {owner} {
566 }
567
573 ptr_t(ptr_t &&ptr) noexcept:
574 owner {ptr.owner} {
575 ptr.owner = nullptr;
576 }
577
583 ptr_t(const ptr_t &ptr) noexcept:
584 owner {ptr.owner} {
585 if (!owner) {
586 return;
587 }
588
589 auto tmp = ptr.owner->ref();
590 tmp.owner = nullptr;
591 }
592
599 ptr_t &operator=(const ptr_t &ptr) noexcept {
600 if (!ptr.owner) {
601 release();
602
603 return *this;
604 }
605
606 return *this = std::move(*ptr.owner->ref());
607 }
608
615 ptr_t &operator=(ptr_t &&ptr) noexcept {
616 if (owner) {
617 release();
618 }
619
620 std::swap(owner, ptr.owner);
621
622 return *this;
623 }
624
625 ~ptr_t() {
626 if (owner) {
627 release();
628 }
629 }
630
634 operator bool() const {
635 return owner != nullptr;
636 }
637
641 void release() {
642 std::lock_guard lg {owner->_lock};
643
644 if (!--owner->_count) {
645 owner->_destruct(*get());
646 (*this)->~element_type();
647 }
648
649 owner = nullptr;
650 }
651
657 element_type *get() const {
658 return reinterpret_cast<element_type *>(owner->_object_buf.data());
659 }
660
667 return reinterpret_cast<element_type *>(owner->_object_buf.data());
668 }
669 };
670
677 template<class FC, class FD>
678 shared_t(FC &&fc, FD &&fd):
679 _construct {std::forward<FC>(fc)},
680 _destruct {std::forward<FD>(fd)} {
681 }
682
688 [[nodiscard]] ptr_t ref() {
689 std::lock_guard lg {_lock};
690
691 if (!_count) {
692 new (_object_buf.data()) element_type;
693 if (_construct(*reinterpret_cast<element_type *>(_object_buf.data())) != 0) {
694 return ptr_t {nullptr};
695 }
696 }
697
698 ++_count;
699
700 return ptr_t {this};
701 }
702
703 private:
704 construct_f _construct;
705 destruct_f _destruct;
706
707 std::array<std::uint8_t, sizeof(element_type)> _object_buf;
708
709 std::uint32_t _count;
710 std::mutex _lock;
711 };
712
720 template<class T, class F_Construct, class F_Destruct>
721 auto make_shared(F_Construct &&fc, F_Destruct &&fd) {
722 return shared_t<T> {
723 std::forward<F_Construct>(fc),
724 std::forward<F_Destruct>(fd)
725 };
726 }
727
732
733 class mail_raw_t;
737 using mail_t = std::shared_ptr<mail_raw_t>;
738
744 void cleanup(mail_raw_t *);
745
749 template<class T>
750 class post_t: public T {
751 public:
758 template<class... Args>
759 post_t(mail_t mail, Args &&...args):
760 T(std::forward<Args>(args)...),
761 mail {std::move(mail)} {
762 }
763
765
766 ~post_t() {
767 cleanup(mail.get());
768 }
769 };
770
777 template<class T>
778 inline auto lock(const std::weak_ptr<void> &wp) {
779 return std::reinterpret_pointer_cast<typename T::element_type>(wp.lock());
780 }
781
785 class mail_raw_t: public std::enable_shared_from_this<mail_raw_t> {
786 public:
790 template<class T>
791 using event_t = std::shared_ptr<post_t<event_t<T>>>;
792
796 template<class T>
797 using queue_t = std::shared_ptr<post_t<queue_t<T>>>;
798
805 template<class T>
806 event_t<T> event(const std::string_view &id) {
807 std::lock_guard lg {mutex};
808
809 auto it = id_to_post.find(id);
810 if (it != std::end(id_to_post)) {
811 return lock<event_t<T>>(it->second);
812 }
813
814 auto post = std::make_shared<typename event_t<T>::element_type>(shared_from_this());
815 id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> {std::string {id}, post});
816
817 return post;
818 }
819
826 template<class T>
827 queue_t<T> queue(const std::string_view &id) {
828 std::lock_guard lg {mutex};
829
830 auto it = id_to_post.find(id);
831 if (it != std::end(id_to_post)) {
832 return lock<queue_t<T>>(it->second);
833 }
834
835 auto post = std::make_shared<typename queue_t<T>::element_type>(shared_from_this(), 32);
836 id_to_post.emplace(std::pair<std::string, std::weak_ptr<void>> {std::string {id}, post});
837
838 return post;
839 }
840
844 void cleanup() {
845 std::lock_guard lg {mutex};
846
847 for (auto it = std::begin(id_to_post); it != std::end(id_to_post); ++it) {
848 auto &weak = it->second;
849
850 if (weak.expired()) {
851 id_to_post.erase(it);
852
853 return;
854 }
855 }
856 }
857
858 std::mutex mutex;
859
860 std::map<std::string, std::weak_ptr<void>, std::less<>> id_to_post;
861 };
862
866 inline void cleanup(mail_raw_t *mail) {
867 mail->cleanup();
868 }
869} // 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:785
queue_t< T > queue(const std::string_view &id)
Create a typed queue channel from the raw mailbox.
Definition thread_safe.h:827
std::map< std::string, std::weak_ptr< void >, std::less<> > id_to_post
Posted objects keyed by cleanup identifier.
Definition thread_safe.h:860
void cleanup()
Run cleanup for completed asynchronous work.
Definition thread_safe.h:844
std::mutex mutex
Mutex protecting the map of live posted objects.
Definition thread_safe.h:858
std::shared_ptr< post_t< queue_t< T > > > queue_t
Mailbox-backed queue wrapper for typed messages.
Definition thread_safe.h:797
event_t< T > event(const std::string_view &id)
Create a typed event channel from the raw mailbox.
Definition thread_safe.h:806
std::shared_ptr< post_t< event_t< T > > > event_t
Mailbox-backed event wrapper for a typed value.
Definition thread_safe.h:791
Wrapper that posts cleanup work to a mailbox when destroyed.
Definition thread_safe.h:750
mail_t mail
Mailbox kept alive until the posted object is destroyed.
Definition thread_safe.h:764
post_t(mail_t mail, Args &&...args)
Construct a message-posting wrapper around an existing type.
Definition thread_safe.h:759
Thread-safe queue with blocking and shutdown-aware consumers.
Definition thread_safe.h:391
queue_t(std::uint32_t max_elements=32)
Construct a bounded blocking queue.
Definition thread_safe.h:403
void stop()
Shut down the queue and wake blocked consumers.
Definition thread_safe.h:502
status_t pop(std::chrono::duration< Rep, Period > delay)
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:445
util::optional_t< T > status_t
Value type stored in the queue.
Definition thread_safe.h:396
bool peek()
Inspect the next queued value without popping it.
Definition thread_safe.h:434
status_t pop()
Remove and return the next queued item, waiting when requested.
Definition thread_safe.h:469
std::vector< T > & unsafe()
Return the wrapped pointer without synchronization checks.
Definition thread_safe.h:495
void raise(Args &&...args)
Notify waiters that a new event value is available.
Definition thread_safe.h:413
bool running() const
Return whether the queue is still running.
Definition thread_safe.h:515
Shared object storage with custom construction and destruction hooks.
Definition thread_safe.h:533
std::function< int(element_type &)> construct_f
Callback signature used to construct the protected object.
Definition thread_safe.h:543
std::function< void(element_type &)> destruct_f
Callback signature used to destroy the protected object.
Definition thread_safe.h:547
T element_type
Object type stored in shared protected storage.
Definition thread_safe.h:538
shared_t(FC &&fc, FD &&fd)
Construct a shared protected object with custom close and destruction hooks.
Definition thread_safe.h:678
ptr_t ref()
Return a shared reference to the protected object.
Definition thread_safe.h:688
Functions for handling command line arguments.
Definition entry_handler.cpp:37
Handles process-wide communication.
Definition globals.h:34
Pointer wrapper state protected by synchronization.
Definition thread_safe.h:552
element_type * operator->()
Access the shared protected object.
Definition thread_safe.h:666
ptr_t(shared_t *owner)
Construct a reference bound to a shared protected object.
Definition thread_safe.h:564
ptr_t(const ptr_t &ptr) noexcept
Copy a shared-object reference without locking the protected object.
Definition thread_safe.h:583
void release()
Release the COM or platform reference owned by the pointer.
Definition thread_safe.h:641
ptr_t(ptr_t &&ptr) noexcept
Move a shared-object reference without locking the protected object.
Definition thread_safe.h:573
element_type * get() const
Return the currently wrapped value or handle.
Definition thread_safe.h:657
shared_t * owner
Shared object that owns the protected value.
Definition thread_safe.h:553
ptr_t & operator=(const ptr_t &ptr) noexcept
Assign state from another instance while preserving ownership semantics.
Definition thread_safe.h:599
ptr_t & operator=(ptr_t &&ptr) noexcept
Assign state from another instance while preserving ownership semantics.
Definition thread_safe.h:615
std::shared_ptr< mail_raw_t > mail_t
Shared mailbox handle used by event and queue wrappers.
Definition thread_safe.h:737
auto make_shared(F_Construct &&fc, F_Destruct &&fd)
Create a shared object or message.
Definition thread_safe.h:721
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
auto lock(const std::weak_ptr< void > &wp)
Acquire the underlying lock or keyed mutex.
Definition thread_safe.h:778
void cleanup(mail_raw_t *)
Run cleanup for completed asynchronous work.
Definition thread_safe.h:866
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:1385