On Timer Design, Part 1 of 3

In the past four large projects I’ve worked in, save for AMX Mod X, I’ve directly designed the timer system in each. In all four, the timer system was either cludgy or lacked some important feature or consideration. When I stepped up to prototype and document the SourceMod version (which faluco is implementing), I knew exactly what had gone wrong with previous iterations, and thus prototyping it was very easy.

In all systems, timers are encapsulated in “timer objects” — the difference is in how they are created, how their intervals are computed, and how they are destroyed.

AMX Mod X: The Worst of All
First, let’s take a look at what’s probably the worst of all, AMX Mod X: the nefarious set_task() function. Given an interval of time k, it has five modes of acting on this interval:

  • default: Execute the timed function once after k seconds.
  • repeat: Repeat the timed function n times over intervals of k seconds.
  • loop: Repeat the timed function every k seconds indefinitely.
  • after start: Execute the timed function k seconds after the map’s timelimit is up (I don’t really understand this one).
  • before timeleft: Execute the timed function k seconds before a map’s timelimit is up.

As if this wasn’t confusing enough, timer callbacks had two other parameters: the task_id and the data array. If a data array was specified, the task_id was the second parameter in the callback; otherwise it was the first. By default, task_ids were 0, but they could be any number you chose. When destroying tasks (usually repeatable ones) with remove_task, you had to specify the id and an “external” or “internal” flag. All tasks with a matching id were then removed; if you specified “external,” tasks outside of the given plugin were removed as well.

A Bit Better: CSDM 2
In CSDM 2, timer objects have no extra modes. They are always one-time executes with a given interval from the current time. A timer was represented by an object which contained the two callback functions:

  • void Run(void): Tells the object to run the task.
  • void deleteThis(): Tells the object to delete itself once the timer system is done with the object.

Rather than task ids, which were optional and non-unique, tasks are identified by the actual object pointer itself. Thus when you created a timer instance, the instance you passed to the timer system was the unique identifier.

CSDM Tasks got two callbacks, exposed through the task object (the system is object oriented):

Proprietary Tasks
For a proprietary project I worked on, I made a third task system. This one was designed to be more flexible than the CSDM one, but less complicated than the AMX Mod X version. The API was similar to CSDM 2′s, except with a few changes:

  • Tasks had a “repeatable” flag and a built-in counter to allow for both infinite and ending repeatable tasks.
  • Tasks were identified by task ids again, except they were unique integers decided by the timer system itself. Thus they were neither pointers nor user-given identifiers.
  • A new function was introduced: FireTask, which allowed you to short-cut and execute a task right away, by its TaskId.
  • The Run() function could return either Timer_Continue or Timer_End, which let you short-cut out of repeatable tasks without calling the expensive KillTask function.

CS:S DM Tasks
Lastly, CS:S DM had essentially the same design as the proprietary system, with minor convenience changes. The deleteThis was removed for virtual destructors instead. Also, task ids were again abandoned in favor of object pointers to the task event itself.

Conclusion
In Monday’s article, I will explain why the design kept changing, and the how and why of SourceMod’s evolved timer system.

Leave a Reply

You must be logged in to post a comment.