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.hpp"
8#include "generator.hpp"
9#include "loop.hpp"
10#include "callback_flags.hpp"
11#include "unfair_mutex.hpp"
12#include <vector>
13#include <tuple>
14#include <functional>
15#include <coroutine>
16#include <mutex>
17
18namespace hi::inline v1 {
19
25template<typename T = void()>
26class notifier {
27};
28
29// The partial template specialization allows the use of a `std::function`-like template
30// argument, that looks like a function prototype.
31template<typename Result, typename... Args>
32class notifier<Result(Args...)> {
33public:
34 static_assert(std::is_same_v<Result, void>, "Result of a notifier must be void.");
35
36 using result_type = Result;
37 using callback_proto = Result(Args...);
39
42
49 class awaiter_type {
50 public:
51 constexpr awaiter_type() noexcept = default;
52 constexpr awaiter_type(awaiter_type const&) noexcept = default;
53 constexpr awaiter_type(awaiter_type&&) noexcept = default;
54 constexpr awaiter_type& operator=(awaiter_type const&) noexcept = default;
55 constexpr awaiter_type& operator=(awaiter_type&&) noexcept = default;
56
57 constexpr awaiter_type(notifier& notifier) noexcept : _notifier(&notifier) {}
58
59 [[nodiscard]] constexpr bool await_ready() noexcept
60 {
61 return false;
62 }
63
64 void await_suspend(std::coroutine_handle<> handle) noexcept
65 {
66 hi_assert_not_null(_notifier);
67
68 // We can use the this pointer in the callback, as `await_suspend()` is called by
69 // the co-routine on the same object as `await_resume()`.
70 _cbt = _notifier->subscribe(
71 [this, handle](Args const&...args) {
72 // Copy the arguments received from the notifier into the awaitable object
73 // So that it can be read using `await_resume()`.
74 _args = {args...};
75
76 // Resume the co-routine.
77 handle.resume();
78 },
79 callback_flags::main | callback_flags::once);
80 }
81
82 constexpr void await_resume() const noexcept requires(sizeof...(Args) == 0) {}
83
84 constexpr auto await_resume() const noexcept requires(sizeof...(Args) == 1)
85 {
86 return std::get<0>(_args);
87 }
88
89 constexpr auto await_resume() const noexcept requires(sizeof...(Args) > 1)
90 {
91 return _args;
92 }
93
94 private:
95 notifier *_notifier = nullptr;
96 callback_token _cbt;
97 std::tuple<Args...> _args;
98 };
99
102 constexpr notifier() noexcept = default;
103 constexpr notifier(notifier&&) noexcept = default;
104 constexpr notifier(notifier const&) noexcept = default;
105 constexpr notifier& operator=(notifier&&) noexcept = default;
106 constexpr notifier& operator=(notifier const&) noexcept = default;
107
110 awaiter_type operator co_await() const noexcept
111 {
112 return awaiter_type{const_cast<notifier&>(*this)};
113 }
114
124 [[nodiscard]] callback_token
125 subscribe(forward_of<callback_proto> auto&& callback, callback_flags flags = callback_flags::synchronous) noexcept
126 {
127 auto token = std::make_shared<function_type>(hi_forward(callback));
128
129 hilet lock = std::scoped_lock(_mutex);
130 _callbacks.emplace_back(token, flags);
131 return token;
132 }
133
139 void operator()(Args const&...args) const noexcept
140 {
141 hilet lock = std::scoped_lock(_mutex);
142
143 for (auto& callback : _callbacks) {
144 if (is_synchronous(callback.flags)) {
145 if (auto func = callback.lock()) {
146 (*func)(args...);
147 }
148
149 } else if (is_local(callback.flags)) {
150 loop::local().post_function([=] {
151 if (auto func = callback.lock()) {
152 (*func)(args...);
153 }
154 });
155
156 } else if (is_main(callback.flags)) {
157 loop::main().post_function([=] {
158 if (auto func = callback.lock()) {
159 (*func)(args...);
160 }
161 });
162
163 } else if (is_timer(callback.flags)) {
164 loop::timer().post_function([=] {
165 if (auto func = callback.lock()) {
166 (*func)(args...);
167 }
168 });
169
170 } else {
172 }
173
174 // If the callback should only be triggered once, like inside an awaitable.
175 // Then reset the weak_ptr in _callbacks so that it will be cleaned up.
176 // In the lambda above the weak_ptr is copied first so that it callback will get executed
177 // as long as the shared_ptr's use count does not go to zero.
178 if (is_once(callback.flags)) {
179 callback.reset();
180 }
181 }
182 clean_up();
183 }
184
185private:
186 struct callback_type {
187 weak_callback_token token;
188 callback_flags flags;
189
190 [[nodiscard]] bool expired() const noexcept
191 {
192 return token.expired();
193 }
194
195 void reset() noexcept
196 {
197 token.reset();
198 }
199
200 [[nodiscard]] callback_token lock() const noexcept
201 {
202 return token.lock();
203 }
204 };
205
206 mutable unfair_mutex _mutex;
207
210 mutable std::vector<callback_type> _callbacks;
211
212 void clean_up() const noexcept
213 {
214 hi_axiom(_mutex.is_locked());
215
216 // Cleanup all callbacks that have expired, or when they may only be triggered once.
217 std::erase_if(_callbacks, [](hilet& item) {
218 return item.expired();
219 });
220 }
221
222#ifndef NDEBUG
225 mutable bool _notifying = false;
226#endif
227};
228
229} // namespace hi::inline v1
#define hi_axiom(expression)
Specify an axiom; an expression that is true.
Definition assert.hpp:133
#define hi_assert_not_null(x)
Assert if an expression is not nullptr.
Definition assert.hpp:118
#define hi_no_default()
This part of the code should not be reachable, unless a programming bug.
Definition assert.hpp:145
Utilities used by the HikoGUI library itself.
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition utility.hpp:29
DOXYGEN BUG.
Definition algorithm.hpp:15
callback_flags
Definition callback_flags.hpp:12
A notifier which can be used to call a set of registered callbacks.
Definition notifier.hpp:26
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:125
constexpr notifier() noexcept=default
Create a notifier.
void operator()(Args const &...args) const noexcept
Call the subscribed callbacks with the given arguments.
Definition notifier.hpp:139
True if T is a forwarded type of Forward.
Definition concepts.hpp:130
T lock(T... args)