1
0
mirror of https://github.com/danog/amp.git synced 2024-12-11 17:09:40 +01:00
amp/lib/Loop/Internal/TimerQueue.php
Aaron Piotrowski 4c8cd6b7b1
Revert DriverControl
Simplifies fiber running the loop. Control complexity seems unnecessary now since the loop should not need to be started by the user.
2020-10-02 22:26:10 -05:00

146 lines
4.1 KiB
PHP

<?php
namespace Amp\Loop\Internal;
use Amp\Loop\Watcher;
/**
* Uses a binary tree stored in an array to implement a heap.
*/
final class TimerQueue
{
/** @var TimerQueueEntry[] */
private array $data = [];
/** @var int[] */
private array $pointers = [];
/**
* Inserts the watcher into the queue. Time complexity: O(log(n)).
*
* @param Watcher $watcher
*
* @psalm-param Watcher<int> $watcher
*
* @return void
*/
public function insert(Watcher $watcher): void
{
\assert($watcher->expiration !== null);
\assert(!isset($this->pointers[$watcher->id]));
$entry = new TimerQueueEntry($watcher, $watcher->expiration);
$node = \count($this->data);
$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;
}
}
/**
* Removes the given watcher from the queue. Time complexity: O(log(n)).
*
* @param Watcher $watcher
*
* @psalm-param Watcher<int> $watcher
*
* @return void
*/
public function remove(Watcher $watcher): void
{
$id = $watcher->id;
if (!isset($this->pointers[$id])) {
return;
}
$this->removeAndRebuild($this->pointers[$id]);
}
/**
* Deletes and returns the Watcher on top of the heap if it has expired, otherwise null is returned.
* Time complexity: O(log(n)).
*
* @param int $now Current loop time.
*
* @return Watcher|null Expired watcher at the top of the heap or null if the watcher has not expired.
*
* @psalm-return Watcher<int>|null
*/
public function extract(int $now): ?Watcher
{
if (empty($this->data)) {
return null;
}
$data = $this->data[0];
if ($data->expiration > $now) {
return null;
}
$this->removeAndRebuild(0);
return $data->watcher;
}
/**
* Returns the expiration time value at the top of the heap. Time complexity: O(1).
*
* @return int|null Expiration time of the watcher at the top of the heap or null if the heap is empty.
*/
public function peek(): ?int
{
return isset($this->data[0]) ? $this->data[0]->expiration : null;
}
/**
* @param int $node Remove the given node and then rebuild the data array from that node downward.
*
* @return void
*/
private function removeAndRebuild(int $node): void
{
$length = \count($this->data) - 1;
$id = $this->data[$node]->watcher->id;
$left = $this->data[$node] = $this->data[$length];
$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;
}
$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;
}
}
}