Skip to content

Comments

Increase the timer resolution for a more precise sleep on Windows#2476

Open
MiniBaker007 wants to merge 6 commits intoPixelGuys:masterfrom
MiniBaker007:better-sleep
Open

Increase the timer resolution for a more precise sleep on Windows#2476
MiniBaker007 wants to merge 6 commits intoPixelGuys:masterfrom
MiniBaker007:better-sleep

Conversation

@MiniBaker007
Copy link
Contributor

On windows:
At the start of the application, the timer resolution is set to the maximum supported value, usualy 0.5 or 1 millisecond. To get consistant timing, we undersleep based on the timer resolution and busy wait the rest.

I also made it set the game's process priority class to "Above normal" ,which helps sleeping be more consistant. This might also generally improve the game's performance a little, I'm not sure.

From my testing, this achieves a quite consistant framerate, but that can depend on a lot of variables that are difficult to control for. So, if anyone is able to test this, that would be helpful.

Also, I'm not convinced with some of the ways I named things and structured the code, but I decided to get others' opinions rather than to keep overthinking it.

@Argmaster Argmaster moved this to Low Priority in PRs to review Jan 11, 2026
@Argmaster
Copy link
Collaborator

Hey, sorry but I will give it low prio, because it's technically already solved, even if the solution is crude. I do appreciate the effort tho!

@@ -513,7 +514,8 @@ pub fn main() void { // MARK: main()
std.log.info("Starting game with version {s}", .{settings.version.version});

if(builtin.os.tag == .windows) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you made a new file for this, I think all operating system nuances should be handled inside of it, and the calling code should be OS-agnostic.

I'd suggest to structure the file similarly to the file_monitor.zig

std.log.info("Set system timer resolution: {d} -> {d}.", .{initialResolution, currentResolution});
}

pub fn resetTimerResolution() void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused function

const start = main.timestamp();
const targetTime = start.addDuration(sleepDuration);

// Update currentResolution because it can change
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait what? Can windows just change this behind our back? Otherwise I'd remove this, since this is a value that we should only set once.

_ = NtQueryTimerResolution(&minResolution, &maxResolution, &currentResolution);

// Negative value for relative time
var delayInterval = -(@divFloor(@as(windows.LARGE_INTEGER, @intCast(sleepDuration.nanoseconds)), 100) - currentResolution);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var delayInterval = -(@divFloor(@as(windows.LARGE_INTEGER, @intCast(sleepDuration.nanoseconds)), 100) - currentResolution);
var delayInterval = currentResolution - @divFloor(@as(windows.LARGE_INTEGER, @intCast(sleepDuration.nanoseconds)), 100);

// Negative value for relative time
var delayInterval = -(@divFloor(@as(windows.LARGE_INTEGER, @intCast(sleepDuration.nanoseconds)), 100) - currentResolution);
if(delayInterval < 0) {
_ = NtDelayExecution(windows.FALSE, &delayInterval);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this different from what Io.sleep calls internally?


while(main.timestamp().durationTo(targetTime).nanoseconds > 0) {
std.atomic.spinLoopHint();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we actually need this busy loop here. Do we need to be this precise? I think it won't be a big deal if the actual time varies by +/- 1 ms.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to handle the oversleeping case by faking the deltaTime and making the sleep in the next frame shorter to account for the difference.

}
};

pub fn setPriorityClass(priority: PriorityClass) void {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is a good idea. In general I'd say we should play nicely with the OS, and setting the entire process priority could seriously harm other applications running (e.g. discord) since Cubyz uses a ton of threads. If at all, we should only prioritize the render thread.

}
}

extern "ntdll" fn NtQueryTimerResolution(MinimumResolution: *windows.ULONG, MaximumResolution: *windows.ULONG, CurrentResolution: *windows.ULONG) callconv(.winapi) windows.NTSTATUS;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to just import the corresponding headers with a C import. This reduces the chance to make mistakes.

@IntegratedQuantum IntegratedQuantum moved this from Low Priority to In review in PRs to review Jan 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

3 participants