diff --git a/lib/Loop/Internal/TimerQueue.php b/lib/Loop/Internal/TimerQueue.php index 1e51de8f..4c1ff946 100644 --- a/lib/Loop/Internal/TimerQueue.php +++ b/lib/Loop/Internal/TimerQueue.php @@ -15,6 +15,60 @@ final class TimerQueue /** @var int[] */ private $pointers = []; + /** + * @param int $node Rebuild the data array from the given node upward. + * + * @return void + */ + private function heapifyUp(int $node) + { + $entry = $this->data[$node]; + while ($node !== 0 && $entry->expiration < $this->data[$parent = ($node - 1) >> 1]->expiration) { + $temp = $this->data[$parent]; + $this->data[$node] = $temp; + $this->pointers[$temp->watcher->id] = $node; + + $this->data[$parent] = $entry; + $this->pointers[$entry->watcher->id] = $parent; + + $node = $parent; + } + } + + /** + * @param int $node Rebuild the data array from the given node downward. + * + * @return void + */ + private function heapifyDown(int $node) + { + $length = \count($this->data); + while (($child = ($node << 1) + 1) < $length) { + if ($this->data[$child]->expiration < $this->data[$node]->expiration + && ($child + 1 >= $length || $this->data[$child]->expiration < $this->data[$child + 1]->expiration) + ) { + // Left child is less than parent and right child. + $swap = $child; + } elseif ($child + 1 < $length && $this->data[$child + 1]->expiration < $this->data[$node]->expiration) { + // Right child is less than parent and left child. + $swap = $child + 1; + } else { // Left and right child are greater than parent. + break; + } + + $left = $this->data[$node]; + $right = $this->data[$swap]; + + $this->data[$node] = $right; + $this->pointers[$right->watcher->id] = $node; + + $this->data[$swap] = $left; + $this->pointers[$left->watcher->id] = $swap; + + $node = $swap; + } + } + /** * Inserts the watcher into the queue. Time complexity: O(log(n)). * @@ -35,16 +89,7 @@ public function insert(Watcher $watcher) $this->data[$node] = $entry; $this->pointers[$watcher->id] = $node; - while ($node !== 0 && $entry->expiration < $this->data[$parent = ($node - 1) >> 1]->expiration) { - $temp = $this->data[$parent]; - $this->data[$node] = $temp; - $this->pointers[$temp->watcher->id] = $node; - - $this->data[$parent] = $entry; - $this->pointers[$watcher->id] = $parent; - - $node = $parent; - } + $this->heapifyUp($node); } /** @@ -105,7 +150,7 @@ public function peek() } /** - * @param int $node Remove the given node and then rebuild the data array from that node downward. + * @param int $node Remove the given node and then rebuild the data array. * * @return void */ @@ -117,29 +162,13 @@ private function removeAndRebuild(int $node) $this->pointers[$left->watcher->id] = $node; unset($this->data[$length], $this->pointers[$id]); - while (($child = ($node << 1) + 1) < $length) { - if ($this->data[$child]->expiration < $this->data[$node]->expiration - && ($child + 1 >= $length || $this->data[$child]->expiration < $this->data[$child + 1]->expiration) - ) { - // Left child is less than parent and right child. - $swap = $child; - } elseif ($child + 1 < $length && $this->data[$child + 1]->expiration < $this->data[$node]->expiration) { - // Right child is less than parent and left child. - $swap = $child + 1; - } else { // Left and right child are greater than parent. - break; + if ($node < $length) { // don't need to do anything if we removed the last element + $parent = ($node - 1) >> 1; + if ($parent >= 0 && $this->data[$node]->expiration < $this->data[$parent]->expiration) { + $this->heapifyUp($node); + } else { + $this->heapifyDown($node); } - - $left = $this->data[$node]; - $right = $this->data[$swap]; - - $this->data[$node] = $right; - $this->pointers[$right->watcher->id] = $node; - - $this->data[$swap] = $left; - $this->pointers[$left->watcher->id] = $swap; - - $node = $swap; } } } diff --git a/test/Loop/TimerQueueTest.php b/test/Loop/TimerQueueTest.php new file mode 100644 index 00000000..8c3c930e --- /dev/null +++ b/test/Loop/TimerQueueTest.php @@ -0,0 +1,46 @@ +type = Watcher::DELAY; + $watcher->id = $id++; + $watcher->callback = static function () {}; + $watcher->expiration = $watcher->value = $value; + $watchers[] = $watcher; + } + + $toRemove = $watchers[$indexToRemove]; + foreach ($watchers as $watcher) { + $queue->insert($watcher); + } + $queue->remove($toRemove); + + \array_splice($values, $indexToRemove, 1); + \sort($values); + $output = []; + while (($extracted = $queue->extract(\PHP_INT_MAX)) !== null) { + $output[] = $extracted->expiration; + } + + $this->assertSame($values, $output); + } +}