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 "../concurrency/concurrency.hpp"
9#include "../macros.hpp"
10#include <vector>
11#include <tuple>
12#include <functional>
13#include <coroutine>
14#include <mutex>
15
16hi_export_module(hikogui.dispatch : notifier);
17
18hi_export namespace hi::inline v1 {
19
25template<typename T = void()>
27
28// The partial template specialization allows the use of a `std::function`-like template
29// argument, that looks like a function prototype.
30template<typename R, typename... Args>
31class notifier<R(Args...)> {
32public:
33 static_assert(std::is_same_v<R, void>, "Result of a notifier must be void.");
34
35 using result_type = R;
36 using callback_proto = R(Args...);
37
38 using callback_type = callback<R(Args...)>;
39 using weak_callback_type = weak_callback<R(Args...)>;
40
47 class awaiter_type {
48 public:
49 constexpr awaiter_type() noexcept = default;
50 constexpr awaiter_type(awaiter_type&&) noexcept = default;
51 constexpr awaiter_type& operator=(awaiter_type const&) noexcept = delete;
52
58 constexpr awaiter_type(awaiter_type const& other) noexcept : _notifier(other._notifier) {}
59
65 constexpr awaiter_type& operator=(awaiter_type&&other) noexcept
66 {
67 _cbt = nullptr;
68 _args = {};
69 _notifier = other._notifier;
70 }
71
72 constexpr awaiter_type(notifier& notifier) noexcept : _notifier(&notifier) {}
73
74 [[nodiscard]] constexpr bool await_ready() const noexcept
75 {
76 return false;
77 }
78
79 void await_suspend(std::coroutine_handle<> handle) noexcept
80 {
81 hi_assert_not_null(_notifier);
82
83 // We can use the this pointer in the callback, as `await_suspend()` is called by
84 // the co-routine on the same object as `await_resume()`.
85 _cbt = _notifier->subscribe(
86 [this, handle](Args... args) {
87 // Copy the arguments received from the notifier into the awaitable object
88 // So that it can be read using `await_resume()`.
89 _args = {std::forward<Args>(args)...};
90
91 // Resume the co-routine.
92 handle.resume();
93 },
94 callback_flags::main | callback_flags::once);
95 }
96
97 constexpr void await_resume() const noexcept
98 requires(sizeof...(Args) == 0)
99 {
100 }
101
102 constexpr auto await_resume() const noexcept
103 requires(sizeof...(Args) == 1)
104 {
105 return std::get<0>(_args);
106 }
107
108 constexpr auto await_resume() const noexcept
109 requires(sizeof...(Args) > 1)
110 {
111 return _args;
112 }
113
114 private:
115 notifier *_notifier = nullptr;
116 callback<R(Args...)> _cbt;
117 std::tuple<Args...> _args;
118 };
119
122 constexpr notifier() noexcept = default;
123 constexpr notifier(notifier&&) noexcept = delete;
124 constexpr notifier(notifier const&) noexcept = delete;
125 constexpr notifier& operator=(notifier&&) noexcept = delete;
126 constexpr notifier& operator=(notifier const&) noexcept = delete;
127
130 awaiter_type operator co_await() const noexcept
131 {
132 return awaiter_type{const_cast<notifier&>(*this)};
133 }
134
147 template<forward_of<callback_proto> Func>
148 [[nodiscard]] callback_type subscribe(Func&& func, callback_flags flags = callback_flags::synchronous) noexcept
149 {
150 auto callback = callback_type{std::forward<Func>(func)};
151 auto const lock = std::scoped_lock(_mutex);
152 _callbacks.emplace_back(callback, flags);
153 return callback;
154 }
155
156 template<forward_of<void()> F>
157 void loop_local_post_function(F&&) const noexcept;
158 template<forward_of<void()> F>
159 void loop_main_post_function(F&&) const noexcept;
160 template<forward_of<void()> F>
161 void loop_timer_post_function(F&&) const noexcept;
162
168 void operator()(Args... args) const noexcept
169 {
170 auto const lock = std::scoped_lock(_mutex);
171
172 for (auto& [callback, flags] : _callbacks) {
173 if (is_synchronous(flags)) {
174 if (auto cb = callback.lock()) {
175 cb(std::forward<Args>(args)...);
176 }
177
178 } else if (is_local(flags)) {
179 loop_local_post_function([=] {
180 // The callback object here is captured by-copy, so that
181 // the loop can check if it was expired.
182 if (auto cb = callback.lock()) {
183 // The captured arguments are now plain copies so we do
184 // not forward them in the call.
185 cb(args...);
186 }
187 });
188
189 } else if (is_main(flags)) {
190 loop_main_post_function([=] {
191 // The callback object here is captured by-copy, so that
192 // the loop can check if it was expired.
193 if (auto cb = callback.lock()) {
194 // The captured arguments are now plain copies so we do
195 // not forward them in the call.
196 cb(args...);
197 }
198 });
199
200 } else if (is_timer(flags)) {
201 loop_timer_post_function([=] {
202 // The callback object here is captured by-copy, so that
203 // the loop can check if it was expired.
204 if (auto cb = callback.lock()) {
205 // The captured arguments are now plain copies so we do
206 // not forward them in the call.
207 cb(args...);
208 }
209 });
210
211 } else {
212 hi_no_default();
213 }
214
215 // If the callback should only be triggered once, like inside an awaitable.
216 // Then reset the weak_ptr in _callbacks so that it will be cleaned up.
217 // In the lambda above the weak_ptr is copied first so that it callback will get executed
218 // as long as the shared_ptr's use count does not go to zero.
219 if (is_once(flags)) {
220 callback.reset();
221 }
222 }
223 clean_up();
224 }
225
226private:
227 mutable unfair_mutex _mutex;
228
232
233 void clean_up() const noexcept
234 {
235 hi_axiom(_mutex.is_locked());
236
237 // Cleanup all callbacks that have expired, or when they may only be triggered once.
238 std::erase_if(_callbacks, [](auto const& item) {
239 return item.first.expired();
240 });
241 }
242
243#ifndef NDEBUG
246 mutable bool _notifying = false;
247#endif
248};
249
250} // namespace hi::inline v1
DOXYGEN BUG.
Definition algorithm_misc.hpp:20
callback_flags
Definition callback_flags.hpp:15
A notifier which can be used to call a set of registered callbacks.
Definition notifier.hpp:26
constexpr notifier() noexcept=default
Create a notifier.
callback_type subscribe(Func &&func, callback_flags flags=callback_flags::synchronous) noexcept
Add a callback to the notifier.
Definition notifier.hpp:148
constexpr awaiter_type & operator=(awaiter_type &&other) noexcept
Copy the awaitable.
Definition notifier.hpp:65
constexpr awaiter_type(awaiter_type const &other) noexcept
Copy the awaitable.
Definition notifier.hpp:58