Sunday, August 18, 2013

Moving through time

Before I start, I know it's been a long time since my last post. Over the next couple days I'm going to write a series of posts about what I've been working on these last two weeks. So without further ado, here is the first one:

While I was coding in the last couple of weeks, I noticed that every time I came back to the main game from a debug window, the whole window hung for a good 6 seconds. After looking at my run() loop for a bit, I realized what the problem was. When I returned from the debug window, the next frame would have a massive deltaTime, which in turn caused a huge frame delay. This was partially a problem with how I had structured my frame delay calculation, but in the end, I needed a way to know when the game was paused, and to modify my deltaTime value accordingly.

To solve the problem, I came up with a pretty simple Clock class that tracks time, allows pausing, (and if you really wanted scaling/reversing):
/* Class for handling frame to frame deltaTime while keeping track of time pauses/un-pauses */
class Clock {
public:
Clock(OSystem *system);

private:
OSystem *_system;
uint32 _lastTime;
int32 _deltaTime;
uint32 _pausedTime;
bool _paused;

public:
/**
* Updates _deltaTime with the difference between the current time and
* when the last update() was called.
*/
void update();
/**
* Get the delta time since the last frame. (The time between update() calls)
*
* @return    Delta time since the last frame (in milliseconds)
*/
uint32 getDeltaTime() const { return _deltaTime; }
/**
* Get the time from the program starting to the last update() call
*
* @return Time from program start to last update() call (in milliseconds)
*/
uint32 getLastMeasuredTime() { return _lastTime; }

/**
* Pause the clock. Any future delta times will take this pause into account.
* Has no effect if the clock is already paused.
*/
void start();
/**
* Un-pause the clock.
* Has no effect if the clock is already un-paused.
*/
void stop();
};


I'll cover the guts of the functions in a bit, but first, here is their use in the main run() loop:
Common::Error ZEngine::run() {
initialize();

// Main loop
while (!shouldQuit()) {
_clock.update();
uint32 currentTime = _clock.getLastMeasuredTime();
uint32 deltaTime = _clock.getDeltaTime();

processEvents();

_scriptManager->update(deltaTime);
_renderManager->update(deltaTime);

// Update the screen

// Calculate the frame delay based off a desired frame time
int delay = _desiredFrameTime - int32(_system->getMillis() - currentTime);
// Ensure non-negative
delay = delay < 0 ? 0 : delay;
_system->delayMillis(delay);
}

return Common::kNoError;
}


And lastly, whenever the engine is paused (by a debug console, by the Global Main Menu, by a phone call, etc.), ScummVM core calls pauseEngineIntern(bool pause), which can be overridden to implement any engine internal pausing. In my case, I can call Clock::start()/stop()
void ZEngine::pauseEngineIntern(bool pause) {
_mixer->pauseAll(pause);

if (pause) {
_clock.stop();
} else {
_clock.start();
}
}


All the work of the class is done by update(). update() gets the current time using getMillis() and subtracts the last recorded time from it to get  _deltaTime. If the clock is currently paused, it subtracts off the amount of time that the clock has been paused. Lastly, it clamps the value to positive values.
void Clock::update() {
uint32 currentTime = _system->getMillis();

_deltaTime = (currentTime - _lastTime);
if (_paused) {
_deltaTime -= (currentTime - _pausedTime);
}

if (_deltaTime < 0) {
_deltaTime = 0;
}

_lastTime = currentTime;
}


If you wanted to slow down or speed up time, it would be a simple matter to scale _deltaTime. You could even make it negative to make time go backwards. The full source code can be found here and here.

Well that's it for this post. Next up is a post about the rendering system. Until then, happy coding!

-RichieSams