Smart Timers

Every game needs some kind of timers to control gameplay related events. A timer works kind of like a stop watch in real life. There are different ways to keep track of time, this method some nice advantages that will make your life easier! To start with lets look at this example that uses a timer to track how long it’s been since the player was last damaged.

static float timeSinceDamaged = 0;
timeSinceDamaged += deltaTime;

if (wasDamaged)
    timeSinceDamaged = 0;

That is a very simple way to keep a timer is by just initializing a float and accumulating the delta time every frame. However it’s not very clean because larger games can have hundreds of timers and it would be ideal to eliminate code like this all over the place. Floating point inaccuracy is another problem that eventually crops up when the time becomes very large. After running for a few hours the precision loss can start to become a big issue.  Smart timers are a way of simplifying code, optimizing, and controlling the floating point inaccuracy at the same time.

struct SmartTimer
{
  SmartTimer() : 
    startTime(invalidTime) {}

  SmartTimer(float currentTime) : 
    startTime(totalTime - long(conversion*currentTime)) {}

  void Set()
    { startTime = totalTime; }

  void Set(float currentTime)
    { startTime = totalTime - long(conversion*currentTime); }

  operator float() const			
    { return IsSet()? (totalTime - startTime)/conversion : 0; }

  void UnSet()
    { startTime = invalidTime; }

  bool IsSet() const
    { return startTime != invalidTime; }

  static void UpdateGlobal(long timeDelta)
    { totalTime += timeDelta; }

private:

  long startTime;
  static long totalTime;
  static const long invalidTime = LONG_MAX;
  static const float conversion;
};

long SmartTimer::totalTime = 0;
const float SmartTimer::conversion = 10000;

Smart timers encapsulate timer functionality into a struct that holds only a long so it uses exactly the same amount of memory as a float. The constructor sets it to an invalid state where the timer is effectively disabled. A simple call to Set() causes the timer to start at 0 or if a value is passed in it will start the timer with that instead. There’s no need to manually increment this timer because a single call to the UpdateGlobal() function is enough to update every timer in the game. A timer can be stopped by calling Invalidate() and that state can also be checked by calling IsValid(). This is desirable because we can control whether a timer is running and check it’s time without using any extra memory. stopwatch2

The issue of floating point accuracy is also addressed by storing the time as 100’s of nanoseconds (10^-4 seconds) which means the precision is fixed.  This will probably be a high enough accuracy for most things but if necessary the code can easily be modified by changing the conversion factor. You may also want to change the storage type to extend the amount of time it can run before wrapping becomes an issue. The values used in this example can run for about 2 days with total accumulated inaccuracy of only a few seconds when updated at 60 fps and it can run for weeks without overflowing. In practice you will want to reset the timers whenever possible such as when a new game begins, but for simulations it may be useful to have something that runs for days.

Another option is to store the times doubles which is somewhat simpler by eliminating the need for a conversion factor. The downside is that it uses twice as much memory and will eventually lose precision over time rather then providing consistent precision like an integer type does. Converting from double to float may be slower or faster then the integer conversion depending on the processor.

Smart timers have been an invaluable tool that I’ve used in almost every project I’ve been a part of. Please feel free to copy or modify my smart timer class for your own projects. My open source game engine uses a more complex version that incorporates interpolation for those interested in learning more.

This entry was posted in Game Dev and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *