|
| 1 | +#ifndef GGL_IPC_SUBSCRIPTION_HPP |
| 2 | +#define GGL_IPC_SUBSCRIPTION_HPP |
| 3 | + |
| 4 | +#include <ggl/error.hpp> |
| 5 | +#include <ggl/ipc/client_c_api.hpp> |
| 6 | +#include <compare> |
| 7 | +#include <functional> |
| 8 | +#include <utility> |
| 9 | + |
| 10 | +extern "C" { |
| 11 | +void ggipc_close_subscription(GgIpcSubscriptionHandle handle) noexcept; |
| 12 | +} |
| 13 | + |
| 14 | +namespace ggl::ipc { |
| 15 | + |
| 16 | +/// Holds a bound function associated with a subscription. Instances of |
| 17 | +/// SubscriptionCallback must outlive their associated subscription(s) (i.e. |
| 18 | +/// close() on the Subscription must be called before destroying its |
| 19 | +/// SubscriptionCallback). The subscription callback function for a bound |
| 20 | +/// subscription is called by exactly one other thread. |
| 21 | +class SubscriptionCallback { |
| 22 | +private: |
| 23 | + /// TODO: |
| 24 | + std::function<void(void)> callback; |
| 25 | + |
| 26 | +public: |
| 27 | + /// TODO: |
| 28 | +}; |
| 29 | + |
| 30 | +/// Subscription handle with std::unique_ptr semantics. The underlying |
| 31 | +/// handle has two copies; one is returned by a Client subscription method, and |
| 32 | +/// another is passed to SubscriptionCallbacks, which are called by a |
| 33 | +/// library-controlled thread. Either copy of the handle can terminate the |
| 34 | +/// underlying subscription. |
| 35 | +class [[nodiscard]] Subscription { |
| 36 | +private: |
| 37 | + GgIpcSubscriptionHandle handle {}; |
| 38 | + |
| 39 | +public: |
| 40 | + /// A default-constructed Subscription is guaranteed to refer to no |
| 41 | + /// subscription at all. |
| 42 | + constexpr Subscription() noexcept = default; |
| 43 | + |
| 44 | + explicit constexpr Subscription( |
| 45 | + GgIpcSubscriptionHandle subscription_handle |
| 46 | + ) noexcept |
| 47 | + : handle { subscription_handle } { |
| 48 | + } |
| 49 | + |
| 50 | + /// Moved Subscriptions are guaranteed afterwards to refer to no |
| 51 | + /// subscription. |
| 52 | + constexpr Subscription(Subscription &&subscription) noexcept |
| 53 | + : handle { subscription.release() } { |
| 54 | + } |
| 55 | + |
| 56 | + Subscription &operator=(Subscription &&subscription) noexcept { |
| 57 | + // Guard against self-moves |
| 58 | + if (subscription != *this) { |
| 59 | + reset(subscription.release()); |
| 60 | + } |
| 61 | + return *this; |
| 62 | + } |
| 63 | + |
| 64 | + Subscription &operator=(const Subscription &) = delete; |
| 65 | + Subscription(const Subscription &) = delete; |
| 66 | + |
| 67 | + ~Subscription() noexcept { |
| 68 | + close(); |
| 69 | + } |
| 70 | + |
| 71 | + /// Returns true if Subscription may refer to an active subscription. |
| 72 | + /// Only semantically meaningful to detect default/moved values. |
| 73 | + /// Does not detect whether another thread calling close() on its copy of |
| 74 | + /// this handle (i.e. calling close() on a SubscriptionCallback's |
| 75 | + /// Subscription& parameter). |
| 76 | + constexpr bool holds_subscription() const noexcept { |
| 77 | + return handle.val != 0; |
| 78 | + } |
| 79 | + |
| 80 | + /// Copies the current raw handle. Use only for hashing. |
| 81 | + constexpr GgIpcSubscriptionHandle get() const noexcept { |
| 82 | + return handle; |
| 83 | + } |
| 84 | + |
| 85 | + /// Relinquish ownership of handle without closing it. |
| 86 | + [[nodiscard]] |
| 87 | + constexpr GgIpcSubscriptionHandle release() noexcept { |
| 88 | + return std::exchange(handle, GgIpcSubscriptionHandle {}); |
| 89 | + } |
| 90 | + |
| 91 | + /// closes current subscription and replaces it with a new handle. |
| 92 | + void reset(GgIpcSubscriptionHandle new_handle) noexcept { |
| 93 | + close(); |
| 94 | + handle = new_handle; |
| 95 | + } |
| 96 | + |
| 97 | + /// Swap handles |
| 98 | + constexpr void swap(Subscription &other) noexcept { |
| 99 | + std::swap(handle, other.handle); |
| 100 | + } |
| 101 | + |
| 102 | + /// Non-member swap |
| 103 | + friend constexpr void swap(Subscription &lhs, Subscription &rhs) noexcept { |
| 104 | + lhs.swap(rhs); |
| 105 | + } |
| 106 | + |
| 107 | + /// Same as holds_subscription() |
| 108 | + constexpr operator bool() const noexcept { |
| 109 | + return holds_subscription(); |
| 110 | + } |
| 111 | + |
| 112 | + /// Sends a terminate stream request to the IPC server. After this function |
| 113 | + /// returns, the Subscription's associated SubscriptionCallback (if any) is |
| 114 | + /// no longer called for messages to this Subscription after this method |
| 115 | + /// returns. Threadsafe and reentrant. Valid even if Subscription is moved / |
| 116 | + /// default-constructed. Valid to call from within a SubscriptionCallback |
| 117 | + /// handler, but such a call is not visible to other threads until they call |
| 118 | + /// close as well. |
| 119 | + void close() noexcept { |
| 120 | + // This check avoids locking a mutex when Subscription is |
| 121 | + // default-initialized |
| 122 | + if (holds_subscription()) { |
| 123 | + ggipc_close_subscription(release()); |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + /// Comparisons for associative containers. |
| 128 | + constexpr std::strong_ordering operator<=>( |
| 129 | + const Subscription &other |
| 130 | + ) const noexcept { |
| 131 | + return handle.val <=> other.handle.val; |
| 132 | + } |
| 133 | +}; |
| 134 | + |
| 135 | +} |
| 136 | + |
| 137 | +/// Subscription hashing |
| 138 | +template <> class std::hash<ggl::ipc::Subscription> { |
| 139 | +public: |
| 140 | + constexpr std::size_t operator()( |
| 141 | + const ggl::ipc::Subscription &subscription |
| 142 | + ) const noexcept { |
| 143 | + return std::hash<std::uint32_t> {}(subscription.get().val); |
| 144 | + } |
| 145 | +}; |
| 146 | + |
| 147 | +#endif |
0 commit comments