HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
loop_intf.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 "function_timer.hpp"
8#include "socket_event.hpp"
9#include "../container/module.hpp"
10#include "../utility/utility.hpp"
11#include "../macros.hpp"
12#include <functional>
13#include <type_traits>
14#include <concepts>
15#include <vector>
16#include <memory>
17
18hi_export_module(hikogui.dispatch.loop : intf);
19
20namespace hi::inline v1 {
21
22class loop {
23public:
25 using render_callback_type = std::function<void(utc_nanoseconds)>;
27
28 class impl_type {
29 public:
30 bool is_main = false;
31
32 impl_type() : _thread_id(current_thread_id()) {}
33
34 virtual ~impl_type() {}
35 impl_type(impl_type const&) = delete;
36 impl_type(impl_type&&) = delete;
37 impl_type& operator=(impl_type const&) = delete;
38 impl_type& operator=(impl_type&&) = delete;
39
40 virtual void set_maximum_frame_rate(double frame_rate) noexcept = 0;
41 virtual void set_vsync_monitor_id(uintptr_t id) noexcept = 0;
42
43 void wfree_post_function(auto&& func) noexcept
44 {
45 return _function_fifo.add_function(hi_forward(func));
46 }
47
48 void post_function(auto&& func) noexcept
49 {
50 _function_fifo.add_function(hi_forward(func));
51 notify_has_send();
52 }
53
54 [[nodiscard]] auto async_function(auto&& func) noexcept
55 {
56 auto future = _function_fifo.add_async_function(hi_forward(func));
57 notify_has_send();
58 return future;
59 }
60
61 timer_callback_token delay_function(utc_nanoseconds time_point, auto&& func) noexcept
62 {
63 auto [token, first_to_call] = _function_timer.delay_function(time_point, hi_forward(func));
64 if (first_to_call) {
65 // Notify if the added function is the next function to call.
66 notify_has_send();
67 }
68 return token;
69 }
70
71 timer_callback_token repeat_function(std::chrono::nanoseconds period, utc_nanoseconds time_point, auto&& func) noexcept
72 {
73 auto [token, first_to_call] = _function_timer.repeat_function(period, time_point, hi_forward(func));
74 if (first_to_call) {
75 // Notify if the added function is the next function to call.
76 notify_has_send();
77 }
78 return token;
79 }
80
81 timer_callback_token repeat_function(std::chrono::nanoseconds period, auto&& func) noexcept
82 {
83 auto [token, first_to_call] = _function_timer.repeat_function(period, hi_forward(func));
84 if (first_to_call) {
85 // Notify if the added function is the next function to call.
86 notify_has_send();
87 }
88 return token;
89 }
90
91 virtual void subscribe_render(std::weak_ptr<render_callback_type> f) noexcept = 0;
92 virtual void add_socket(int fd, socket_event event_mask, std::function<void(int, socket_events const&)> f) = 0;
93 virtual void remove_socket(int fd) = 0;
94 virtual int resume(std::stop_token stop_token) noexcept = 0;
95 virtual void resume_once(bool block) noexcept = 0;
96
97 [[nodiscard]] bool on_thread() const noexcept
98 {
99 return current_thread_id() == _thread_id;
100 }
101
102 protected:
105 virtual void notify_has_send() noexcept = 0;
106
107 function_fifo<> _function_fifo;
108 function_timer<> _function_timer;
109
110 std::optional<int> _exit_code = {};
111 double _maximum_frame_rate = 30.0;
112 std::chrono::nanoseconds _minimum_frame_time = std::chrono::nanoseconds(33'333'333);
113 thread_id _thread_id;
115 };
116
120 loop();
121 loop(loop const&) = delete;
122 loop(loop&&) noexcept = default;
123 loop& operator=(loop const&) = delete;
124 loop& operator=(loop&&) noexcept = default;
125
128 [[nodiscard]] hi_no_inline static loop& local() noexcept
129 {
130 if (not _local) {
131 _local = std::make_unique<loop>();
132 }
133 return *_local;
134 }
135
141 [[nodiscard]] hi_no_inline static loop& main() noexcept
142 {
143 if (auto ptr = _main.load(std::memory_order::acquire)) {
144 return *ptr;
145 }
146
147 hi_axiom(_timer.load(std::memory_order::relaxed) == nullptr, "loop::main() must be called before loop::timer()");
148
149 // This is the first time loop::main() is called so we must be on the main-thread
150 // So name the thread "main" so we can find it during debugging.
151 set_thread_name("main");
152
153 auto ptr = std::addressof(local());
154 ptr->_pimpl->is_main = true;
155 _main.store(ptr, std::memory_order::release);
156 return *ptr;
157 }
158
163 [[nodiscard]] hi_no_inline static loop& timer() noexcept
164 {
165 // The first time timer() is called, make sure that the main-loop exists,
166 // or even create the main-loop on the current thread.
167 [[maybe_unused]] hilet &tmp = loop::main();
168
169 return *start_subsystem_or_terminate(_timer, nullptr, timer_init, timer_deinit);
170 }
171
178 void set_maximum_frame_rate(double frame_rate) noexcept
179 {
180 hi_assert_not_null(_pimpl);
181 return _pimpl->set_maximum_frame_rate(frame_rate);
182 }
183
186 void set_vsync_monitor_id(uintptr_t id) noexcept
187 {
188 hi_assert_not_null(_pimpl);
189 return _pimpl->set_vsync_monitor_id(id);
190 }
191
201 void wfree_post_function(auto&& func) noexcept
202 {
203 hi_assert_not_null(_pimpl);
204 return _pimpl->wfree_post_function(hi_forward(func));
205 }
206
212 void post_function(auto&& func) noexcept
213 {
214 hi_assert_not_null(_pimpl);
215 return _pimpl->post_function(hi_forward(func));
216 }
217
225 [[nodiscard]] auto async_function(auto&& func) noexcept
226 {
227 hi_assert_not_null(_pimpl);
228 return _pimpl->async_function(hi_forward(func));
229 }
230
236 [[nodiscard]] timer_callback_token delay_function(utc_nanoseconds time_point, auto&& func) noexcept
237 {
238 hi_assert_not_null(_pimpl);
239 return _pimpl->delay_function(time_point, hi_forward(func));
240 }
241
248 [[nodiscard]] timer_callback_token
249 repeat_function(std::chrono::nanoseconds period, utc_nanoseconds time_point, auto&& func) noexcept
250 {
251 hi_assert_not_null(_pimpl);
252 return _pimpl->repeat_function(period, time_point, hi_forward(func));
253 }
254
260 [[nodiscard]] timer_callback_token repeat_function(std::chrono::nanoseconds period, auto&& func) noexcept
261 {
262 hi_assert_not_null(_pimpl);
263 return _pimpl->repeat_function(period, hi_forward(func));
264 }
265
271 {
272 hi_assert_not_null(_pimpl);
273 return _pimpl->subscribe_render(std::move(f));
274 }
275
280 template<std::invocable<utc_nanoseconds> F>
282 {
283 hi_assert_not_null(_pimpl);
284 auto ptr = std::make_shared<render_callback_type>(std::forward<F>(f));
285 _pimpl->subscribe_render(ptr);
286 return ptr;
287 }
288
301 void add_socket(int fd, socket_event event_mask, std::function<void(int, socket_events const&)> f)
302 {
303 hi_assert_not_null(_pimpl);
304 return _pimpl->add_socket(fd, event_mask, std::move(f));
305 }
306
311 void remove_socket(int fd)
312 {
313 hi_assert_not_null(_pimpl);
314 return _pimpl->remove_socket(fd);
315 }
316
324 int resume(std::stop_token stop_token = {}) noexcept
325 {
326 hi_assert_not_null(_pimpl);
327 return _pimpl->resume(stop_token);
328 }
329
343 void resume_once(bool block = false) noexcept
344 {
345 hi_assert_not_null(_pimpl);
346 return _pimpl->resume_once(block);
347 }
348
353 [[nodiscard]] bool on_thread() const noexcept
354 {
355 hi_assert_not_null(_pimpl);
356 return _pimpl->on_thread();
357 }
358
359private:
360 static loop *timer_init() noexcept
361 {
362 hi_assert(not _timer_thread.joinable());
363
364 _timer_thread = std::jthread{[](std::stop_token stop_token) {
365 _timer.store(std::addressof(loop::local()), std::memory_order::release);
366
367 set_thread_name("timer");
368 loop::local().resume(stop_token);
369 }};
370
371 while (true) {
372 if (auto ptr = _timer.load(std::memory_order::relaxed)) {
373 return ptr;
374 }
376 }
377 }
378
379 static void timer_deinit() noexcept
380 {
381 if (auto const *const ptr = _timer.exchange(nullptr, std::memory_order::acquire)) {
382 hi_assert(_timer_thread.joinable());
383 _timer_thread.request_stop();
384 _timer_thread.join();
385 }
386 }
387
388 inline static thread_local std::unique_ptr<loop> _local;
389
392 inline static std::atomic<loop *> _main;
393
396 inline static std::atomic<loop *> _timer;
397
398 inline static std::jthread _timer_thread;
399
401};
402
403template<typename Result, typename... Args>
404template<typename F>
405void notifier<Result(Args...)>::loop_local_post_function(F&& f) const noexcept
406{
407 return loop::local().post_function(std::forward<F>(f));
408}
409
410template<typename Result, typename... Args>
411template<typename F>
412void notifier<Result(Args...)>::loop_main_post_function(F&& f) const noexcept
413{
414 return loop::main().post_function(std::forward<F>(f));
415}
416
417template<typename Result, typename... Args>
418template<typename F>
419void notifier<Result(Args...)>::loop_timer_post_function(F&& f) const noexcept
420{
421 return loop::timer().post_function(std::forward<F>(f));
422}
423
424} // namespace hi::inline v1
DOXYGEN BUG.
Definition algorithm.hpp:16
@ local
Call the function asynchronously from the current thread's loop.
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
A fifo (First-in, Firts-out) for asynchronous calls.
Definition function_fifo.hpp:24
A time that calls functions.
Definition function_timer.hpp:25
Definition loop_intf.hpp:22
void resume_once(bool block=false) noexcept
Resume for a single iteration.
Definition loop_intf.hpp:343
bool on_thread() const noexcept
Check if the current thread is the same as the loop's thread.
Definition loop_intf.hpp:353
static hi_no_inline loop & timer() noexcept
Get or create the timer event-loop.
Definition loop_intf.hpp:163
auto async_function(auto &&func) noexcept
Call a function from the loop.
Definition loop_intf.hpp:225
timer_callback_token delay_function(utc_nanoseconds time_point, auto &&func) noexcept
Call a function at a certain time.
Definition loop_intf.hpp:236
void set_vsync_monitor_id(uintptr_t id) noexcept
Set the monitor id for vertical sync.
Definition loop_intf.hpp:186
int resume(std::stop_token stop_token={}) noexcept
Resume the loop on the current thread.
Definition loop_intf.hpp:324
void set_maximum_frame_rate(double frame_rate) noexcept
Set maximum frame rate.
Definition loop_intf.hpp:178
void subscribe_render(std::weak_ptr< render_callback_type > f) noexcept
Subscribe a render function to be called on vsync.
Definition loop_intf.hpp:270
void wfree_post_function(auto &&func) noexcept
Wait-free post a function to be called from the loop.
Definition loop_intf.hpp:201
void post_function(auto &&func) noexcept
Post a function to be called from the loop.
Definition loop_intf.hpp:212
timer_callback_token repeat_function(std::chrono::nanoseconds period, auto &&func) noexcept
Call a function repeatedly.
Definition loop_intf.hpp:260
static hi_no_inline loop & main() noexcept
Get or create the main-loop.
Definition loop_intf.hpp:141
void remove_socket(int fd)
Remove the callback associated with a socket.
Definition loop_intf.hpp:311
render_callback_token subscribe_render(F &&f) noexcept
Subscribe a render function to be called on vsync.
Definition loop_intf.hpp:281
void add_socket(int fd, socket_event event_mask, std::function< void(int, socket_events const &)> f)
Add a callback that reacts on a socket.
Definition loop_intf.hpp:301
timer_callback_token repeat_function(std::chrono::nanoseconds period, utc_nanoseconds time_point, auto &&func) noexcept
Call a function repeatedly.
Definition loop_intf.hpp:249
Definition loop_intf.hpp:28
Definition socket_event_intf.hpp:74
T addressof(T... args)
T move(T... args)
T sleep_for(T... args)