Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
subscription.hpp
Go to the documentation of this file.
1#pragma once
2
4
5// ============================================================================
6// subscription.hpp
7// ----------------------------------------------------------------------------
8// Unified RAII subscription handle for the whole framework.
9//
10// In Aria there are two independent event mechanisms:
11//
12// 1. reactive::Graph -- dependency-tracked values and derivations
13// (Property, Computed, Effect). Its subscriptions own a
14// Reaction node; dropping the handle destroys the node, which
15// automatically detaches from the graph.
16//
17// 2. abi::SignalErased -- type-erased, fire-and-forget signals used
18// for events that do not participate in dependency tracking
19// (ObservableList diffs, Command::can_execute_changed, EventBus).
20// Its subscriptions own a small "disconnector" shim whose
21// destructor calls back into the signal to remove the slot.
22//
23// Both live behind a single user-facing handle: `Subscription`. Internally
24// the handle is nothing more than a `std::shared_ptr<void>` -- destruction
25// of the last-owning Subscription destroys whatever the void* points at,
26// which in turn performs the backend-specific disconnect.
27//
28// This unification is what lets `SubscriptionBag` aggregate any mix of
29// reactive/event subscriptions without caring about their origin.
30// ============================================================================
31
32#include <cstddef>
33#include <concepts>
34#include <type_traits>
35#include <functional>
36#include <memory>
37#include <utility>
38#include <vector>
39
40namespace aria {
41
45public:
46 Subscription() noexcept = default;
47
51 template<class T>
52 explicit Subscription(std::shared_ptr<T> owner) noexcept
53 : owner_(std::move(owner)) {}
54
58 template<class Fn>
59 requires std::invocable<std::decay_t<Fn>&>
60 explicit Subscription(Fn&& on_disconnect) {
61 if constexpr (std::is_pointer_v<std::decay_t<Fn>>) {
62 if (!on_disconnect) return;
63 }
64 owner_ = std::make_shared<CallbackDeleter<std::decay_t<Fn>>>(
65 std::forward<Fn>(on_disconnect));
66 }
67
68 explicit Subscription(std::function<void()> on_disconnect) {
69 if (on_disconnect) {
70 owner_ = std::make_shared<CallbackDeleter<std::function<void()>>>(
71 std::move(on_disconnect));
72 }
73 }
74
75 Subscription(const Subscription&) = delete;
77 Subscription(Subscription&&) noexcept = default;
78 Subscription& operator=(Subscription&&) noexcept = default;
79
80 ~Subscription() noexcept = default;
81
83 void release() noexcept { owner_.reset(); }
84
85 [[nodiscard]] bool active() const noexcept { return static_cast<bool>(owner_); }
86 explicit operator bool() const noexcept { return active(); }
87
88private:
89 template<class Fn>
90 struct CallbackDeleter {
91 Fn fn;
92
93 template<class F>
94 explicit CallbackDeleter(F&& callback) : fn(std::forward<F>(callback)) {}
95
96 CallbackDeleter(CallbackDeleter&&) = delete;
97 CallbackDeleter& operator=(CallbackDeleter&&) = delete;
98 CallbackDeleter(const CallbackDeleter&) = delete;
99 CallbackDeleter& operator=(const CallbackDeleter&) = delete;
100
101 ~CallbackDeleter() noexcept {
102 try {
103 std::invoke(fn);
104 } catch (...) {
105 report_callback_failure("subscription.disconnect", std::current_exception());
106 }
107 }
108 };
109
110 std::shared_ptr<void> owner_;
111};
112
117public:
118 SubscriptionBag() = default;
119
122 SubscriptionBag(SubscriptionBag&& other) noexcept : subs_(std::move(other.subs_)) {}
124 if (this != &other) {
125 auto previous = std::move(subs_);
126 subs_ = std::move(other.subs_);
127 release_reverse_(previous);
128 }
129 return *this;
130 }
131
132 ~SubscriptionBag() noexcept {
133 destroying_ = true;
134 clear();
135 }
136
138 if (!destroying_) subs_.push_back(std::move(s));
139 }
140
142 add(std::move(s));
143 return *this;
144 }
145
148 void clear() noexcept {
149 std::vector<Subscription> previous;
150 previous.swap(subs_);
151 release_reverse_(previous);
152 }
153
154 [[nodiscard]] std::size_t size() const noexcept { return subs_.size(); }
155 [[nodiscard]] bool empty() const noexcept { return subs_.empty(); }
156
157private:
158 static void release_reverse_(std::vector<Subscription>& subscriptions) noexcept {
159 while (!subscriptions.empty()) {
160 auto last = std::move(subscriptions.back());
161 subscriptions.pop_back();
162 last.release();
163 }
164 }
165
166 std::vector<Subscription> subs_;
167 bool destroying_ = false;
168};
169
170} // namespace aria
SubscriptionBag & operator+=(Subscription s)
Definition subscription.hpp:141
SubscriptionBag(const SubscriptionBag &)=delete
void add(Subscription s)
Definition subscription.hpp:137
~SubscriptionBag() noexcept
Definition subscription.hpp:132
SubscriptionBag(SubscriptionBag &&other) noexcept
Definition subscription.hpp:122
bool empty() const noexcept
Definition subscription.hpp:155
SubscriptionBag & operator=(const SubscriptionBag &)=delete
std::size_t size() const noexcept
Definition subscription.hpp:154
SubscriptionBag & operator=(SubscriptionBag &&other) noexcept
Definition subscription.hpp:123
void clear() noexcept
Disconnect the current contents in reverse insertion order.
Definition subscription.hpp:148
RAII handle to a single subscription.
Definition subscription.hpp:44
Subscription(const Subscription &)=delete
Subscription(Fn &&on_disconnect)
Invoke a disconnect callback exactly once when this handle is released.
Definition subscription.hpp:60
Subscription(std::function< void()> on_disconnect)
Definition subscription.hpp:68
bool active() const noexcept
Definition subscription.hpp:85
Subscription(Subscription &&) noexcept=default
Subscription & operator=(const Subscription &)=delete
void release() noexcept
Explicitly disconnect now (instead of at destruction).
Definition subscription.hpp:83
Subscription() noexcept=default
Definition signal.hpp:12
void report_callback_failure(std::string_view category, std::exception_ptr exception, std::string_view message={}) noexcept
Report a callback failure.
Definition validation_key.hpp:110