33namespace React\EventLoop\Timer;
44
55use React\EventLoop\TimerInterface;
6- use SplObjectStorage;
7- use SplPriorityQueue;
86
97/**
108 * A scheduler implementation that can hold multiple timer instances
1715final class Timers
1816{
1917 private $time;
20- private $timers;
21- private $scheduler;
22-
23- public function __construct()
24- {
25- $this->timers = new SplObjectStorage();
26- $this->scheduler = new SplPriorityQueue();
27- }
18+ private $timers = array();
19+ private $schedule = array();
20+ private $sorted = true;
2821
2922 public function updateTime()
3023 {
@@ -38,36 +31,32 @@ public function getTime()
3831
3932 public function add(TimerInterface $timer)
4033 {
41- $interval = $timer->getInterval();
42- $scheduledAt = $interval + microtime(true);
43-
44- $this->timers->attach($timer, $scheduledAt);
45- $this->scheduler->insert($timer, -$scheduledAt);
34+ $id = spl_object_hash($timer);
35+ $this->timers[$id] = $timer;
36+ $this->schedule[$id] = $timer->getInterval() + microtime(true);
37+ $this->sorted = false;
4638 }
4739
4840 public function contains(TimerInterface $timer)
4941 {
50- return $this->timers->contains ($timer);
42+ return isset( $this->timers[spl_oject_hash ($timer)] );
5143 }
5244
5345 public function cancel(TimerInterface $timer)
5446 {
55- $this->timers->detach($timer);
47+ $id = spl_object_hash($timer);
48+ unset($this->timers[$id], $this->schedule[$id]);
5649 }
5750
5851 public function getFirst()
5952 {
60- while ($this->scheduler->count()) {
61- $timer = $this->scheduler->top();
62-
63- if ($this->timers->contains($timer)) {
64- return $this->timers[$timer];
65- }
66-
67- $this->scheduler->extract();
53+ // ensure timers are sorted to simply accessing next (first) one
54+ if (!$this->sorted) {
55+ $this->sorted = true;
56+ asort($this->schedule);
6857 }
6958
70- return null ;
59+ return reset($this->schedule) ;
7160 }
7261
7362 public function isEmpty()
@@ -77,32 +66,34 @@ public function isEmpty()
7766
7867 public function tick()
7968 {
80- $time = $this->updateTime();
81- $timers = $this->timers;
82- $scheduler = $this->scheduler;
83-
84- while (!$scheduler->isEmpty()) {
85- $timer = $scheduler->top();
69+ // ensure timers are sorted so we can execute in order
70+ if (!$this->sorted) {
71+ $this->sorted = true;
72+ asort($this->schedule);
73+ }
8674
87- if (!isset($timers[$timer])) {
88- $scheduler->extract();
89- $timers->detach($timer);
75+ $time = $this->updateTime();
9076
91- continue;
77+ foreach ($this->schedule as $id => $scheduled) {
78+ // schedule is ordered, so loop until first timer that is not scheduled for execution now
79+ if ($scheduled >= $time) {
80+ break;
9281 }
9382
94- if ($timers[$timer] >= $time) {
95- break;
83+ // skip any timers that are removed while we process the current schedule
84+ if (!isset($this->schedule[$id]) || $this->schedule[$id] !== $scheduled) {
85+ continue;
9686 }
9787
98- $scheduler->extract() ;
88+ $timer = $this->timers[$id] ;
9989 call_user_func($timer->getCallback(), $timer);
10090
101- if ($timer->isPeriodic() && isset($timers[$timer])) {
102- $timers[$timer] = $scheduledAt = $timer->getInterval() + $time;
103- $scheduler->insert($timer, -$scheduledAt);
91+ // re-schedule if this is a periodic timer and it has not been cancelled explicitly already
92+ if ($timer->isPeriodic() && isset($this->timers[$id])) {
93+ $this->schedule[$id] = $timer->getInterval() + $time;
94+ $this->sorted = false;
10495 } else {
105- $ timers->detach($timer );
96+ unset($this-> timers[$id], $this->schedule[$id] );
10697 }
10798 }
10899 }
0 commit comments