Switch system_clock to high_resolution_clock, work around win32 sleep_for/sleep_until bug when clocks go backwards

This commit is contained in:
Mr-Wiseguy 2024-04-05 21:06:04 -04:00
parent 65ced0c594
commit bd537728e3
6 changed files with 62 additions and 25 deletions

View File

@ -3,10 +3,10 @@
static ultramodern::input_callbacks_t input_callbacks; static ultramodern::input_callbacks_t input_callbacks;
std::chrono::system_clock::time_point input_poll_time; std::chrono::high_resolution_clock::time_point input_poll_time;
void update_poll_time() { void update_poll_time() {
input_poll_time = std::chrono::system_clock::now(); input_poll_time = std::chrono::high_resolution_clock::now();
} }
extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) { extern "C" void recomp_set_current_frame_poll_id(uint8_t* rdram, recomp_context* ctx) {
@ -18,7 +18,7 @@ extern "C" void recomp_measure_latency(uint8_t* rdram, recomp_context* ctx) {
} }
void ultramodern::measure_input_latency() { void ultramodern::measure_input_latency() {
// printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - input_poll_time)); // printf("Delta: %ld micros\n", std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - input_poll_time));
} }
void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) { void set_input_callbacks(const ultramodern::input_callbacks_t& callbacks) {

View File

@ -429,8 +429,7 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a
}, window_handle, rdram_buffer.get()}; }, window_handle, rdram_buffer.get()};
while (!exited) { while (!exited) {
using namespace std::chrono_literals; ultramodern::sleep_milliseconds(1);
std::this_thread::sleep_for(1ms);
if (gfx_callbacks.update_gfx != nullptr) { if (gfx_callbacks.update_gfx != nullptr) {
gfx_callbacks.update_gfx(gfx_data); gfx_callbacks.update_gfx(gfx_data);
} }

View File

@ -322,10 +322,7 @@ public:
throw std::runtime_error("Failed to make RmlUi data model for the graphics config menu"); throw std::runtime_error("Failed to make RmlUi data model for the graphics config menu");
} }
{ ultramodern::sleep_milliseconds(50);
using namespace std::chrono_literals;
std::this_thread::sleep_for(50ms);
}
new_options = ultramodern::get_graphics_config(); new_options = ultramodern::get_graphics_config();
bind_config_list_events(constructor); bind_config_list_events(constructor);

View File

@ -115,15 +115,20 @@ void vi_thread_func() {
while (!exited) { while (!exited) {
// Determine the next VI time (more accurate than adding 16ms each VI interrupt) // Determine the next VI time (more accurate than adding 16ms each VI interrupt)
auto next = ultramodern::get_start() + (total_vis * 1000000us) / (60 * ultramodern::get_speed_multiplier()); auto next = ultramodern::get_start() + (total_vis * 1000000us) / (60 * ultramodern::get_speed_multiplier());
//if (next > std::chrono::system_clock::now()) { //if (next > std::chrono::high_resolution_clock::now()) {
// printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n", // printf("Sleeping for %" PRIu64 " us to get from %" PRIu64 " us to %" PRIu64 " us \n",
// (next - std::chrono::system_clock::now()) / 1us, // (next - std::chrono::high_resolution_clock::now()) / 1us,
// (std::chrono::system_clock::now() - events_context.start) / 1us, // (std::chrono::high_resolution_clock::now() - events_context.start) / 1us,
// (next - events_context.start) / 1us); // (next - events_context.start) / 1us);
//} else { //} else {
// printf("No need to sleep\n"); // printf("No need to sleep\n");
//} //}
std::this_thread::sleep_until(next); // Detect if there's more than a second to wait and wait a fixed amount instead for the next VI if so, as that usually means the system clock went back in time.
if (std::chrono::floor<std::chrono::seconds>(next - std::chrono::high_resolution_clock::now()) > 1s) {
// printf("Skipping the next VI wait\n");
next = std::chrono::high_resolution_clock::now();
}
ultramodern::sleep_until(next);
// Calculate how many VIs have passed // Calculate how many VIs have passed
uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1; uint64_t new_total_vis = (ultramodern::time_since_start() * (60 * ultramodern::get_speed_multiplier()) / 1000ms) + 1;
if (new_total_vis > total_vis + 1) { if (new_total_vis > total_vis + 1) {
@ -325,9 +330,9 @@ void gfx_thread_func(uint8_t* rdram, std::atomic_flag* thread_ready, ultramodern
sp_complete(); sp_complete();
ultramodern::measure_input_latency(); ultramodern::measure_input_latency();
auto rt64_start = std::chrono::system_clock::now(); auto rt64_start = std::chrono::high_resolution_clock::now();
RT64SendDL(rdram, &task_action->task); RT64SendDL(rdram, &task_action->task);
auto rt64_end = std::chrono::system_clock::now(); auto rt64_end = std::chrono::high_resolution_clock::now();
dp_complete(); dp_complete();
// printf("RT64 ProcessDList time: %d us\n", static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(rt64_end - rt64_start).count())); // printf("RT64 ProcessDList time: %d us\n", static_cast<u32>(std::chrono::duration_cast<std::chrono::microseconds>(rt64_end - rt64_start).count()));
} }

View File

@ -6,8 +6,13 @@
#include "ultra64.h" #include "ultra64.h"
#include "ultramodern.hpp" #include "ultramodern.hpp"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include "Windows.h"
#endif
// Start time for the program // Start time for the program
static std::chrono::system_clock::time_point start_time = std::chrono::system_clock::now(); static std::chrono::high_resolution_clock::time_point start_time = std::chrono::high_resolution_clock::now();
// Game speed multiplier (1 means no speedup) // Game speed multiplier (1 means no speedup)
constexpr uint32_t speed_multiplier = 1; constexpr uint32_t speed_multiplier = 1;
// N64 CPU counter ticks per millisecond // N64 CPU counter ticks per millisecond
@ -37,7 +42,7 @@ struct {
moodycamel::BlockingConcurrentQueue<Action> action_queue{}; moodycamel::BlockingConcurrentQueue<Action> action_queue{};
} timer_context; } timer_context;
uint64_t duration_to_ticks(std::chrono::system_clock::duration duration) { uint64_t duration_to_ticks(std::chrono::high_resolution_clock::duration duration) {
uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count(); uint64_t delta_micros = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
// More accurate than using a floating point timer, will only overflow after running for 12.47 years // More accurate than using a floating point timer, will only overflow after running for 12.47 years
// Units: (micros * (counts/millis)) / (micros/millis) = counts // Units: (micros * (counts/millis)) / (micros/millis) = counts
@ -51,12 +56,12 @@ std::chrono::microseconds ticks_to_duration(uint64_t ticks) {
return ticks * 1000us / counter_per_ms; return ticks * 1000us / counter_per_ms;
} }
std::chrono::system_clock::time_point ticks_to_timepoint(uint64_t ticks) { std::chrono::high_resolution_clock::time_point ticks_to_timepoint(uint64_t ticks) {
return start_time + ticks_to_duration(ticks); return start_time + ticks_to_duration(ticks);
} }
uint64_t time_now() { uint64_t time_now() {
return duration_to_ticks(std::chrono::system_clock::now() - start_time); return duration_to_ticks(std::chrono::high_resolution_clock::now() - start_time);
} }
void timer_thread(RDRAM_ARG1) { void timer_thread(RDRAM_ARG1) {
@ -111,7 +116,7 @@ void timer_thread(RDRAM_ARG1) {
active_timers.erase(cur_timer_); active_timers.erase(cur_timer_);
// Determine how long to wait to reach the timer's timestamp // Determine how long to wait to reach the timer's timestamp
auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::system_clock::now(); auto wait_duration = ticks_to_timepoint(cur_timer->timestamp) - std::chrono::high_resolution_clock::now();
// Wait for either the duration to complete or a new action to come through // Wait for either the duration to complete or a new action to come through
if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) { if (wait_duration.count() >= 0 && timer_context.action_queue.wait_dequeue_timed(cur_action, wait_duration)) {
@ -142,12 +147,12 @@ uint32_t ultramodern::get_speed_multiplier() {
return speed_multiplier; return speed_multiplier;
} }
std::chrono::system_clock::time_point ultramodern::get_start() { std::chrono::high_resolution_clock::time_point ultramodern::get_start() {
return start_time; return start_time;
} }
std::chrono::system_clock::duration ultramodern::time_since_start() { std::chrono::high_resolution_clock::duration ultramodern::time_since_start() {
return std::chrono::system_clock::now() - start_time; return std::chrono::high_resolution_clock::now() - start_time;
} }
extern "C" u32 osGetCount() { extern "C" u32 osGetCount() {
@ -188,3 +193,32 @@ extern "C" int osStopTimer(RDRAM_ARG PTR(OSTimer) t_) {
// TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was // TODO don't blindly return 0 here; requires some response from the timer thread to know what the returned value was
return 0; return 0;
} }
#ifdef _WIN32
// The implementations of std::chrono::sleep_until and sleep_for were affected by changing the system clock backwards in older versions
// of Microsoft's STL. This was fixed as of Visual Studio 2022 17.9, but to be safe ultramodern uses Win32 Sleep directly.
void ultramodern::sleep_milliseconds(uint32_t millis) {
Sleep(millis);
}
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
auto time_now = std::chrono::high_resolution_clock::now();
if (time_point > time_now) {
long long delta_ms = std::chrono::ceil<std::chrono::milliseconds>(time_point - time_now).count();
// printf("Sleeping %lld %d ms\n", delta_ms, (uint32_t)delta_ms);
Sleep(delta_ms);
}
}
#else
void ultramodern::sleep_milliseconds(uint32_t millis) {
std::this_thread::sleep_for(std::chrono::milliseconds{millis});
}
void ultramodern::sleep_until(const std::chrono::high_resolution_clock::time_point& time_point) {
std::this_thread::sleep_until(time_point);
}
#endif

View File

@ -92,11 +92,13 @@ bool is_game_thread();
void submit_rsp_task(RDRAM_ARG PTR(OSTask) task); void submit_rsp_task(RDRAM_ARG PTR(OSTask) task);
void send_si_message(); void send_si_message();
uint32_t get_speed_multiplier(); uint32_t get_speed_multiplier();
std::chrono::system_clock::time_point get_start(); std::chrono::high_resolution_clock::time_point get_start();
std::chrono::system_clock::duration time_since_start(); std::chrono::high_resolution_clock::duration time_since_start();
void get_window_size(uint32_t& width, uint32_t& height); void get_window_size(uint32_t& width, uint32_t& height);
uint32_t get_target_framerate(uint32_t original); uint32_t get_target_framerate(uint32_t original);
void measure_input_latency(); void measure_input_latency();
void sleep_milliseconds(uint32_t millis);
void sleep_until(const std::chrono::high_resolution_clock::time_point& time_point);
// Audio // Audio
void init_audio(); void init_audio();