Files
spice2x_spice2x.github.io/src/spice2x/sdk/sample/v0/cpp/v0_cpp.cpp
T
bicarus 71ba9b6b47 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
2026-05-08 02:25:20 -07:00

99 lines
2.7 KiB
C++

#include <format>
#include <chrono>
#include <thread>
#include <windows.h>
#include "sdk/include/spicesdk.h"
#include "sdk/include/spicesdk_io.h"
// this sample assumes that the game is DDR
// convenience wrapper for logging; feel free to add other levels
#define LOG_INFO(message) spice.log(SPICE_SDK_LOG_LEVEL_INFO, "sample_v0_cpp", message)
static SPICE_SDK_V0 spice = {};
static spice_sdk_destroy_callback_func destroy_callback;
static std::jthread worker_thread;
static void worker_thread_main(std::stop_token stop_token);
// main entry point into the DLL
// spice executable will call into this shortly after the game is partially
// initialized (but not quite fully booted just yet)
SPICE_SDK_ENTRY_POINT
spice_sdk_entry_point(
spice_sdk_init_func *init
)
{
SPICE_SDK_STATUS_CODE status;
// SPICE_SDK_V0 must be zeroed AND the size field must be set
spice.size = sizeof(spice);
// initialize the SDK - can be called any time, but we'll do it here
status = init(0, destroy_callback, &spice);
// always check for return value; failure could be usage error,
// or the spice executable doesn't support this SDK version
// check the log messages from sdk:
if (status != SPICE_SDK_STATUS_SUCCESS) {
return 0;
}
LOG_INFO("plugin loaded");
// spin up a worker thread
worker_thread = std::jthread(worker_thread_main);
return 1;
}
void
__cdecl
destroy_callback(
void
)
{
LOG_INFO("plugin unloading");
if (worker_thread.joinable()) {
worker_thread.request_stop();
worker_thread.join();
}
}
struct ArrowButton {
int key;
SPICE_SDK_DDR_BUTTONS button;
const char* name;
bool previous_state;
};
static ArrowButton arrow_buttons[] = {
{ VK_UP, DDR_Button_P1_PANEL_UP, "UP", false },
{ VK_DOWN, DDR_Button_P1_PANEL_DOWN, "DOWN", false },
{ VK_LEFT, DDR_Button_P1_PANEL_LEFT, "LEFT", false },
{ VK_RIGHT, DDR_Button_P1_PANEL_RIGHT, "RIGHT", false },
};
// worker thread for I/O
static void worker_thread_main(std::stop_token stop_token) {
while (!stop_token.stop_requested()) {
for (auto& arrow : arrow_buttons) {
// check for ctrl + arrow keys and trigger p1 pad arrows
if (((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0) &&
((GetAsyncKeyState(arrow.key) & 0x8000) != 0)) {
spice.set_button(arrow.button, true, 1.f);
if (!arrow.previous_state) {
LOG_INFO(std::format("let me hear you say: {}", arrow.name).c_str());
arrow.previous_state = true;
}
} else {
spice.set_button(arrow.button, false, 0.f);
arrow.previous_state = false;
}
}
Sleep(1);
}
}