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