C++ Essence Library 0.1.0
A Utility Library for Modern C++ Programming
Loading...
Searching...
No Matches
delegate.hpp
1/*
2 * Copyright (c) 2024 The RefValue Project
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22
23#pragma once
24
25#include "char8_t_remediation.hpp"
26#include "error_extensions.hpp"
27#include "scope.hpp"
28
29#ifndef __EMSCRIPTEN__
30#include <atomic>
31#endif
32
33#include <algorithm>
34#include <concepts>
35#include <cstddef>
36#include <cstdint>
37#include <functional>
38#include <memory>
39#include <optional>
40#include <tuple>
41#include <type_traits>
42#include <utility>
43#include <vector>
44
45#if __has_include(<version> )
46#include <version>
47#endif
48
49namespace essence {
50 template <typename Function>
51 class delegate {};
52
53 template <typename Signature>
54 requires std::is_function_v<Signature>
55 class delegate<std::function<Signature>> : public delegate<Signature> {};
56
57 template <typename R, typename... Args>
58 class delegate<R(Args...)> {
59 public:
60 using function_type = std::function<R(Args...)>;
61 using nothrow_function_type =
62 std::conditional_t<std::is_void_v<R>, function_type, std::function<std::optional<R>(Args...)>>;
63
64 delegate() : lifetime_observer_{std::make_shared<std::byte>()} {
65 update_readable_buffer();
66 }
67
68 delegate(const delegate&) = delete;
69 delegate(delegate&&) noexcept = delete;
70 delegate& operator=(const delegate&) = delete;
71 delegate& operator=(delegate&&) noexcept = delete;
72
73 explicit operator bool() const noexcept {
74#ifdef __EMSCRIPTEN__
75 auto func = readable_buffer_;
76#else
77 auto func = std::atomic_load_explicit(&readable_buffer_, std::memory_order::acquire);
78#endif
79
80 return func && static_cast<bool>(*func);
81 }
82
83 decltype(auto) operator+=(const delegate & right) {
84 for (auto&& [id, func] : right.listeners_) {
85 add_listener<false>(func);
86 }
87
88 update_readable_buffer();
89
90 return *this;
91 }
92
93 template <std::convertible_to<function_type> Callable>
94 decltype(auto) operator+=(Callable && handler) {
95 add_listener<true>(std::forward<Callable>(handler));
96
97 return *this;
98 }
99
100 template <typename... Equivalents>
101 requires std::is_invocable_r_v<R, function_type, Equivalents...>
102 auto try_invoke(Equivalents&&... args) const {
103#ifdef __EMSCRIPTEN__
104 auto buffer = readable_buffer_;
105#else
106 // Fetches the buffer automatically.
107 auto buffer = std::atomic_load_explicit(&readable_buffer_, std::memory_order::acquire);
108#endif
109
110 if constexpr (std::is_void_v<R>) {
111 if (buffer) {
112 for (auto ptr = buffer.get(); *ptr; ++ptr) {
113 (*ptr)(std::forward<Equivalents>(args)...);
114 }
115 }
116 } else {
117 if (!buffer) {
118 return std::optional<R>{};
119 }
120
121 auto ptr = buffer.get();
122 auto tuple = std::forward_as_tuple(std::forward<Equivalents>(args)...);
123 auto handler = [&](auto inner) -> std::optional<R> { return std::apply(*inner, std::move(tuple)); };
124
125 // Exactly one single subscriber.
126 if (!ptr[1]) {
127 return handler(ptr);
128 }
129
130 for (; *(ptr + 1); ++ptr) {
131 static_cast<void>(handler(ptr));
132 }
133
134 // Returns the result of the final subscriber.
135 return handler(ptr);
136 }
137 }
138
139 template <typename... Equivalents>
140 requires std::is_invocable_r_v<R, function_type, Equivalents...>
141 R operator()(Equivalents&&... args) const {
142 if constexpr (std::is_void_v<R>) {
143 return try_invoke(std::forward<Equivalents>(args)...);
144 } else {
145 auto result = try_invoke(std::forward<Equivalents>(args)...);
146
147 if (!result) {
149 U8("A delegate with a return value cannot be invoked, within which no subscriber exists.")};
150 }
151
152 return std::move(*result);
153 }
154 }
155
156 template <std::convertible_to<function_type> Callable>
157 auto add_listener(Callable&& handler) {
158 auto id = add_listener<true>(std::forward<Callable>(handler));
159 auto lifetime_handler = [this, id, observer = std::weak_ptr<void>{lifetime_observer_}] {
160 if (auto lock = observer.lock()) {
161 remove_listener(id);
162 }
163 };
164
165 return std::make_shared<scope_exit<decltype(lifetime_handler)>>(std::move(lifetime_handler));
166 }
167
168 function_type to_function() const {
169 return [this]<typename... Equivalents>(
170 Equivalents&&... args) { return (*this)(std::forward<Equivalents>(args)...); };
171 }
172
173 nothrow_function_type to_nothrow_function() const {
174 return [this]<typename... Equivalents>(
175 Equivalents&&... args) { return this->try_invoke(std::forward<Equivalents>(args)...); };
176 }
177
178 private:
179 template <bool Update, std::convertible_to<function_type> Callable>
180 auto add_listener(Callable&& handler) {
181#ifdef __EMSCRIPTEN__
182 auto counter = ++global_counter_;
183#else
184 auto counter = global_counter_.fetch_add(1, std::memory_order::acq_rel) + 1;
185#endif
186
187 auto id = listeners_.emplace_back(counter, std::forward<Callable>(handler)).first;
188
189 if constexpr (Update) {
190 update_readable_buffer();
191 }
192
193 return id;
194 }
195
196 void remove_listener(std::uint64_t id) {
197 if (auto iter = std::find_if(listeners_.begin(), listeners_.end(),
198 [&](const std::pair<std::uint64_t, function_type>& inner) { return inner.first == id; });
199 iter != listeners_.end()) {
200 listeners_.erase(iter);
201 update_readable_buffer();
202 }
203 }
204
205 void update_readable_buffer() {
206 // This statement is exception-safe here.
207 // An unbounded array is not considered because there is no corresponding overload for
208 // std::atomic<std::shared_ptr<T>>::load.
209#if __cpp_lib_smart_ptr_for_overwrite >= 202002L
210 auto buffer = std::static_pointer_cast<function_type>(
211 std::make_shared_for_overwrite<function_type[]>(listeners_.size() + 1));
212#else
213 std::shared_ptr<function_type> buffer{
214 new function_type[listeners_.size() + 1], std::default_delete<function_type[]> {}};
215#endif
216 std::transform(listeners_.begin(), listeners_.end(), buffer.get(),
217 [](const std::pair<std::uint64_t, function_type>& inner) { return inner.second; });
218
219#ifdef __EMSCRIPTEN__
220 readable_buffer_ = std::move(buffer);
221#else
222 std::atomic_store_explicit(&readable_buffer_, std::move(buffer), std::memory_order::release);
223#endif
224 }
225
226 std::vector<std::pair<std::uint64_t, function_type>> listeners_;
227
228#ifdef __EMSCRIPTEN__
229 std::shared_ptr<function_type> readable_buffer_;
230#elif __cpp_lib_atomic_shared_ptr >= 201711L
231 std::atomic<std::shared_ptr<function_type>> readable_buffer_;
232#else
233 std::shared_ptr<function_type> readable_buffer_;
234#endif
235
236 std::shared_ptr<void> lifetime_observer_;
237
238#ifdef __EMSCRIPTEN__
239 inline static std::uint64_t global_counter_;
240#else
241 inline static std::atomic_uint64_t global_counter_;
242#endif
243 };
244} // namespace essence
Definition delegate.hpp:51
Definition scope.hpp:35
An exception class derived from std::runtime_error that provides source code information of the sourc...
Definition error_extensions.hpp:73