On Timer Design, Part 2 of 3

Last time we saw four timer systems and their evolution. Today, let’s look at the mistakes in each, and how they affect SourceMod’s final design.

With AMX Mod X, as usual, the mistakes were plentiful. The most serious was that timers were non-uniquely identifiable, a direct fault of user-chosen ids. Unfortunately this was a convenience benefit so users could make up base ids and add an offset. For example, you could have:

#define PLAYER_TASK   0x3000 

Then if you passed PLAYER_TASK+id into set_task, you could subtract PLAYER_TASK from the task id in the callback, and retrieve the player index. Aside from this convenience, non-unique task ids were a hindrance and led to unreadable code, as users randomly created large numbers to avoid internal collision.

The second mistake was having so many confusing modes. In AMX Mod X, the entire timer list is checked every 0.1 seconds. This cannot really be optimized without splitting the timers into multiple lists, and that is a direct result of the extra modes based on map changes. Furthermore, these extra flags added unnecessary complexity for functions that are very rarely used

Lastly, AMX Mod X had a very, very horrible function, called task_exists(). It should be obvious why such a function is a bad idea, but let’s go over why in case you don’t see it: You should always know if a task exists or not. You either created it or you didn’t. If you did create it, it has either executed or it hasn’t. This function is a very expensive call and exposes two bad design practices:

  • Not writing deterministic code. If you have to make sure a timer is currently running or not, you should be using global variables that deterministically tell you whether the task in session.
  • Inventing task ids so you can check later if they’re still in session. Again, you can save this globally; why eat up another unique ID?

Task ids are a problem shared between both AMX Mod X and the proprietary system. In order to find the timer associated with a task id, you must iterate through all tasks until you get a match. This makes timer removal/lookup very expensive (and another reason task_exists is so bad in AMX Mod X). Luckily, properly written code usually doesn’t rely on task lookup; it only uses the TaskId for sanity checks, but the issue is still there when it is used.

However, let’s take a look at CSDM and CS:S DM’s timers, which use objects: in both systems, the object pointer you pass in is the same as the unique task identifier. The trouble here is: what if you want to create two timers on the same object? This fundamental flaw makes both systems very limited. While the proprietary system gets around this, it still exposes a ‘deleteThis’ function, meaning it treats each task instance as a temporary object. CS:S DM is the worst, and doesn’t even give you a choice of whether to delete the object or not. Conclusion here: user-owned timer identifiers are bad, and task ids are bad, which means all four systems are bad.

Lastly, AMX Mod X is the only system to actually tell the callback what timer is being run! In the other three systems, whether they support multiple timers on the same object or not, the unique timer identifier is never passed into the object. This is quite problematic.

How does SourceMod’s Timer System fix these issues?

  • Timers are identified by a pointer created by the timer system. They are not owned by the user.
  • Timer callbacks (there are two: one for executing, one for terminating) all receive the given timer identifier, even if it’s the same object being used.
  • There are only two modes: repeat or no repeat. There are no counters. Repeatable timers are stored in a flat list, single timers are sorted for optimal iteration.
  • Although repeatable timers can short-cut out like in CS:S DM, the timer removal functions are O(1) because of pointer usage.

As such, the SourceMod timer system is not only a lot simpler, but much more flexible and easier to work with. It’s also more optimized.

Bonus Question: Why were repeating counters left out?
Answer: You don’t need them. Since there are multiple ways to kill a repeating task, a counter is just syntax fluff; you can make your own. The system is easier to design if extra unnecessary options are left out. Furthermore, repeatable counters are not very common, and when they are needed, it’s usually for just one repeat where creating a new task suffices.

The third part of this series will be continued later.

Update March 14, 2007 4:46PM Fixed SVN link.

Leave a Reply

You must be logged in to post a comment.