mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 22:30:42 -07:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -279,6 +279,7 @@ namespace launcher {
|
||||
LovePlusPrinterJPGQuality,
|
||||
OptionConflictResolution,
|
||||
OtocaCamHook,
|
||||
DisableHighResTimer,
|
||||
};
|
||||
|
||||
enum class OptionsCategory {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user