36 loop& operator=(
loop const&) =
delete;
37 loop& operator=(
loop&&)
noexcept =
delete;
42 while (_handles.size() > _socket_handle_idx) {
43 if (not WSACloseEvent(_handles.back())) {
44 hi_log_error(
"Could not clock socket event handle for socket {}. {}", _sockets.back(), get_last_error_message());
51 if (_vsync_thread.joinable()) {
52 _vsync_thread.request_stop();
56 if (not CloseHandle(_handles[_function_handle_idx])) {
57 hi_log_error(
"Could not close async-event handle. {}", get_last_error_message());
59 if (not CloseHandle(_handles[_vsync_handle_idx])) {
60 hi_log_error(
"Could not close vsync-event handle. {}", get_last_error_message());
62 if (not CloseHandle(_use_vsync_handle)) {
63 hi_log_error(
"Could not close use-vsync handle. {}", get_last_error_message());
67 loop() noexcept : _thread_id(current_thread_id())
70 if (
auto handle = CreateEventW(NULL, TRUE, TRUE, NULL)) {
71 _use_vsync_handle = handle;
73 hi_log_fatal(
"Could not create an use-vsync handle. {}", get_last_error_message());
77 if (
auto handle = CreateEventW(NULL, FALSE, FALSE, NULL)) {
78 _handles.push_back(handle);
79 _sockets.push_back(-1);
80 _socket_functions.emplace_back();
82 hi_log_fatal(
"Could not create an vsync-event handle. {}", get_last_error_message());
86 if (
auto handle = CreateEventW(NULL, FALSE, FALSE, NULL)) {
87 _handles.push_back(handle);
88 _sockets.push_back(-1);
89 _socket_functions.emplace_back();
91 hi_log_fatal(
"Could not create an async-event handle. {}", get_last_error_message());
97 [[nodiscard]]
static loop&
local()
noexcept;
104 [[nodiscard]] hi_no_inline
static loop&
main() noexcept
106 if (
auto ptr = _main.load(std::memory_order::acquire)) {
110 hi_axiom(_timer.load(std::memory_order::relaxed) ==
nullptr,
"loop::main() must be called before loop::timer()");
114 set_thread_name(
"main");
117 _main.store(ptr, std::memory_order::release);
125 [[nodiscard]] hi_no_inline
static loop&
timer() noexcept
129 [[maybe_unused]]
auto const &tmp = loop::main();
131 return *start_subsystem_or_terminate(_timer,
nullptr, timer_init, timer_deinit);
142 hi_axiom(on_thread());
149 _selected_monitor_id.store(
id, std::memory_order::relaxed);
161 template<forward_of<
void()> Func>
164 _function_fifo.add_function(std::forward<Func>(func));
172 template<forward_of<
void()> Func>
175 _function_fifo.add_function(std::forward<Func>(func));
186 template<
typename Func>
189 auto future = _function_fifo.add_async_function(std::forward<Func>(func));
199 template<forward_of<
void()> Func>
200 [[nodiscard]] callback<void()>
delay_function(utc_nanoseconds time_point, Func&& func)
noexcept
202 auto [callback, first_to_call] = _function_timer.delay_function(time_point, std::forward<Func>(func));
216 template<forward_of<
void()> Func>
217 [[nodiscard]] callback<void()>
220 auto [callback, first_to_call] = _function_timer.repeat_function(period, time_point, std::forward<Func>(func));
233 template<forward_of<
void()> Func>
236 auto [callback, first_to_call] = _function_timer.repeat_function(period, std::forward<Func>(func));
244 void subscribe_render(weak_callback<
void(utc_nanoseconds)> callback)
noexcept
252 template<forward_of<
void(utc_nanoseconds)> Func>
255 hi_axiom(on_thread());
257 auto cb = callback<void(utc_nanoseconds)>{std::forward<Func>(func)};
259 _render_functions.push_back(cb);
262 if (not _vsync_thread.joinable()) {
263 _vsync_thread = std::jthread{[
this](std::stop_token token) {
264 return vsync_thread_proc(
std::move(token));
285 hi_axiom(on_thread());
286 hi_not_implemented();
295 hi_axiom(on_thread());
296 hi_not_implemented();
306 int resume(std::stop_token stop_token = {})
noexcept
308 auto const is_main =
this == _main.load(std::memory_order::relaxed);
311 auto const thread_handle = GetCurrentThread();
313 int original_thread_priority = GetThreadPriority(thread_handle);
314 if (original_thread_priority == THREAD_PRIORITY_ERROR_RETURN) {
315 original_thread_priority = THREAD_PRIORITY_NORMAL;
316 hi_log_error(
"GetThreadPriority() for loop failed {}", get_last_error_message());
319 if (is_main and original_thread_priority < THREAD_PRIORITY_ABOVE_NORMAL) {
320 if (not SetThreadPriority(thread_handle, THREAD_PRIORITY_ABOVE_NORMAL)) {
321 hi_log_error(
"SetThreadPriority() for loop failed {}", get_last_error_message());
326 while (not _exit_code) {
329 if (stop_token.stop_possible()) {
330 if (stop_token.stop_requested()) {
335 if (_render_functions.empty() and _function_fifo.empty() and _function_timer.empty() and
336 _handles.size() <= _socket_handle_idx) {
344 if (is_main and original_thread_priority < THREAD_PRIORITY_ABOVE_NORMAL) {
345 if (not SetThreadPriority(thread_handle, original_thread_priority)) {
368 using namespace std::chrono_literals;
370 hi_axiom(on_thread());
372 auto const is_main =
this == _main.load(std::memory_order::relaxed);
374 auto current_time = std::chrono::utc_clock::now();
375 auto timeout = std::chrono::duration_cast<std::chrono::milliseconds>(_function_timer.current_deadline() - current_time);
377 timeout = std::clamp(timeout, 0ms, 100ms);
378 auto const timeout_ms = narrow_cast<DWORD>(timeout / 1ms);
382 auto const message_mask = is_main and block ? QS_ALLINPUT : 0;
385 MsgWaitForMultipleObjects(narrow_cast<DWORD>(_handles.size()), _handles.data(), FALSE, timeout_ms, message_mask);
387 if (wait_r == WAIT_FAILED) {
388 hi_log_fatal(
"Failed on MsgWaitForMultipleObjects(), {}", get_last_error_message());
390 }
else if (wait_r == WAIT_TIMEOUT) {
394 }
else if (wait_r == WAIT_OBJECT_0 + _vsync_handle_idx) {
399 }
else if (wait_r == WAIT_OBJECT_0 + _function_handle_idx) {
403 }
else if (wait_r >= WAIT_OBJECT_0 + _socket_handle_idx and wait_r < WAIT_OBJECT_0 + _handles.size()) {
404 auto const index = wait_r - WAIT_OBJECT_0;
406 WSANETWORKEVENTS events;
407 if (WSAEnumNetworkEvents(_sockets[index], _handles[index], &events) != 0) {
408 switch (WSAGetLastError()) {
409 case WSANOTINITIALISED:
410 hi_log_fatal(
"WSAStartup was not called.");
412 hi_log_fatal(
"The network subsystem has failed.");
414 hi_log_fatal(
"One of the specified parameters was invalid.");
417 "A blocking Windows Sockets 1.1 call is in progress, or the service provider is still processing a "
422 hi_log_fatal(
"The lpNetworkEvents parameter is not a valid part of the user address space.");
425 hi_log_error(
"Error during WSAEnumNetworkEvents on socket {}: {}", _sockets[index], get_last_error_message());
426 _handles.erase(_handles.begin() + index);
427 _sockets.erase(_sockets.begin() + index);
428 _socket_functions.erase(_socket_functions.begin() + index);
436 _socket_functions[index](_sockets[index], socket_events_from_win32(events));
439 }
else if (wait_r == WAIT_OBJECT_0 + _handles.size()) {
442 }
else if (wait_r >= WAIT_ABANDONED_0 and wait_r < WAIT_ABANDONED_0 + _handles.size()) {
443 auto const index = wait_r - WAIT_ABANDONED_0;
444 if (index == _vsync_handle_idx) {
445 hi_log_fatal(
"The vsync-handle has been abandoned.");
447 }
else if (index == _function_handle_idx) {
448 hi_log_fatal(
"The async-handle has been abandoned.");
452 hi_log_error(
"The socket-handle for socket {} has been abandoned.", _sockets[index]);
453 _handles.erase(_handles.begin() + index);
454 _sockets.erase(_sockets.begin() + index);
455 _socket_functions.erase(_socket_functions.begin() + index);
476 return current_thread_id() == _thread_id;
488 inline static std::jthread _timer_thread;
493 std::optional<int> _exit_code = {};
494 double _maximum_frame_rate = 30.0;
496 thread_id _thread_id;
497 std::vector<weak_callback<void(utc_nanoseconds)>> _render_functions;
505 constexpr static size_t _vsync_handle_idx = 0;
506 constexpr static size_t _function_handle_idx = 1;
507 constexpr static size_t _socket_handle_idx = 2;
516 HANDLE _use_vsync_handle;
524 bool _vsync_time_from_sleep =
true;
536 uint64_t _sub_frame_count = 0;
542 uint64_t _frame_count = 0;
568 std::jthread _vsync_thread;
572 HANDLE _vsync_thread_handle;
576 int _vsync_thread_priority = THREAD_PRIORITY_NORMAL;
589 IDXGIOutput *_vsync_monitor_output =
nullptr;
591 static loop *timer_init() noexcept
593 hi_assert(not _timer_thread.joinable());
595 _timer_thread = std::jthread{[](std::stop_token stop_token) {
599 loop::local().resume(stop_token);
603 if (
auto ptr = _timer.
load(std::memory_order::relaxed)) {
610 static void timer_deinit() noexcept
612 if (
auto const *
const ptr = _timer.
exchange(
nullptr, std::memory_order::acquire)) {
613 hi_assert(_timer_thread.joinable());
614 _timer_thread.request_stop();
615 _timer_thread.join();
621 void notify_has_send() noexcept
623 if (not SetEvent(_handles[_function_handle_idx])) {
632 void handle_vsync() noexcept
638 if (not _vsync_thread.joinable()) {
640 _vsync_time.
store(std::chrono::utc_clock::now());
645 for (
auto& render_function : _render_functions) {
646 if (
auto rf = render_function.lock()) {
651 std::erase_if(_render_functions, [](
auto& render_function) {
652 return render_function.expired();
655 if (_render_functions.empty()) {
657 if (_vsync_thread.joinable()) {
658 _vsync_thread.request_stop();
667 void handle_functions() noexcept
672 void handle_timers() noexcept
674 _function_timer.
run_all(std::chrono::utc_clock::now());
681 void handle_gui_events() noexcept
684 auto const t1 = trace<
"loop:gui-events">();
685 while (PeekMessageW(&msg,
nullptr, 0, 0, PM_REMOVE | PM_NOYIELD)) {
686 auto const t2 = trace<
"loop:gui-event">();
688 if (msg.message == WM_QUIT) {
689 _exit_code = narrow_cast<int>(msg.wParam);
693 TranslateMessage(&msg);
694 DispatchMessageW(&msg);
702 void vsync_thread_update_dxgi_output() noexcept
704 if (not
compare_store(_vsync_monitor_id, _selected_monitor_id.
load(std::memory_order::relaxed))) {
708 if (_vsync_monitor_output) {
709 _vsync_monitor_output->Release();
710 _vsync_monitor_output =
nullptr;
713 IDXGIFactory *factory =
nullptr;
714 if (FAILED(CreateDXGIFactory(__uuidof(IDXGIFactory), (
void **)&factory))) {
715 hi_log_error_once(
"vsync:error:CreateDXGIFactory",
"Could not IDXGIFactory. {}",
get_last_error_message());
718 hi_assert_not_null(factory);
719 auto d1 = defer([&] {
723 IDXGIAdapter *adapter =
nullptr;
724 if (FAILED(factory->EnumAdapters(0, &adapter))) {
725 hi_log_error_once(
"vsync:error:EnumAdapters",
"Could not get IDXGIAdapter. {}",
get_last_error_message());
728 hi_assert_not_null(adapter);
729 auto d2 = defer([&] {
733 if (FAILED(adapter->EnumOutputs(0, &_vsync_monitor_output))) {
738 DXGI_OUTPUT_DESC description;
739 if (FAILED(_vsync_monitor_output->GetDesc(&description))) {
740 hi_log_error_once(
"vsync:error:GetDesc",
"Could not get IDXGIOutput description. {}",
get_last_error_message());
741 _vsync_monitor_output->Release();
742 _vsync_monitor_output =
nullptr;
746 if (description.Monitor != std::bit_cast<HMONITOR>(_vsync_monitor_id)) {
747 hi_log_error_once(
"vsync:error:not-primary-monitor",
"DXGI primary monitor does not match desktop primary monitor");
748 _vsync_monitor_output->Release();
749 _vsync_monitor_output =
nullptr;
768 auto const ts = time_stamp_count(time_stamp_count::inplace_with_cpu_id{});
769 auto const new_time = time_stamp_utc::make(ts);
771 auto const was_sleeping = std::exchange(_vsync_time_from_sleep, on_sleep);
772 auto const old_time = _vsync_time.
exchange(new_time, std::memory_order::acquire);
778 void vsync_thread_wait_for_vblank() noexcept
780 using namespace std::chrono_literals;
782 vsync_thread_update_dxgi_output();
784 if (_vsync_monitor_output and FAILED(_vsync_monitor_output->WaitForVBlank())) {
788 if (vsync_thread_update_time(
false) < 1ms) {
789 hi_log_info_once(
"vsync:monitor-off",
"WaitForVBlank() did not block; is the monitor turned off?");
793 vsync_thread_update_time(
true);
795 ++global_counter<
"vsync:vertical-blank">;
806 [[nodiscard]]
bool vsync_thread_pull_down() noexcept
808 _sub_frame_count += _pull_down.
load(std::memory_order::relaxed);
817 void vsync_thread_update_priority(
int new_priority)
noexcept
819 if (std::exchange(_vsync_thread_priority, new_priority) != new_priority) {
820 if (not SetThreadPriority(_vsync_thread_handle, new_priority)) {
821 hi_log_error_once(
"vsync:error:SetThreadPriority",
"Could not set the vsync thread priority to {}", new_priority);
826 void vsync_thread_proc(std::stop_token stop_token)
noexcept
828 _vsync_thread_handle = GetCurrentThread();
831 while (not stop_token.stop_requested()) {
832 switch (WaitForSingleObject(_use_vsync_handle, 30)) {
835 vsync_thread_update_time(
true);
837 vsync_thread_update_priority(THREAD_PRIORITY_NORMAL);
839 ++global_counter<
"vsync:low-priority">;
840 ++global_counter<
"vsync:frame">;
841 SetEvent(_handles[_vsync_handle_idx]);
846 vsync_thread_update_priority(THREAD_PRIORITY_TIME_CRITICAL);
848 vsync_thread_wait_for_vblank();
850 if (vsync_thread_pull_down()) {
851 ++global_counter<
"vsync:frame">;
852 SetEvent(_handles[_vsync_handle_idx]);
858 hi_log_error_once(
"vsync:error:WAIT_ABANDONED",
"use_vsync_handle has been abandoned.");
859 ResetEvent(_use_vsync_handle);
864 ResetEvent(_use_vsync_handle);