os: implement win10 high-resolution timer as replacement for Sleep() / sleep_for() (#682)

## Link to GitHub Issue or related Pull Request, if one exists
Fixes #681 

## Description of change
`Sleep` and `sleep_for()` can be very inaccurate and varies depending on
what the OS gives us...

### `timeBeginPeriod(1)`
On boot, we are now calling `timeBeginPeriod(1)`, which affects the
whole process but makes `Sleep` more accurate. There is some risk here
if any game was relying on doing things like `Sleep(1)` and expecting it
to run for 15.6ms. Most games already call `timeBeginPeriod(1)` in the
game engine, though not the whole time, so I'm hoping that this is not
too impactful.

### Opt out of Win11 power throttling
Ensure that timer resolution change above is respected even when the
window is occluded / minimized by opting out of throttling via
`PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION`.

### Use Win10 high resolution timer instead of Sleep

On Win10 1803 and above, there is a new OS-level API for high resolution
timers; if this is available, use it
(`CREATE_WAITABLE_TIMER_HIGH_RESOLUTION`). Worth noting that WINE
doesn't support this currently.

If not, fall back to `Sleep`, which is significantly better than
`sleep_for()` in my experiments.

Callers of Sleep / sleep_for were replaced with this new timer. Most of
them anyway; calls to Sleep() with more than 100ms+ was left alone.

### Add an option as a chicken bit

To opt out I'm adding a new option called `Use Legacy Timers` which will
revert to behavior before this PR. The code paths that switched from
`sleep_for` to `Sleep` will remain in place though, not affected by the
option.

## Expected changes
In some I/O emulation modules, poll threads may run more frequently,
resulting in lower latency.

It also means that spice overall may use more CPU resources and power.
If you don't like this, you can always enable the option to opt out;
e.g., if you're on old arcade cab PC.

## Testing

DDR p4io - ok
drs touch hook - ok
IIDX camera hook - ok
CCJ trackball - ok
This commit is contained in:
bicarus
2026-05-08 02:25:20 -07:00
committed by GitHub
parent 1957917270
commit 71ba9b6b47
25 changed files with 320 additions and 34 deletions
+1
View File
@@ -636,6 +636,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
util/time.cpp
util/cpuutils.cpp
util/netutils.cpp
util/precise_timer.cpp
util/sysutils.cpp
util/lz77.cpp
util/tapeled.cpp
+5 -2
View File
@@ -1,14 +1,16 @@
#include "mdxf.h"
#include "mdxf_poll.h"
#include <mutex>
#include "avs/game.h"
#include "games/ddr/ddr.h"
#include "games/ddr/io.h"
#include "launcher/launcher.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include <mutex>
#define MDFX_DEBUG_VERBOSE 0
@@ -109,9 +111,10 @@ static void mdxf_thread_start() {
MDXF_THREAD = std::thread([] {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
timeutils::PreciseSleepTimer timer;
while (MDXF_THREAD_RUNNING.load(std::memory_order_acquire)) {
mdxf_poll(false);
std::this_thread::sleep_for(THREAD_PERIOD);
timer.sleep(THREAD_PERIOD);
}
});
}
+3 -1
View File
@@ -5,6 +5,7 @@
#include "games/sdvx/sdvx.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
using namespace acioemu;
@@ -46,11 +47,12 @@ ICCADevice::ICCADevice(bool flip_order, bool keypad_thread, uint8_t node_count)
// keypad thread for faster polling
if (keypad_thread) {
this->keypad_thread = new std::thread([this]() {
timeutils::PreciseSleepTimer timer;
while (this->cards) {
for (int unit = 0; unit < this->node_count; unit++) {
this->update_keypad(unit, false);
}
Sleep(7);
timer.sleep(7);
}
});
}
+4 -2
View File
@@ -7,6 +7,7 @@
#include "avs/game.h"
#include "external/rapidjson/document.h"
#include "misc/eamuse.h"
#include "util/precise_timer.h"
using namespace std::placeholders;
using namespace rapidjson;
@@ -58,6 +59,7 @@ namespace api::modules {
// get params
auto keypad = req.params[0].GetUint();
auto input = std::string(req.params[1].GetString());
timeutils::PreciseSleepTimer timer;
// process all chars
for (auto c : input) {
@@ -91,11 +93,11 @@ namespace api::modules {
// set
eamuse_set_keypad_overrides(keypad, state);
Sleep(sleep_time);
timer.sleep(sleep_time);
// unset
eamuse_set_keypad_overrides(keypad, 0);
Sleep(sleep_time);
timer.sleep(sleep_time);
}
}
+3 -1
View File
@@ -5,6 +5,7 @@
#include <utility>
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
@@ -16,6 +17,7 @@ namespace api {
controller->init_state(this->state);
this->thread = new std::thread([this] () {
log_warning("api::serial", "listening on {} (baud: {})", this->port, this->baud);
timeutils::PreciseSleepTimer timer;
// read buffer
uint8_t read_buffer[16*1024];
@@ -160,7 +162,7 @@ namespace api {
// slow down on reconnect
if (this->running) {
Sleep(retry_time);
timer.sleep(retry_time);
}
}
});
+3 -1
View File
@@ -6,6 +6,7 @@
#include "util/logging.h"
#include "rawinput/rawinput.h"
#include "games/io.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "io.h"
@@ -243,6 +244,7 @@ namespace games::ccj {
log_info("trackball", "thread start, use mouse: {}, toggle: {}", MOUSE_TRACKBALL, MOUSE_TRACKBALL_USE_TOGGLE);
tbThread = new std::thread([&] {
timeutils::PreciseSleepTimer timer;
while (tbThreadRunning) {
if (hWnd && wndProc) {
wndProc(hWnd, WM_INPUT, RIM_INPUT, (LPARAM)fakeHandle);
@@ -251,7 +253,7 @@ namespace games::ccj {
if (!tbThreadRunning)
break;
std::this_thread::sleep_for(10ms);
timer.sleep(10);
}
});
}
+3 -1
View File
@@ -3,6 +3,7 @@
#include "cfg/api.h"
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "../ddr.h"
@@ -459,7 +460,8 @@ int games::ddr::DDRP3IOHandle::device_io(
if (nOutBufferSize >= 4) {
// cool down
Sleep(1);
static thread_local timeutils::PreciseSleepTimer timer;
timer.sleep(1);
// get controls as single variable (4 bytes)
auto &controls = *(uint32_t*) lpOutBuffer;
+3 -1
View File
@@ -2,6 +2,7 @@
#include "rawinput/rawinput.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "../io.h"
@@ -255,7 +256,8 @@ namespace games::ddr {
case P4IO_IOCTL_GET_INPUTS: {
// Prevents this function from being called at its normal 2000-2500 kHz cadence and overloading the CPU, instead reduces it to 250 Hz
Sleep(4);
static thread_local timeutils::PreciseSleepTimer timer;
timer.sleep(4);
memset(lpOutBuffer, 0, 16);
auto controls = (uint32_t*) lpOutBuffer;
+3 -1
View File
@@ -7,6 +7,7 @@
#include "games/game.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/memutils.h"
#include "rgb_cam.h"
@@ -221,10 +222,11 @@ namespace games::drs {
void start_touch() {
std::thread t([] {
log_info("drs", "starting touch input thread");
timeutils::PreciseSleepTimer timer;
// main loop
while (TRUE) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
timer.sleep(1);
TOUCH_EVENTS.clear();
touch_get_events(TOUCH_EVENTS);
+3 -1
View File
@@ -2,6 +2,7 @@
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "iidx.h"
@@ -82,7 +83,8 @@ bool games::iidx::IIDXFMSerialHandle::FMSerialDevice::parse_msg(
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
static thread_local timeutils::PreciseSleepTimer timer;
timer.sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
+3 -1
View File
@@ -2,6 +2,7 @@
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "iidx.h"
@@ -82,7 +83,8 @@ bool games::iidx::BI2XSerialHandle::BI2XDevice::parse_msg(
}
// sleep - otherwise the IO thread will go too hard on the CPU
Sleep(1);
static thread_local timeutils::PreciseSleepTimer timer;
timer.sleep(1);
// generate message
auto msg = this->create_msg(msg_in, 0x2E);
+3 -1
View File
@@ -3,6 +3,7 @@
#if SPICE64 && !SPICE_XP
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "mf_wrappers.h"
@@ -399,6 +400,7 @@ namespace games::iidx {
void IIDXLocalCamera::CreateThread() {
// Create thread
m_drawThread = new std::thread([this]() {
timeutils::PreciseSleepTimer timer;
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
double accumulator = 0.0;
@@ -412,7 +414,7 @@ namespace games::iidx {
accumulator -= 1.0;
floorFrameTimeMicroSec += 1;
}
std::this_thread::sleep_for(std::chrono::microseconds(floorFrameTimeMicroSec));
timer.sleep(std::chrono::microseconds(floorFrameTimeMicroSec));
}
});
}
+3 -1
View File
@@ -15,6 +15,7 @@
#include "touch/touch.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#define POKE_NATIVE_TOUCH 0
@@ -200,6 +201,7 @@ namespace games::iidx::poke {
// create new thread
THREAD_RUNNING = true;
THREAD = new std::thread([] {
timeutils::PreciseSleepTimer timer;
// log
log_info("poke", "enabled");
@@ -319,7 +321,7 @@ namespace games::iidx::poke {
}
// slow down
Sleep(50);
timer.sleep(50);
}
return nullptr;
+3 -1
View File
@@ -9,6 +9,7 @@
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/precise_timer.h"
namespace games::nost::poke {
@@ -63,6 +64,7 @@ namespace games::nost::poke {
// create new thread
THREAD_RUNNING = true;
THREAD = new std::thread([] {
timeutils::PreciseSleepTimer timer;
const DWORD touch_id = (DWORD)(0xFFFFFFFE);
const int swipe_anim_total_frames = 6;
@@ -178,7 +180,7 @@ namespace games::nost::poke {
}
// slow down
Sleep(30);
timer.sleep(30);
}
return nullptr;
+5 -1
View File
@@ -4,6 +4,7 @@
#include "touchpanel.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "touch/touch.h"
using namespace std::chrono_literals;
@@ -44,6 +45,9 @@ bool games::onpara::TouchPanelHandle::open(LPCWSTR lpFileName) {
}
int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesToRead) {
static thread_local timeutils::PreciseSleepTimer timer;
DWORD i;
auto buffer = reinterpret_cast<uint8_t *>(lpBuffer);
@@ -67,7 +71,7 @@ int games::onpara::TouchPanelHandle::read(LPVOID lpBuffer, DWORD nNumberOfBytesT
enqueue_packet(report);
// prevent cpu bullying
std::this_thread::sleep_for(1ms);
timer.sleep(1);
}
// copy from output queue
@@ -3,6 +3,7 @@
#include "hooks/audio/audio.h"
#include "hooks/audio/backends/wasapi/audio_client.h"
#include "hooks/audio/backends/wasapi/defs.h"
#include "util/precise_timer.h"
static REFERENCE_TIME WASAPI_TARGET_REFTIME = TARGET_REFTIME;
@@ -185,6 +186,7 @@ HRESULT WaveOutBackend::on_get_buffer(uint32_t num_frames_requested, BYTE **ppDa
}
HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwFlags) {
bool written = false;
timeutils::PreciseSleepTimer timer;
// reset the dispatcher event
ResetEvent(this->dispatcher_event);
@@ -209,7 +211,7 @@ HRESULT WaveOutBackend::on_release_buffer(uint32_t num_frames_written, DWORD dwF
// avoid pegging the CPU
if (!written) {
Sleep(1);
timer.sleep(1);
}
}
+8
View File
@@ -108,6 +108,7 @@
#include "util/libutils.h"
#include "util/logging.h"
#include "util/peb.h"
#include "util/precise_timer.h"
#include "util/socd_cleaner.h"
#include "util/sysutils.h"
#include "util/tapeled.h"
@@ -1273,6 +1274,10 @@ int main_implementation(int argc, char *argv[]) {
games::loveplus::CAMERA_ENABLE = true;
}
if (options[launcher::Options::DisableHighResTimer].value_bool()) {
timeutils::TIMER_HACKS_DISABLE = true;
}
// API debugging
if (api_debug && !cfg::CONFIGURATOR_STANDALONE) {
API_CONTROLLER = std::make_unique<api::Controller>(api_port, api_pass, api_pretty);
@@ -2227,6 +2232,9 @@ int main_implementation(int argc, char *argv[]) {
hooks::lang::early_init();
}
// use high resolution timer for this process
timeutils::set_timer_resolution();
// load DLLs
avs::core::load_dll();
avs::ea3::load_dll();
+11 -1
View File
@@ -2769,7 +2769,17 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
.type = OptionType::Bool,
.game_name = "Otoca D'or",
.category = "Game Options (Peripherals)",
}
},
{
// DisableHighResTimer
.title = "Use Legacy Timers",
.name = "notimerhacks",
.desc = "Disables high resolution timers, reverting to legacy behavior. "
"Recommended that you leave this OFF for optimal input latency, "
"unless you're on a resource-constrained system (e.g., old cabinet PC)",
.type = OptionType::Bool,
.category = "Development"
},
};
const std::vector<std::string> &launcher::get_categories(Options::OptionsCategory category) {
+1
View File
@@ -279,6 +279,7 @@ namespace launcher {
LovePlusPrinterJPGQuality,
OptionConflictResolution,
OtocaCamHook,
DisableHighResTimer,
};
enum class OptionsCategory {
+9 -6
View File
@@ -9,6 +9,7 @@
#include "rawinput/rawinput.h"
#include "games/sdvx/sdvx.h"
#include "util/logging.h"
#include "util/precise_timer.h"
#include "util/time.h"
#include "util/utils.h"
#include "overlay/overlay.h"
@@ -286,6 +287,7 @@ void eamuse_coin_start_thread() {
COIN_INPUT_THREAD = new std::thread([]() {
auto overlay_buttons = games::get_buttons_overlay(eamuse_get_game());
static bool COIN_INPUT_KEY_STATE = false;
timeutils::PreciseSleepTimer timer;
while (COIN_INPUT_THREAD_ACTIVE) {
// check input key
@@ -305,7 +307,7 @@ void eamuse_coin_start_thread() {
}
// once every two frames
Sleep(1000 / 30);
timer.sleep(1000 / 30);
}
});
}
@@ -340,6 +342,7 @@ void eamuse_pin_macro_start_thread() {
size_t pin_index[2] = {PIN_MACRO_VALUES[0].length(), PIN_MACRO_VALUES[1].length()};
std::optional<uint8_t> active_unit = std::nullopt;
timeutils::PreciseSleepTimer timer;
while (PIN_MACRO_THREAD_ACTIVE) {
// wait for key press
@@ -359,7 +362,7 @@ void eamuse_pin_macro_start_thread() {
}
if (!active_unit.has_value()) {
Sleep(20);
timer.sleep(20);
continue;
}
}
@@ -375,22 +378,22 @@ void eamuse_pin_macro_start_thread() {
eamuse_set_keypad_overrides(unit, keypad_overrides[char_index]);
}
pin_index[unit]++;
Sleep(100);
timer.sleep(100);
// clear
eamuse_set_keypad_overrides(unit, 0);
Sleep(50);
timer.sleep(50);
// end of PIN
if (pin_index[unit] == PIN_MACRO_VALUES[unit].length()) {
active_unit = std::nullopt;
Sleep(120);
timer.sleep(120);
}
continue;
}
Sleep(200);
timer.sleep(200);
}
});
}
+3 -1
View File
@@ -9,6 +9,7 @@
#include "util/logging.h"
#include "external/robin_hood.h"
#include "util/precise_timer.h"
#include "util/time.h"
#include "util/utils.h"
@@ -155,8 +156,9 @@ void rawinput::RawInputManager::input_hwnd_create() {
});
// wait for window creation being done
timeutils::PreciseSleepTimer timer;
while (!this->input_hwnd) {
Sleep(1);
timer.sleep(1);
}
}
+9 -5
View File
@@ -7,6 +7,7 @@
#include "util/logging.h"
#include "misc/eamuse.h"
#include "util/precise_timer.h"
#include "util/utils.h"
#include "structuredmessage.h"
@@ -217,6 +218,7 @@ bool Reader::set_comm_state(DWORD BaudRate) {
}
bool Reader::wait_for_handshake() {
timeutils::PreciseSleepTimer timer;
// baud rates
DWORD baud_rates[] = { CBR_57600, CBR_38400, CBR_19200, CBR_9600 };
@@ -268,7 +270,7 @@ bool Reader::wait_for_handshake() {
}
// sleep
Sleep(50);
timer.sleep(50);
}
log_warning("reader", "{}: no handshake received for {} baud", this->port, baud_rates[i]);
@@ -405,6 +407,7 @@ void start_reader_thread(const std::string &port, int id) {
READER_THREAD_RUNNING = true;
READER_THREADS.push_back(new std::thread([port, id]() {
log_info("reader", "{}: starting reader thread", port);
timeutils::PreciseSleepTimer timer;
while (READER_THREAD_RUNNING) {
@@ -442,21 +445,22 @@ void start_reader_thread(const std::string &port, int id) {
}
if (did_read_card) {
Sleep(2500);
timer.sleep(2500);
}
Sleep(20);
timer.sleep(20);
}
}
// sleep between reader connection retries
if (READER_THREAD_RUNNING)
Sleep(5000);
timer.sleep(5000);
}
}));
// wait for thread to start
Sleep(10);
timeutils::PreciseSleepTimer timer;
timer.sleep(10);
}
void stop_reader_thread() {
+1 -1
View File
@@ -93,6 +93,6 @@ static void worker_thread_main(std::stop_token stop_token) {
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
Sleep(1);
}
}
+188
View File
@@ -0,0 +1,188 @@
#define _WIN32_WINNT 0x0602
#include "precise_timer.h"
#define WIN32_NO_STATUS
#include <windows.h>
#undef WIN32_NO_STATUS
#include <timeapi.h>
#if !SPICE_XP
#include <processthreadsapi.h>
#endif
#include <chrono>
#include <cmath>
#include <limits>
#include "util/logging.h"
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#define PERIOD 1
#define TOLERANCE 0.02
#ifndef PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION
#define PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION 0x4
#endif
namespace timeutils {
bool TIMER_HACKS_DISABLE = false;
static TimerHandle get_precise_sleep_timer();
static void destroy_precise_sleep_timer(TimerHandle timer);
static void precise_sleep(TimerHandle timer, double seconds);
void set_timer_resolution() {
if (TIMER_HACKS_DISABLE) {
return;
}
#if !SPICE_XP
// make a call to opt out of power throttling
// (requested timer resolution being ignored when window is occluded or minimized)
// SetProcessInformation is win8+
const auto kernel32 = GetModuleHandleA("kernel32.dll");
if (kernel32) {
const auto SetProcessInformation_addr =
reinterpret_cast<decltype(SetProcessInformation) *>(
GetProcAddress(kernel32, "SetProcessInformation"));
if (SetProcessInformation_addr) {
PROCESS_POWER_THROTTLING_STATE state {};
state.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
state.ControlMask = PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
state.StateMask = 0;
const auto ret = SetProcessInformation_addr(
GetCurrentProcess(),
ProcessPowerThrottling,
&state,
sizeof(state));
log_info(
"timeutils",
"SetProcessInformation called to disable timer resolution throttling, returned {}",
ret);
}
}
#endif
const auto ret = timeBeginPeriod(PERIOD);
log_info(
"timeutils",
"timeBeginPeriod({}) called, returned {}",
PERIOD,
ret);
// and then we never call timeEndPeriod();
// the game will also call this but the OS will honor the lowest value (1 above)
}
#if !SPICE_XP
// timerSleep from https://blog.bearcats.nl/perfect-sleep-function/
static void timerSleep(HANDLE timer, double seconds) {
using namespace std::chrono;
auto t = high_resolution_clock::now();
const auto target = t + nanoseconds(int64_t(seconds * 1e9));
const int64_t maxTicks = PERIOD * 9'500;
for (;;) {
const int64_t remaining = (target - t).count();
int64_t ticks = (remaining - TOLERANCE) / 100;
if (ticks <= 0) {
break;
}
if (ticks > maxTicks) {
ticks = maxTicks;
}
LARGE_INTEGER due;
due.QuadPart = -ticks;
SetWaitableTimerEx(timer, &due, 0, NULL, NULL, NULL, 0);
WaitForSingleObject(timer, INFINITE);
t = high_resolution_clock::now();
}
while (high_resolution_clock::now() < target) {
YieldProcessor();
}
}
#endif
static TimerHandle get_precise_sleep_timer() {
TimerHandle timer = nullptr;
if (TIMER_HACKS_DISABLE) {
return timer;
}
// CreateWaitableTimerExW is Vista+
// CREATE_WAITABLE_TIMER_HIGH_RESOLUTION win10 1803+
#if !SPICE_XP
timer = CreateWaitableTimerExW(
NULL,
NULL,
CREATE_WAITABLE_TIMER_HIGH_RESOLUTION,
TIMER_ALL_ACCESS);
#endif
return timer;
}
static void destroy_precise_sleep_timer(TimerHandle timer) {
if (timer) {
CloseHandle(timer);
}
}
static void precise_sleep(TimerHandle timer, double seconds) {
if (!std::isfinite(seconds) || seconds <= 0.0) {
return;
}
#if !SPICE_XP
if (timer) {
timerSleep(timer, seconds);
return;
}
#endif
// robustSleep is too CPU heavy so we will stick to Sleep() with
// timeBeginPeriod, which has an error at most 1ms
const auto milliseconds = std::ceil(seconds * 1000.0);
if (milliseconds >= std::numeric_limits<DWORD>::max()) {
Sleep(std::numeric_limits<DWORD>::max());
return;
}
Sleep(static_cast<DWORD>(milliseconds));
}
// RAII wrapper
PreciseSleepTimer::PreciseSleepTimer() : timer(get_precise_sleep_timer()) {}
PreciseSleepTimer::~PreciseSleepTimer() {
destroy_precise_sleep_timer(timer);
}
void PreciseSleepTimer::sleep(uint32_t ms) const {
sleep(std::chrono::milliseconds(ms));
}
void PreciseSleepTimer::sleep_seconds(double seconds) const {
precise_sleep(timer, seconds);
}
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <chrono>
#include <cstdint>
namespace timeutils {
extern bool TIMER_HACKS_DISABLE;
void set_timer_resolution();
using TimerHandle = void *;
class PreciseSleepTimer {
public:
PreciseSleepTimer();
~PreciseSleepTimer();
PreciseSleepTimer(const PreciseSleepTimer &) = delete;
PreciseSleepTimer &operator=(const PreciseSleepTimer &) = delete;
PreciseSleepTimer(PreciseSleepTimer &&) = delete;
PreciseSleepTimer &operator=(PreciseSleepTimer &&) = delete;
void sleep(uint32_t ms) const;
template <typename Rep, typename Period>
void sleep(std::chrono::duration<Rep, Period> duration) const {
sleep_seconds(std::chrono::duration<double>(duration).count());
}
private:
void sleep_seconds(double seconds) const;
TimerHandle timer;
};
}