As an addition to Journeyman Geek's answer (because my edit got rejected) for the people who are interested in the coding part/developer perspective:
From the programmers perspective, for those who are interested, the DOS times were times when every CPU tick was important, so programmers kept the code as fast as possible.
A typical scenario where any program will run at the max CPU speed is this simple pseudo C:
int main()
{
while(true)
{
}
}
This will run forever.
Now, let's turn this code snippet into a pseudo-DOS game:
int main()
{
bool GameRunning = true;
while(GameRunning)
{
ProcessUserMouseAndKeyboardInput();
ProcessGamePhysics();
DrawGameOnScreen();
// close game
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Unless the DrawGameOnScreen
function uses double buffering/V-sync (which was kind of expensive in the days when DOS games were made), the game will run at maximum CPU speed. On a modern day mobile i7, this would run at around 1,000,000 to 5,000,000 times per second (depending on the laptop configuration and current CPU usage).
This would mean that if I could get any DOS game working on my modern CPU in my 64-bit Windows, I could get more than a thousand (1000!) FPS – which is too fast for any human to play – if the physics processing "assumes" it runs between 50 and 60 FPS.
What modern-day developers (can) do
- Enable V-Sync in the game (not available for windowed applications* – i.e., only available in full-screen apps)
- Measure the time since the last update and adjust the physics processing accordingly, which effectively makes the game/program run at the same speed regardless of the CPU speed
- Limit the framerate programmatically
* depending on the graphics card/driver/OS configuration, it may be possible.
For the first option, I won't show any examples because it's not really "programming" – it's just using the graphics features.
As for the other two options, I will show the corresponding code snippets and explanations.
Measure the time since last update
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
DrawGameOnScreen();
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
Here you can see that the user input and physics take the time difference into account, yet you could still get 1000+ FPS on screen because the loop is running as fast as possible. Because the physics engine knows how much time passed, it doesn't have to depend on "no assumptions" or "a certain frequency", so the game will work at the same framerate on any CPU.
Limit the framerate programmatically
What developers can do to limit the framerate to, for example, 30 FPS isn't any more difficult – just take a look:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
// if certain number of milliseconds pass...
if(LastTick-LastDraw >= TimeToPassBeforeNextDraw)
{
// draw our game
DrawGameOnScreen();
// and save when we last drew the game
LastDraw = LastTick;
}
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
What happens here is that the program counts the milliseconds passed, and when a certain amount is reached (33 ms), it redraws the game screen, effectively applying a frame rate near 30 FPS.
Also, the developer may choose to limit all processing to 30 FPS by slightly modifying the above code:
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
double DESIRED_FPS = 30;
// how many milliseconds need to pass before the next draw so we get the framerate we want
double TimeToPassBeforeNextDraw = 1000.0/DESIRED_FPS;
// note to geek programmers: this is pseudo code, so I don't care about variable types and return types
double LastDraw = GetCurrentTime();
while(GameRunning)
{
LastTick = GetCurrentTime();
TimeDifference = LastTick - LastDraw;
// if certain number of milliseconds pass...
if(TimeDifference >= TimeToPassBeforeNextDraw)
{
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
// draw our game
DrawGameOnScreen();
// and save when we last drew the game
LastDraw = LastTick;
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
}
}
}
Other alternatives
There are a few other methods, and some of them I really do hate. For example, using sleep(NumberOfMilliseconds)
.
I know this is one method to limit the framerate, but what happens when your game processing takes 3 milliseconds or more and then you execute the sleep? This will result in a lower framerate than the one which only sleep()
should be causing.
Let's, for example, take a sleep time of 16 ms. This would make the program run at 60 Hz. Now let's say the processing of the data, input, drawing and all the stuff takes 5 milliseconds. This gets us to 21 milliseconds for one loop, which results in slightly less than 50 Hz, while you could easily still be at 60 Hz, but because of the hardcoded sleep, it's impossible.
One solution would be to make an "adaptive sleep", in the form of measuring the processing time and deducting the processing time from the desired sleep, resulting in fixing our "bug":
int main()
{
bool GameRunning = true;
long long LastTick = GetCurrentTime();
long long TimeDifference;
long long NeededSleep;
while(GameRunning)
{
TimeDifference = GetCurrentTime() - LastTick;
LastTick = GetCurrentTime();
// process movements based on time passed and keys pressed
ProcessUserMouseAndKeyboardInput(TimeDifference);
// pass the time difference to the physics engine, so it can calculate anything time-based
ProcessGamePhysics(TimeDifference);
// draw our game
DrawGameOnScreen();
// close game if escape is pressed
if(Pressed(KEY_ESCAPE))
{
GameRunning = false;
}
NeededSleep = 33 - (GetCurrentTime() - LastTick);
if(NeededSleep > 0)
{
Sleep(NeededSleep);
}
}
}
This was a while back and I don't recall doing any compatibility trickery, but that's beside the point. There is plenty of information out there on how to fix this, but not so much on why exactly they run that way, which is what I'm asking. – TreyK – 2013-08-12T01:45:02.967
9Remember the turbo button on older PC's? :D – Viktor Mellgren – 2013-08-12T07:35:11.447
1Ah. Makes me remember the 1 sec delay on the ABC80 (Swedish z80-based PC with Basic).
FOR F IN 0 TO 1000; NEXT F;
– Macke – 2013-08-12T08:18:13.6601Just to clarify, "a few old programs I pulled off an early 90s-era Windows computer" are those DOS programs on a Windows machine, or Windows programs where this behaviour happens? I'm used to seeing it on DOS, but not windows, IIRC. – That Brazilian Guy – 2013-08-12T11:09:28.853
See also CPU or framerate limiting on older games
– BlueRaja - Danny Pflughoeft – 2013-08-12T16:14:44.963A quick note: even games as recent as the 2013 Need For Speed have made this error. The developers locked their game to 30 FPS, but also forced their game engine to update at FPS intervals. If you try to set the framerate to 60 through a CLI parameter, the movement and physics engine go all crazy. – Nzall – 2013-12-11T09:32:58.290