I have recently been researching authoritative servers and slowly piecing together the various components I will need to start implementation. One thing that I seem to be hung up on at the moment is how one would go about implementing an accurate fixed tick rate without locking an entire thread in a while(true) loop. I've come across several examples where individuals are sleeping the thread at a fixed interval, and this is how I had originally thought this would work, however through some tests I'm now not so sure and was hoping someone with real-world experience might be able to clarify what the best way to go about this would be. For example:
Using sleep:
using namespace std::chrono;
auto fixedTimeStep = 16ms; // 60 times per second(ish)
auto next = steady_clock::now();
auto prev = next - fixedTimeStep;
while (true)
{
// do stuff
auto now = steady_clock::now();
std::cout << duration_cast<milliseconds>(now - prev).count() << '\n';
prev = now;
// delay until time to iterate again
next += fixedTimeStep;
std::this_thread::sleep_until(next);
}
Yields the following results:
16
15
15
15
15
14
15
15
15
15
31
0
29
15
15
15
15
15
15
16
15
Where as if I were to NOT sleep as such:
using namespace std::chrono;
auto fixedTimeStep = 16ms; // 60 times per second(ish)
auto newTime = steady_clock::now();
auto prevTime = steady_clock::now();
auto tickTime = newTime - prevTime;
while (true)
{
newTime = steady_clock::now();
tickTime = newTime - prevTime;
if (tickTime >= fixedTimeStep)
{
// do stuff
std::cout << duration_cast<milliseconds>(newTime - prevTime).count() << '\n';
prevTime = newTime;
}
}
Results are always 16ms.
In my confusion, I started looking through the documentation/source for the networking library I plan to use, and in the usage example they're using the sleep method. I then recreated their loop using the functionality from the library as such:
double prevTime = yojimbo_time();
double m_time = 0.0f;
double fixedDt = 1.0 / 60.0;
while (true) {
double currentTime = yojimbo_time();
if (m_time <= currentTime)
{
// do something
std::cout << (currentTime - prevTime) << std::endl;
prevTime = currentTime;
m_time += fixedDt;
}
else
{
yojimbo_sleep(m_time - currentTime);
}
}
Which yielded similar results to my first loop using std chrono:
0.0149996
0.0160216
0.0160085
0.0160089
0.0147039
0.0303099
0.0150091
0.0155342
0.0158427
0.0155628
0.0155981
0.0151898
0.0158014
0.0154033
0.0159931
0.0159994
0.0160106
0.0159969
0.015992
0.0154033
0.0160725
0.0305259
0.0160052
0.0150037
0.0149955
Therefore I have to be confused about something, as the author of that library is highly regarded and I can't imagine they would be using sleep() if it was not a feasible solution….right? But then if your authoritative server MUST update at a fixed tickrate to insure a consistent accurate simulation…what gives?
Feel like I'm one “Aha” moment away from this making sense…hopefully someone on here can shed some light on where it is I'm confused ?