HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
notifier.hpp
1// Copyright Take Vos 2020.
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 "required.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
16namespace hi::inline v1 {
17
23template<typename T = void()>
24class notifier {
25};
26
27// The partial template specialization allows the use of a `std::function`-like template
28// argument, that looks like a function prototype.
29template<typename Result, typename... Args>
30class notifier<Result(Args...)> {
31public:
32 static_assert(std::is_same_v<Result, void>, "Result of a notifier must be void.");
33
34 using result_type = Result;
35 using function_type = std::function<Result(Args const&...)>;
36
39
46 class awaiter_type {
47 public:
48 constexpr awaiter_type() noexcept = default;
49 constexpr awaiter_type(awaiter_type const&) noexcept = default;
50 constexpr awaiter_type(awaiter_type&&) noexcept = default;
51 constexpr awaiter_type& operator=(awaiter_type const&) noexcept = default;
52 constexpr awaiter_type& operator=(awaiter_type&&) noexcept = default;
53
54 constexpr awaiter_type(notifier& notifier) noexcept : _notifier(&notifier) {}
55
56 [[nodiscard]] constexpr bool await_ready() noexcept
57 {
58 return false;
59 }
60
61 void await_suspend(std::coroutine_handle<> handle) noexcept
62 {
63 hi_axiom(_notifier != nullptr);
64
65 // We can use the this pointer in the callback, as `await_suspend()` is called by
66 // the co-routine on the same object as `await_resume()`.
67 _cbt = _notifier->subscribe(callback_flags::main | callback_flags::once, [this, handle](Args const&...args) {
68 // Copy the arguments received from the notifier into the awaitable object
69 // So that it can be read using `await_resume()`.
70 _args = {args...};
71
72 // Resume the co-routine.
73 handle.resume();
74 });
75 }
76
77 constexpr void await_resume() const noexcept requires(sizeof...(Args) == 0) {}
78
79 constexpr auto await_resume() const noexcept requires(sizeof...(Args) == 1)
80 {
81 return std::get<0>(_args);
82 }
83
84 constexpr auto await_resume() const noexcept requires(sizeof...(Args) > 1)
85 {
86 return _args;
87 }
88
89 private : notifier *_notifier = nullptr;
90 token_type _cbt;
91 std::tuple<Args...> _args;
92 };
93
96 constexpr notifier() noexcept = default;
97 notifier(notifier&&) = delete;
98 notifier(notifier const&) = delete;
99 notifier& operator=(notifier&&) = delete;
100 notifier& operator=(notifier const&) = delete;
101
104 awaiter_type operator co_await() const noexcept
105 {
106 return awaiter_type{const_cast<notifier&>(*this)};
107 }
108
117 [[nodiscard]] token_type subscribe(callback_flags flags, std::invocable<Args...> auto&& callback) noexcept
118 {
119 auto token = std::make_shared<function_type>(hi_forward(callback));
120 _callbacks.emplace_back(token, flags);
121 return token;
122 }
123
124 [[nodiscard]] token_type subscribe(std::invocable<Args...> auto&& callback) noexcept
125 {
126 return subscribe(callback_flags::synchronous, hi_forward(callback));
127 }
128
134 void operator()(Args const&...args) const noexcept
135 {
136 for (auto& callback : _callbacks) {
137 if (is_synchronous(callback.flags)) {
138 if (auto func = callback.lock()) {
139 (*func)(args...);
140 }
141
142 } else if (is_local(callback.flags)) {
143 loop::local().post_function([=] {
144 if (auto func = callback.lock()) {
145 (*func)(args...);
146 }
147 });
148
149 } else if (is_main(callback.flags)) {
150 loop::main().post_function([=] {
151 if (auto func = callback.lock()) {
152 (*func)(args...);
153 }
154 });
155
156 } else if (is_timer(callback.flags)) {
157 loop::timer().post_function([=] {
158 if (auto func = callback.lock()) {
159 (*func)(args...);
160 }
161 });
162
163 } else {
164 hi_no_default();
165 }
166
167 // If the callback should only be triggered once, like inside an awaitable.
168 // Then reset the weak_ptr in _callbacks so that it will be cleaned up.
169 // In the lambda above the weak_ptr is copied first so that it callback will get executed
170 // as long as the shared_ptr's use count does not go to zero.
171 if (is_once(callback.flags)) {
172 callback.reset();
173 }
174 }
175 clean_up();
176 }
177
178private:
179 struct callback_type {
180 weak_token_type token;
181 callback_flags flags;
182
183 [[nodiscard]] bool expired() const noexcept
184 {
185 return token.expired();
186 }
187
188 void reset() noexcept
189 {
190 token.reset();
191 }
192
193 [[nodiscard]] token_type lock() const noexcept
194 {
195 return token.lock();
196 }
197 };
198
201 mutable std::vector<callback_type> _callbacks;
202
203 void clean_up() const noexcept
204 {
205 // Cleanup all callbacks that have expired, or when they may only be triggered once.
206 std::erase_if(_callbacks, [](hilet& item) {
207 return item.expired();
208 });
209 }
210
211#if HI_BUILD_TYPE == HI_BT_DEBUG
214 mutable bool _notifying = false;
215#endif
216};
217
218} // namespace hi::inline v1
This file includes required definitions.
#define hilet
Invariant should be the default for variables.
Definition required.hpp:23
#define hi_forward(x)
Forward a value, based on the decltype of the value.
Definition required.hpp:29
A notifier which can be used to call a set of registered callbacks.
Definition notifier.hpp:24
constexpr notifier() noexcept=default
Create a notifier.
token_type subscribe(callback_flags flags, std::invocable< Args... > auto &&callback) noexcept
Add a callback to the notifier.
Definition notifier.hpp:117
void operator()(Args const &...args) const noexcept
Call the subscribed callbacks with the given arguments.
Definition notifier.hpp:134
T lock(T... args)