diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index 17bbdac8..7944b4c1 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -3,8 +3,6 @@ namespace React\EventLoop\Timer; use React\EventLoop\TimerInterface; -use SplObjectStorage; -use SplPriorityQueue; /** * A scheduler implementation that can hold multiple timer instances @@ -17,14 +15,9 @@ final class Timers { private $time; - private $timers; - private $scheduler; - - public function __construct() - { - $this->timers = new SplObjectStorage(); - $this->scheduler = new SplPriorityQueue(); - } + private $timers = array(); + private $schedule = array(); + private $sorted = true; public function updateTime() { @@ -38,36 +31,32 @@ public function getTime() public function add(TimerInterface $timer) { - $interval = $timer->getInterval(); - $scheduledAt = $interval + microtime(true); - - $this->timers->attach($timer, $scheduledAt); - $this->scheduler->insert($timer, -$scheduledAt); + $id = spl_object_hash($timer); + $this->timers[$id] = $timer; + $this->schedule[$id] = $timer->getInterval() + microtime(true); + $this->sorted = false; } public function contains(TimerInterface $timer) { - return $this->timers->contains($timer); + return isset($this->timers[spl_oject_hash($timer)]); } public function cancel(TimerInterface $timer) { - $this->timers->detach($timer); + $id = spl_object_hash($timer); + unset($this->timers[$id], $this->schedule[$id]); } public function getFirst() { - while ($this->scheduler->count()) { - $timer = $this->scheduler->top(); - - if ($this->timers->contains($timer)) { - return $this->timers[$timer]; - } - - $this->scheduler->extract(); + // ensure timers are sorted to simply accessing next (first) one + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); } - return null; + return reset($this->schedule); } public function isEmpty() @@ -77,32 +66,34 @@ public function isEmpty() public function tick() { - $time = $this->updateTime(); - $timers = $this->timers; - $scheduler = $this->scheduler; - - while (!$scheduler->isEmpty()) { - $timer = $scheduler->top(); + // ensure timers are sorted so we can execute in order + if (!$this->sorted) { + $this->sorted = true; + asort($this->schedule); + } - if (!isset($timers[$timer])) { - $scheduler->extract(); - $timers->detach($timer); + $time = $this->updateTime(); - continue; + foreach ($this->schedule as $id => $scheduled) { + // schedule is ordered, so loop until first timer that is not scheduled for execution now + if ($scheduled >= $time) { + break; } - if ($timers[$timer] >= $time) { - break; + // skip any timers that are removed while we process the current schedule + if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) { + continue; } - $scheduler->extract(); + $timer = $this->timers[$id]; call_user_func($timer->getCallback(), $timer); - if ($timer->isPeriodic() && isset($timers[$timer])) { - $timers[$timer] = $scheduledAt = $timer->getInterval() + $time; - $scheduler->insert($timer, -$scheduledAt); + // re-schedule if this is a periodic timer and it has not been cancelled explicitly already + if ($timer->isPeriodic() && isset($this->timers[$id])) { + $this->schedule[$id] = $timer->getInterval() + $time; + $this->sorted = false; } else { - $timers->detach($timer); + unset($this->timers[$id], $this->schedule[$id]); } } }