HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
notifier.hpp
1// Copyright Take Vos 2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../utility/utility.hpp"
8#include "unfair_mutex.hpp"
9#include "callback_flags.hpp"
10#include "../macros.hpp"
11#include <vector>
12#include <tuple>
13#include <functional>
14#include <coroutine>
15#include <mutex>
16
17
18
19namespace hi::inline v1 {
20
26template<typename T = void()>
27class notifier {
28};
29
30// The partial template specialization allows the use of a `std::function`-like template
31// argument, that looks like a function prototype.
32template<typename Result, typename... Args>
33class notifier<Result(Args...)> {
34public:
35 static_assert(std::is_same_v<Result, void>, "Result of a notifier must be void.");
36
37 using result_type = Result;
38 using callback_proto = Result(Args...);
40
43
50 class awaiter_type {
51 public:
52 constexpr awaiter_type() noexcept = default;
53 constexpr awaiter_type(awaiter_type const&) noexcept = default;
54 constexpr awaiter_type(awaiter_type&&) noexcept = default;
55 constexpr awaiter_type& operator=(awaiter_type const&) noexcept = default;
56 constexpr awaiter_type& operator=(awaiter_type&&) noexcept = default;
57
58 constexpr awaiter_type(notifier& notifier) noexcept : _notifier(&notifier) {}
59
60 [[nodiscard]] constexpr bool await_ready() noexcept
61 {
62 return false;
63 }
64
65 void await_suspend(std::coroutine_handle<> handle) noexcept
66 {
67 hi_assert_not_null(_notifier);
68
69 // We can use the this pointer in the callback, as `await_suspend()` is called by
70 // the co-routine on the same object as `await_resume()`.
71 _cbt = _notifier->subscribe(
72 [this, handle](Args const&...args) {
73 // Copy the arguments received from the notifier into the awaitable object
74 // So that it can be read using `await_resume()`.
75 _args = {args...};
76
77 // Resume the co-routine.
78 handle.resume();
79 },
80 callback_flags::main | callback_flags::once);
81 }
82
83 constexpr void await_resume() const noexcept requires(sizeof...(Args) == 0) {}
84
85 constexpr auto await_resume() const noexcept requires(sizeof...(Args) == 1)
86 {
87 return std::get<0>(_args);
88 }
89
90 constexpr auto await_resume() const noexcept requires(sizeof...(Args) > 1)
91 {
92 return _args;
93 }
94
95 private:
96 notifier *_notifier = nullptr;
97 callback_token _cbt;
98 std::tuple<Args...> _args;
99 };
100
103 constexpr notifier() noexcept = default;
104 constexpr notifier(notifier&&) noexcept = default;
105 constexpr notifier(notifier const&) noexcept = default;
106 constexpr notifier& operator=(notifier&&) noexcept = default;
107 constexpr notifier& operator=(notifier const&) noexcept = default;
108
111 awaiter_type operator co_await() const noexcept
112 {
113 return awaiter_type{const_cast<notifier&>(*this)};
114 }
115
125 [[nodiscard]] callback_token
126 subscribe(forward_of<callback_proto> auto&& callback, callback_flags flags = callback_flags::synchronous) noexcept
127 {
128 auto token = std::make_shared<function_type>(hi_forward(callback));
129
130 hilet lock = std::scoped_lock(_mutex);
131 _callbacks.emplace_back(token, flags);
132 return token;
133 }
134
135 template<typename F>
136 void loop_local_post_function(F&&) const noexcept;
137 template<typename F>
138 void loop_main_post_function(F&&) const noexcept;
139 template<typename F>
140 void loop_timer_post_function(F&&) const noexcept;
141
147 void operator()(Args const&...args) const noexcept
148 {
149 hilet lock = std::scoped_lock(_mutex);
150
151 for (auto& callback : _callbacks) {
152 if (is_synchronous(callback.flags)) {
153 if (auto func = callback.lock()) {
154 (*func)(args...);
155 }
156
157 } else if (is_local(callback.flags)) {
158 loop_local_post_function([=] {
159 if (auto func = callback.lock()) {
160 (*func)(args...);
161 }
162 });
163
164 } else if (is_main(callback.flags)) {
165 loop_main_post_function([=] {
166 if (auto func = callback.lock()) {
167 (*func)(args...);
168 }
169 });
170
171 } else if (is_timer(callback.flags)) {
172 loop_timer_post_function([=] {
173 if (auto func = callback.lock()) {
174 (*func)(args...);
175 }
176 });
177
178 } else {
179 hi_no_default();
180 }
181
182 // If the callback should only be triggered once, like inside an awaitable.
183 // Then reset the weak_ptr in _callbacks so that it will be cleaned up.
184 // In the lambda above the weak_ptr is copied first so that it callback will get executed
185 // as long as the shared_ptr's use count does not go to zero.
186 if (is_once(callback.flags)) {
187 callback.reset();
188 }
189 }
190 clean_up();
191 }
192
193private:
194 struct callback_type {
195 weak_callback_token token;
196 callback_flags flags;
197
198 [[nodiscard]] bool expired() const noexcept
199 {
200 return token.expired();
201 }
202
203 void reset() noexcept
204 {
205 token.reset();
206 }
207
208 [[nodiscard]] callback_token lock() const noexcept
209 {
210 return token.lock();
211 }
212 };
213
214 mutable unfair_mutex _mutex;
215
218 mutable std::vector<callback_type> _callbacks;
219
220 void clean_up() const noexcept
221 {
222 hi_axiom(_mutex.is_locked());
223
224 // Cleanup all callbacks that have expired, or when they may only be triggered once.
225 std::erase_if(_callbacks, [](hilet& item) {
226 return item.expired();
227 });
228 }
229
230#ifndef NDEBUG
233 mutable bool _notifying = false;
234#endif
235};
236
237} // namespace hi::inline v1
Definition of the unfair_mutex.
DOXYGEN BUG.
Definition algorithm.hpp:16
callback_flags
Definition callback_flags.hpp:14
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
A notifier which can be used to call a set of registered callbacks.
Definition notifier.hpp:27
callback_token subscribe(forward_of< callback_proto > auto &&callback, callback_flags flags=callback_flags::synchronous) noexcept
Add a callback to the notifier.
Definition notifier.hpp:126
constexpr notifier() noexcept=default
Create a notifier.
T lock(T... args)