- Joined
- Jul 10, 2007
- Messages
- 6,306
Skip everything and read this post instead
The timer system algorithm I came up with is a binary heap of lists.
The heads of the lists are stored in the binary heap, which include timestamps so that you know precisely how much time is left in a timer. The only running timer at any given time is the root of the heap.
The lists on the heap are based on remaining time: each one contains a constant timeout. For example, if a timer were to expire and repeat, it'd move to the back of the list it's on.
So.. when allocation occurs, first it takes the timeout of the timer to see if there is a timer queue already created for it. If there isn't, it creates that queue.
The timer queue is a segmented list (timers can be started at different timers). It looks at the last list element to see if it's timestamp matches the created timer's stamp. If they match, the created timer is added to that segment. If not, a new segment is made and the timer is added to that new segment.
Each segment contains a trigger. When a timer is added to a segment, it's boolean expression code is added to that segment's trigger. When a segment expires, the trigger for that segment is evaluated.
You don't loop through timers on a segment. Data retrieve is this-
What this means is that when a boolean expression on the trigger is fired and retrieves data, it will end up preparing the next node. When the trigger is finished being evaluated, currentNode will be the last node on the segment.
If a timer is destroyed, it is added to a destruction list (for recycling and removing from trigger). If a timer is paused or moved to another segment, it is added to a removal list (for removing itself from trigger only).
When a segment is finished firing (after trigger evaluation), it will always be on the last node for that segment (destruction/pause/movement will handle updating the node to the previous if that node was the current node for expiration). If that last node happened to be a segment head, then the expired segment is empty and can be deallocated. If not, then a range operation can be performed using the head of the segment and that last node to push it behind the segment's head (node inside of the heap). The heap node's timestamp is then updated and it is cycled through heap (new position).
In the case of list removal, the heap node's last node is then checked to see if it's a head. If it is a head, the heap node is empty and can be deallocated. In this case, the heap node is removed from the timer heap.
Triggers continue to be evaluated until the remaining time of the current root of the heap is not 0 (accurate to 6 decimal places).
So... this makes it so that there is only 1 node in the heap for each timeout, 1 trigger for each set of expiring timers (segments), minimal loops and minimal building/clearing of triggers and trigger destruction. And ofc, all of this would run on 1 timer.
In most cases, there will only be 1 binary heap operation (cycle/delete) and 1 list operation ^^.
So, anyone know of any better algorithms, or is this the best one for timers?
There is a timer wheel algorithm as well, but that'd only seem useful for other languages where you are depending on sleep command and ticks. Really, I almost think the algorithm I came up with is better than the timer wheel algorithm as it takes up way less memory and only minimally slower (binary heap operations).
edit
The one thing that I don't like in this design is the use of timestamps. If anyone can think up a way to do this w/o the use of timestamps, that'd be amazing ; D.
The timer system algorithm I came up with is a binary heap of lists.
The heads of the lists are stored in the binary heap, which include timestamps so that you know precisely how much time is left in a timer. The only running timer at any given time is the root of the heap.
The lists on the heap are based on remaining time: each one contains a constant timeout. For example, if a timer were to expire and repeat, it'd move to the back of the list it's on.
So.. when allocation occurs, first it takes the timeout of the timer to see if there is a timer queue already created for it. If there isn't, it creates that queue.
The timer queue is a segmented list (timers can be started at different timers). It looks at the last list element to see if it's timestamp matches the created timer's stamp. If they match, the created timer is added to that segment. If not, a new segment is made and the timer is added to that new segment.
Each segment contains a trigger. When a timer is added to a segment, it's boolean expression code is added to that segment's trigger. When a segment expires, the trigger for that segment is evaluated.
You don't loop through timers on a segment. Data retrieve is this-
JASS:
set currentNode=currentNode.next
return currentNode
What this means is that when a boolean expression on the trigger is fired and retrieves data, it will end up preparing the next node. When the trigger is finished being evaluated, currentNode will be the last node on the segment.
If a timer is destroyed, it is added to a destruction list (for recycling and removing from trigger). If a timer is paused or moved to another segment, it is added to a removal list (for removing itself from trigger only).
When a segment is finished firing (after trigger evaluation), it will always be on the last node for that segment (destruction/pause/movement will handle updating the node to the previous if that node was the current node for expiration). If that last node happened to be a segment head, then the expired segment is empty and can be deallocated. If not, then a range operation can be performed using the head of the segment and that last node to push it behind the segment's head (node inside of the heap). The heap node's timestamp is then updated and it is cycled through heap (new position).
In the case of list removal, the heap node's last node is then checked to see if it's a head. If it is a head, the heap node is empty and can be deallocated. In this case, the heap node is removed from the timer heap.
Triggers continue to be evaluated until the remaining time of the current root of the heap is not 0 (accurate to 6 decimal places).
So... this makes it so that there is only 1 node in the heap for each timeout, 1 trigger for each set of expiring timers (segments), minimal loops and minimal building/clearing of triggers and trigger destruction. And ofc, all of this would run on 1 timer.
In most cases, there will only be 1 binary heap operation (cycle/delete) and 1 list operation ^^.
So, anyone know of any better algorithms, or is this the best one for timers?
There is a timer wheel algorithm as well, but that'd only seem useful for other languages where you are depending on sleep command and ticks. Really, I almost think the algorithm I came up with is better than the timer wheel algorithm as it takes up way less memory and only minimally slower (binary heap operations).
edit
The one thing that I don't like in this design is the use of timestamps. If anyone can think up a way to do this w/o the use of timestamps, that'd be amazing ; D.
Last edited: