Skip to content

Commit 9b3cc65

Browse files
author
Greg Bowler
committed
Functionality complete Loop, ready to refactor using Iterator
1 parent 03ec98a commit 9b3cc65

3 files changed

Lines changed: 66 additions & 18 deletions

File tree

src/Loop.php

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public function waitUntil(float $waitUntilEpoch):void {
5959

6060
// TODO: The epochList is a perfect candidate for one of SPL's Iterators.
6161
// Probably the MultipleIterator...
62-
public function getTimerOrder() {
62+
/** @return array[] */
63+
public function getTimerOrder():array {
6364
$epochList = [];
6465

6566
// Create a list of all timers that have a next run time.
@@ -78,6 +79,13 @@ public function getTimerOrder() {
7879
return $epochList;
7980
}
8081

82+
/** return array[] */
83+
public function getReadyTimers(array $epochList):array {
84+
$now = microtime(true);
85+
$epochList = array_filter($epochList, fn($a) => $a[0] <= $now);
86+
return $epochList;
87+
}
88+
8189
private function triggerNextTimers():int {
8290
$epochList = $this->getTimerOrder();
8391
// If there are no more timers to run, return early.
@@ -88,33 +96,23 @@ private function triggerNextTimers():int {
8896
// Wait until the first epoch is due, then trigger the timer.
8997
$this->waitUntil($epochList[0][0]);
9098
$this->trigger($epochList[0][1]);
91-
$triggered = 1;
9299

93100
// Triggering the timer may have caused time to pass so that
94101
// other timers are now due.
95102
array_shift($epochList);
96-
$triggered += $this->executeAllReadyTimers($epochList);
97-
return $triggered;
103+
$readyList = $this->getReadyTimers($epochList);
104+
$this->executeTimers($readyList);
105+
return 1 + count($readyList);
98106
}
99107

100108
private function trigger(Timer $timer):void {
101109
$timer->tick();
102110
}
103111

104112
/** @param array[] $epochList [$epoch, $timer] */
105-
private function executeAllReadyTimers(array $epochList):int {
106-
$now = microtime(true);
107-
$triggered = 0;
108-
109-
while(isset($epochList[0])
110-
&& $epochList[0][0] <= $now) {
111-
$this->trigger($epochList[0][1]);
112-
array_shift($epochList);
113-
$triggered++;
114-
115-
$now = microtime(true);
113+
private function executeTimers(array $epochList):void {
114+
foreach($epochList as $timerThing) {
115+
$this->trigger($timerThing[1]);
116116
}
117-
118-
return $triggered;
119117
}
120118
}

src/Timer/Timer.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
* use of CPU cycles, sleep for the duration until the Timer's next run time.
1010
*/
1111
abstract class Timer {
12+
/** @var float[] */
1213
protected array $triggerTimeQueue;
14+
/** @var callable[] */
1315
protected array $callbackList;
1416

1517
public function __construct() {
@@ -43,7 +45,7 @@ public function tick():bool {
4345
array_shift($this->triggerTimeQueue);
4446
}
4547
while(isset($this->triggerTimeQueue[0])
46-
&& $this->triggerTimeQueue <= $now);
48+
&& $this->triggerTimeQueue[0] <= $now);
4749

4850
return true;
4951
}

test/phpunit/LoopTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,52 @@ public function testGetTimerListOutOfOrder() {
137137
self::assertSame($timer2, $timerOrder[0][1]);
138138
self::assertSame($timer1, $timerOrder[1][1]);
139139
}
140+
141+
public function testGetTimerListManyReadyManyFuture() {
142+
$epoch = microtime(true);
143+
144+
$timerList = [];
145+
146+
for($i = 0; $i < 100; $i++) {
147+
// Offset the epoch by a random amount between -10 and +10 seconds.
148+
$rand = rand(-1000, 1000) / 100;
149+
$timer = self::createMock(Timer::class);
150+
$timer->method("getNextRunTime")
151+
->willReturn($epoch + $rand);
152+
$timerList[] = $timer;
153+
}
154+
155+
$sut = new Loop();
156+
$sut->setSleepFunction(function() {});
157+
158+
foreach($timerList as $timer) {
159+
$sut->addTimer($timer);
160+
}
161+
162+
$timerOrder = $sut->getTimerOrder();
163+
$earliest = 0;
164+
foreach($timerOrder as $timer) {
165+
self::assertGreaterThanOrEqual($earliest, $timer[0]);
166+
$earliest = $timer[0];
167+
}
168+
}
169+
170+
public function testGetReadyTimers() {
171+
$epoch = microtime(true);
172+
173+
$timerFuture = self::createMock(Timer::class);
174+
$timerFuture->method("getNextRunTime")
175+
->willReturn($epoch + 100);
176+
$timerPast = self::createMock(Timer::class);
177+
$timerPast->method("getNextRunTime")
178+
->willReturn($epoch - 100);
179+
$sut = new Loop();
180+
$sut->addTimer($timerFuture);
181+
$sut->addTimer($timerPast);
182+
$allTimers = $sut->getTimerOrder();
183+
$readyTimers = $sut->getReadyTimers($allTimers);
184+
185+
self::assertCount(1, $readyTimers);
186+
self::assertSame($timerPast, $readyTimers[0][1]);
187+
}
140188
}

0 commit comments

Comments
 (0)