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 <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 callback_proto = Result(Args...);
37
40
47 class awaiter_type {
48 public:
49 constexpr awaiter_type() noexcept = default;
50 constexpr awaiter_type(awaiter_type const&) noexcept = default;
51 constexpr awaiter_type(awaiter_type&&) noexcept = default;
52 constexpr awaiter_type& operator=(awaiter_type const&) noexcept = default;
53 constexpr awaiter_type& operator=(awaiter_type&&) noexcept = default;
54
55 constexpr awaiter_type(notifier& notifier) noexcept : _notifier(&notifier) {}
56
57 [[nodiscard]] constexpr bool await_ready() noexcept
58 {
59 return false;
60 }
61
62 void await_suspend(std::coroutine_handle<> handle) noexcept
63 {
64 hi_axiom(_notifier != nullptr);
65
66 // We can use the this pointer in the callback, as `await_suspend()` is called by
67 // the co-routine on the same object as `await_resume()`.
68 _cbt = _notifier->subscribe(
69 [this, handle](Args const&...args) {
70 // Copy the arguments received from the notifier into the awaitable object
71 // So that it can be read using `await_resume()`.
72 _args = {args...};
73
74 // Resume the co-routine.
75 handle.resume();
76 },
77 callback_flags::main | callback_flags::once);
78 }
79
80 constexpr void await_resume() const noexcept requires(sizeof...(Args) == 0) {}
81
82 constexpr auto await_resume() const noexcept requires(sizeof...(Args) == 1)
83 {
84 return std::get<0>(_args);
85 }
86
87 constexpr auto await_resume() const noexcept requires(sizeof...(Args) > 1)
88 {
89 return _args;
90 }
91
92 private:
93 notifier *_notifier = nullptr;
94 callback_token _cbt;
95 std::tuple<Args...> _args;
96 };
97
100 constexpr notifier() noexcept = default;
101 constexpr notifier(notifier&&) noexcept = default;
102 constexpr notifier(notifier const&) noexcept = default;
103 constexpr notifier& operator=(notifier&&) noexcept = default;
104 constexpr notifier& operator=(notifier const&) noexcept = default;
105
108 awaiter_type operator co_await() const noexcept
109 {
110 return awaiter_type{const_cast<notifier&>(*this)};
111 }
112
122 [[nodiscard]] callback_token
123 subscribe(forward_of<callback_proto> auto&& callback, callback_flags flags = callback_flags::synchronous) noexcept
124 {
125 auto token = std::make_shared<function_type>(hi_forward(callback));
126 _callbacks.emplace_back(token, flags);
127 return token;
128 }
129
135 void operator()(Args const&...args) const noexcept
136 {
137 for (auto& callback : _callbacks) {
138 if (is_synchronous(callback.flags)) {
139 if (auto func = callback.lock()) {
140 (*func)(args...);
141 }
142
143 } else if (is_local(callback.flags)) {
144 loop::local().post_function([=] {
145 if (auto func = callback.lock()) {
146 (*func)(args...);
147 }
148 });
149
150 } else if (is_main(callback.flags)) {
151 loop::main().post_function([=] {
152 if (auto func = callback.lock()) {
153 (*func)(args...);
154 }
155 });
156
157 } else if (is_timer(callback.flags)) {
158 loop::timer().post_function([=] {
159 if (auto func = callback.lock()) {
160 (*func)(args...);
161 }
162 });
163
164 } else {
165 hi_no_default();
166 }
167
168 // If the callback should only be triggered once, like inside an awaitable.
169 // Then reset the weak_ptr in _callbacks so that it will be cleaned up.
170 // In the lambda above the weak_ptr is copied first so that it callback will get executed
171 // as long as the shared_ptr's use count does not go to zero.
172 if (is_once(callback.flags)) {
173 callback.reset();
174 }
175 }
176 clean_up();
177 }
178
179private:
180 struct callback_type {
181 weak_callback_token token;
182 callback_flags flags;
183
184 [[nodiscard]] bool expired() const noexcept
185 {
186 return token.expired();
187 }
188
189 void reset() noexcept
190 {
191 token.reset();
192 }
193
194 [[nodiscard]] callback_token lock() const noexcept
195 {
196 return token.lock();
197 }
198 };
199
202 mutable std::vector<callback_type> _callbacks;
203
204 void clean_up() const noexcept
205 {
206 // Cleanup all callbacks that have expired, or when they may only be triggered once.
207 std::erase_if(_callbacks, [](hilet& item) {
208 return item.expired();
209 });
210 }
211
212#ifndef NDEBUG
215 mutable bool _notifying = false;
216#endif
217};
218
219} // namespace hi::inline v1
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:24
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:123
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:135
True if T is a forwarded type of Forward.
Definition concepts.hpp:130
T lock(T... args)