I just had a look at the implementations of the Sleep functions for both Win32 and Unix.
Even though it probably has nothing directly to do with this thread, the implementation of Sleep in Unix was sort of... broken in specific cases
. When using pthread_cond_wait or pthread_cond_timedwait, one has to be aware of the evil
spurious wakeups that can occur. They are supposed to be a rare occurrence, but if for some reason on some system they happen more than expected, using sf::Sleep is like taking part in a lottery
. To fix this, either you evaluate the condition every time the function returns or you reimplement Sleep without using it (which is what I did). In the comments you mentioned that usleep is unreliable and might put the entire process to sleep but usleep also isn't part of POSIX any more, I guess because it was that horrible. Instead you can use nanosleep which is part of POSIX as well and should be better supported on all compatible platforms.
For the Win32 implementation,
... and on Windows I think it's impossible.
this is only partially true. On Windows you can set the timer resolution to whatever you want within the system specified limits. On my system this is anything from 1ms to 1000000ms. According to Microsoft they implemented it like this originally to save energy and increase system responsiveness. Now with the new timer coalescing API it got a bit more complicated but that isn't something we have to worry about. Fact is that the default timer resolution on Windows systems is not 1ms but e.g. on Windows 7 15.6ms. The problem arrises when a user calls sf::Sleep with values that don't fit into this interval nicely. You can read more about this in
this Microsoft document.
If you call sf::Sleep with a value of 1ms in my tests Windows will almost always sleep for 15.6ms instead, so calling sf::Sleep( sf::milliseconds( 1 ) ); 1000 times actually puts the thread to sleep for 15.6 seconds instead of 1 second. It seems that Windows likes to round the sleep times up but also rounds down sometimes. If you sleep for some value that is a multiple of 15.6ms then everything behaves as you would expect.
So in essence if you want to lock your application at 60 FPS, you tell SFML to sleep for 1/60 seconds which is 16.6ms. If you are lucky, this is close enough to 15.6ms that it doesn't have any adverse side effects on your application. However if you are not so lucky it is rounded up to 31.2ms which causes your application to run at half the desired frame rate. I assume this is happening here. In order to force Windows to obey your desired frame rate up to 1000 FPS you would need to set the timer resolution to 1ms which is what I did and everything worked nicely. Anything above 1000 FPS and Windows can't handle it because 1ms is the lowest resolution available (but seriously, why would you set the limit to something above 1000 FPS?
)
So at the default resolution of 15.6ms on Windows 7 sf::Sleep can only be truly accurate at 64, 32, 16... FPS. In extreme cases (such as is the case here) where you deviate from those values only just a bit Windows locks you at the next lower frame rate.
Here you can test sf::Sleep with and without timeBeginPeriod().
#include <iostream>
#include <SFML/System.hpp>
#include <windows.h>
int main() {
//timeBeginPeriod( 1 );
{
sf::Clock clock;
for( int i = 0; i < 1000000; i++ ) {
sf::sleep( sf::microseconds( 1 ) );
}
std::cout << clock.getElapsedTime().asMilliseconds() << "ms\n";
}
{
sf::Clock clock;
for( int i = 0; i < 1000; i++ ) {
sf::sleep( sf::milliseconds( 1 ) );
}
std::cout << clock.getElapsedTime().asMilliseconds() << "ms\n";
}
{
sf::Clock clock;
for( int i = 0; i < 1000/15; i++ ) {
sf::sleep( sf::milliseconds( 15 ) );
}
std::cout << clock.getElapsedTime().asMilliseconds() << "ms\n";
}
{
sf::Clock clock;
for( int i = 0; i < 1; i++ ) {
sf::sleep( sf::seconds( 1 ) );
}
std::cout << clock.getElapsedTime().asMilliseconds() << "ms\n";
}
//timeEndPeriod( 1 );
return 0;
}
The changes I made to the library can be found here:
https://github.com/binary1248/SFML/commit/20db4969c59c06c4605473fc8f36ad7e5859453bSetting timeBeginPeriod to 1 might increase power consumption when running an SFML application that uses sf::Sleep but this is negligible because the high-resolution timers do this already anyway. It's never too late to contribute to global warming in your own special way
.