Simple Game Loop

I was making a video of gameplay for a game I made previously when I noticed that the level timer was horribly slow. For a 23-minute video, it said that it took me 6-minutes to play the level! First, I looked at the code for outputting the time. That was fine. Then I traced the method calls that set the time all the way back to the main loop.

I’ve had various problems with the game running sluggishly since before the game was released. On newer computers, it wasn’t much of a problem, but it was horrible on older computers. It was odd because the game didn’t seem like it should use much in the way of system resources. I’ve “fixed” this many times without the problem really being fixed. I decided to take a long hard look at my game loop.

Here was my old code:

protected final void runSafely() {
	//verify that the loop will continue
	shouldContinueLoop = true;
	
	//This variable is used to only skip yielding a certain number of times in a row.
	byte numDelays = 0;
	
	//This variable stores how much time the loop overslept by in the last cycle.
	//Negative values probably won't occur because Thread.sleep shouldn't undersleep.
	long overSleepTime = 0L;
	
	//This variable is used for computing the length of an update phase.
	long lastUpdatePhase = Time.currentTimeMillis();

	while(shouldContinueLoop) {
		//if the application isn't active, sleep for half a second
		if(!isApplicationActive()) {
			try {
				Thread.sleep(500);
			} catch(InterruptedException exception) {}
				
			continue;
		} //end if the frame isn't active
		
		//figure out the start of the current phase
		long currentCycle = Time.currentTimeMillis();
		long updatePhaseLength = currentCycle - lastUpdatePhase;
		if(updatePhaseLength > period)
			updatePhaseLength = period;
		
		//update the current model
		doUpdatePhase(updatePhaseLength);
		lastUpdatePhase = Time.currentTimeMillis();
	
		//render the game
		render();
	
		//figure out how long the cycle took
		long timeAfterCycle = Time.currentTimeMillis();
		long cycleLength = timeAfterCycle - currentCycle;
		long sleepTime = period - cycleLength - overSleepTime;
		
		//if some time is left in this cycle
		if(sleepTime > 0) {
			//sleep
			try {
				Thread.sleep(sleepTime);
			} catch(InterruptedException exception) {}
				
			overSleepTime = Time.currentTimeMillis() - timeAfterCycle - sleepTime;
		} //end if some time is left in this cycle
		//else no time is left in this cycle
		else {
			overSleepTime = 0L;

			//don't sleep, but yield if absolutely necessary
			numDelays++;
			if(numDelays >= maximumDelaysPerYield) {
				//yield to let other threads execute
				Thread.yield();
				numDelays = 0;
			} //end if there have been too many delays
		} //end else no time is left in this cycle
	} //end while forever
} //end runSafely

The code was heavily based upon the game loop code in Killer Game Programming in Java, though it wasn’t quite identical. The point of most of the code was to allow other processes to run at the same time as the game rather than hogging all the system’s resources. I deleted most of that code.

Suddenly, the sluggishness problems were less, though the bug with the time wasn’t fixed. It increased the “period” constant from 40 to 250 because it is effectively the maximum length of the update phase in milliseconds. The problem was solved.

I also changed one other line of code

lastUpdatePhase = Time.currentTimeMillis();

to

lastUpdatePhase = currentCycle;

. This didn’t change anything for me, but I figure I was losing the time that update phase took, which wouldn’t show up on the timer. Since my computer is way more powerful than what the program needs, the update phase just happened to be almost instantaneous.

This is now my game loop for a game I’m updating:

protected final void runSafely() {
	//verify that the loop will continue
	shouldContinueLoop = true;
	
	//This variable is used for computing the length of an update phase.
	long lastUpdatePhase = Time.currentTimeMillis();

	while(shouldContinueLoop) {
		//if the application isn't active, sleep for half a second
		if(!isApplicationActive()) {
			try {
				Thread.sleep(500);
			} catch(InterruptedException exception) {}
				
			continue;
		} //end if the frame isn't active
		
		//figure out the start of the current phase
		long currentCycle = Time.currentTimeMillis();
		long updatePhaseLength = currentCycle - lastUpdatePhase;
		if(updatePhaseLength > maximumUpdateLength)
			updatePhaseLength = maximumUpdateLength;
		
		//update the current model
		doUpdatePhase(updatePhaseLength);
		lastUpdatePhase = currentCycle;
	
		//render the game
		render();
	} //end while forever
} //end runSafely

I won’t go so far as to say that the code from Killer Game Programming in Java was wrong because I didn’t copy it exactly. I’m just saying that you have to be careful what you do with your main loop code. My program has always been somewhat sluggish because of the main loop code, and I didn’t notice the timer but until I made a gameplay video.

The problem is probably caused by how the rest of my code worked. In KGPiJ, the update phase seems like it does a specific amount of updating in each phase. My own code sends the number of milliseconds elapsed to the update method, which updates different amounts based upon how much time has passed. This caused me to change parts of KGPiJ’s game loop, which is probably how things got screwed up.

I have some concerns that my program might run at 100% CPU usage now, but that doesn’t seem to actually happen for some reason. In any case, I would rather use 100% of the CPU than have the game run slowly with an incorrect timer.

Remember that if you have a multicore CPU, your game loop thread will only hog one core. So if you have four cores you will only see 25% usage. But anyone with a single core CPU will see 100% usage.

This IS an issue because that will certainly drive up the fan speed to vacuum cleaner levels.

If you worry about the imprecise sleep / time readouts in Windows environments: there is a documented workaround for that. Start up an endlessly sleeping daemon thread. The JVM keeps the timer precision at 1ms as long as at least one thread is asleep (and you don’t sleep for an interval that is a multiple of 10 - Long.maxLong works just fine). Its a bit of a hack, but I use it with good results and so have others I’ve shared the trick with.

After many years wrangling and wringing my hands in despair I finally turned my back on sleep() and use a busy yield() loop once more, because I get rock steady framerates and bedamned the CPU gobbling power. I probably caused a flower to wilt somewhere in a rainforest, and then resumed my guilt-free life of lethargy and rampant consumerism.

Cas :slight_smile:

I agree with Cas. I turned to yield() a long time ago, and framerates have been constant since :wink:

Haha. I use a happy medium - there is yielding and also after a given interval a sleep for just 1ms. The framerate is great and my CPU consumption remains low.

Hmm, an interesting idea. I might give it a go and see how it fares.

Cas :slight_smile:

I once did this myself in a second thread that handled sound. It busy yielded whilst waiting to push the next block of data (or something to that effect). The game was unusable on Windows XP, MacOS and Linux (fine on Vista and Win7). Busy yield loops can easily starve other threads (yes even though they are repeatedly yielding).

I’d make the sound thread a higher priority than the game thread, but make it so that it sleeps().

Cas :slight_smile:

I didn’t notice the replies to this thread before.

I added a Thread.yield() with no apparent difference. I don’t think I want to mess with it any more than that since it actually works now. I might tamper with it for future games, but this is a game that I released quite two years ago.

Maybe now that it doesn’t have these performance issues on certain computers, someone will actually buy it.

It was never that much of an issue on newer computers, but it did mess up the timer. I had received reports of the game being unplayable on really old computers, but I used to think it was because of the giant background images. Removing the background images did increase performance, but that wasn’t the real issue. Sadly, it’s difficult to pinpoint these things because I don’t have any QA staff or even a bunch of old computers to test the game.