1
0
mirror of https://github.com/danog/amp.git synced 2024-11-30 04:29:08 +01:00

Add Loop::now()

This commit is contained in:
Aaron Piotrowski 2018-01-05 20:32:57 -06:00 committed by Niklas Keller
parent 96c2eeaa1f
commit 77a12c823b
8 changed files with 157 additions and 13 deletions

View File

@ -332,6 +332,18 @@ final class Loop
self::$driver->unreference($watcherId); self::$driver->unreference($watcherId);
} }
/**
* Returns the current loop time in millisecond increments. Note this value does not necessarily correlate to
* wall-clock time, rather the value returned is meant to be used in relative comparisons to prior values returned
* by this method (intervals, expiration calculations, etc.) and is only updated once per loop tick.
*
* @return int
*/
public static function now(): int
{
return self::$driver->now();
}
/** /**
* Stores information in the loop bound registry. * Stores information in the loop bound registry.
* *

View File

@ -590,6 +590,20 @@ abstract class Driver
($this->errorHandler)($exception); ($this->errorHandler)($exception);
} }
/**
* Returns the current loop time in millisecond increments. Note this value does not necessarily correlate to
* wall-clock time, rather the value returned is meant to be used in relative comparisons to prior values returned
* by this method (intervals, expiration calculations, etc.) and is only updated once per loop tick.
*
* Extending classes should override this function to return a value cached once per loop tick.
*
* @return int
*/
public function now(): int
{
return \microtime(true) * self::MILLISEC_PER_SEC;
}
/** /**
* Get the underlying loop handle. * Get the underlying loop handle.
* *

View File

@ -11,22 +11,38 @@ class EvDriver extends Driver
{ {
/** @var \EvSignal[]|null */ /** @var \EvSignal[]|null */
private static $activeSignals; private static $activeSignals;
/** @var \EvLoop */ /** @var \EvLoop */
private $handle; private $handle;
/** @var \EvWatcher[] */ /** @var \EvWatcher[] */
private $events = []; private $events = [];
/** @var callable */ /** @var callable */
private $ioCallback; private $ioCallback;
/** @var callable */ /** @var callable */
private $timerCallback; private $timerCallback;
/** @var callable */ /** @var callable */
private $signalCallback; private $signalCallback;
/** @var \EvSignal[] */ /** @var \EvSignal[] */
private $signals = []; private $signals = [];
/** @var int Internal timestamp for now. */
private $now = 0;
/** @var bool */
private $nowUpdateNeeded = false;
/** @var int Loop time offset from microtime() */
private $nowOffset;
public function __construct() public function __construct()
{ {
$this->handle = new \EvLoop; $this->handle = new \EvLoop;
$this->nowOffset = (int) (\microtime(true) * self::MILLISEC_PER_SEC);
if (self::$activeSignals === null) { if (self::$activeSignals === null) {
self::$activeSignals = &$this->signals; self::$activeSignals = &$this->signals;
@ -179,6 +195,19 @@ class EvDriver extends Driver
parent::stop(); parent::stop();
} }
/**
* {@inheritdoc}
*/
public function now(): int
{
if ($this->nowUpdateNeeded) {
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC) - $this->nowOffset;
$this->nowUpdateNeeded = false;
}
return $this->now;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -192,6 +221,7 @@ class EvDriver extends Driver
*/ */
protected function dispatch(bool $blocking) protected function dispatch(bool $blocking)
{ {
$this->nowUpdateNeeded = true;
$this->handle->run($blocking ? \Ev::RUN_ONCE : \Ev::RUN_ONCE | \Ev::RUN_NOWAIT); $this->handle->run($blocking ? \Ev::RUN_ONCE : \Ev::RUN_ONCE | \Ev::RUN_NOWAIT);
} }

View File

@ -11,25 +11,38 @@ class EventDriver extends Driver
{ {
/** @var \Event[]|null */ /** @var \Event[]|null */
private static $activeSignals; private static $activeSignals;
/** @var \EventBase */ /** @var \EventBase */
private $handle; private $handle;
/** @var \Event[] */ /** @var \Event[] */
private $events = []; private $events = [];
/** @var callable */ /** @var callable */
private $ioCallback; private $ioCallback;
/** @var callable */ /** @var callable */
private $timerCallback; private $timerCallback;
/** @var callable */ /** @var callable */
private $signalCallback; private $signalCallback;
/** @var \Event[] */ /** @var \Event[] */
private $signals = []; private $signals = [];
/** @var bool */
private $nowUpdateNeeded = false;
/** @var int Internal timestamp for now. */ /** @var int Internal timestamp for now. */
private $now; private $now = 0;
/** @var int Loop time offset from microtime() */
private $nowOffset;
public function __construct() public function __construct()
{ {
$this->handle = new \EventBase; $this->handle = new \EventBase;
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC); $this->nowOffset = (int) (\microtime(true) * self::MILLISEC_PER_SEC);
if (self::$activeSignals === null) { if (self::$activeSignals === null) {
self::$activeSignals = &$this->signals; self::$activeSignals = &$this->signals;
@ -184,6 +197,19 @@ class EventDriver extends Driver
parent::stop(); parent::stop();
} }
/**
* {@inheritdoc}
*/
public function now(): int
{
if ($this->nowUpdateNeeded) {
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC) - $this->nowOffset;
$this->nowUpdateNeeded = false;
}
return $this->now;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -197,8 +223,8 @@ class EventDriver extends Driver
*/ */
protected function dispatch(bool $blocking) protected function dispatch(bool $blocking)
{ {
$this->nowUpdateNeeded = true;
$this->handle->loop($blocking ? \EventBase::LOOP_ONCE : \EventBase::LOOP_ONCE | \EventBase::LOOP_NONBLOCK); $this->handle->loop($blocking ? \EventBase::LOOP_ONCE : \EventBase::LOOP_ONCE | \EventBase::LOOP_NONBLOCK);
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC);
} }
/** /**
@ -206,7 +232,7 @@ class EventDriver extends Driver
*/ */
protected function activate(array $watchers) protected function activate(array $watchers)
{ {
$now = (int) (\microtime(true) * self::MILLISEC_PER_SEC); $now = (int) (\microtime(true) * self::MILLISEC_PER_SEC) - $this->nowOffset;
foreach ($watchers as $watcher) { foreach ($watchers as $watcher) {
if (!isset($this->events[$id = $watcher->id])) { if (!isset($this->events[$id = $watcher->id])) {
@ -262,7 +288,7 @@ class EventDriver extends Driver
switch ($watcher->type) { switch ($watcher->type) {
case Watcher::DELAY: case Watcher::DELAY:
case Watcher::REPEAT: case Watcher::REPEAT:
$interval = $watcher->value - ($now - $this->now); $interval = $watcher->value - ($now - $this->now());
$this->events[$id]->add($interval > 0 ? $interval / self::MILLISEC_PER_SEC : 0); $this->events[$id]->add($interval > 0 ? $interval / self::MILLISEC_PER_SEC : 0);
break; break;

View File

@ -30,8 +30,14 @@ class NativeDriver extends Driver
/** @var \Amp\Loop\Watcher[][] */ /** @var \Amp\Loop\Watcher[][] */
private $signalWatchers = []; private $signalWatchers = [];
/** @var bool */
private $nowUpdateNeeded = false;
/** @var int Internal timestamp for now. */ /** @var int Internal timestamp for now. */
private $now; private $now = 0;
/** @var int Loop time offset from microtime() */
private $nowOffset;
/** @var bool */ /** @var bool */
private $signalHandling; private $signalHandling;
@ -40,7 +46,7 @@ class NativeDriver extends Driver
{ {
$this->timerQueue = new \SplPriorityQueue(); $this->timerQueue = new \SplPriorityQueue();
$this->signalHandling = \extension_loaded("pcntl"); $this->signalHandling = \extension_loaded("pcntl");
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC); $this->nowOffset = (int) (\microtime(true) * self::MILLISEC_PER_SEC);
} }
/** /**
@ -57,6 +63,19 @@ class NativeDriver extends Driver
return parent::onSignal($signo, $callback, $data); return parent::onSignal($signo, $callback, $data);
} }
/**
* {@inheritdoc}
*/
public function now(): int
{
if ($this->nowUpdateNeeded) {
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC) - $this->nowOffset;
$this->nowUpdateNeeded = false;
}
return $this->now;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -67,14 +86,14 @@ class NativeDriver extends Driver
protected function dispatch(bool $blocking) protected function dispatch(bool $blocking)
{ {
$this->nowUpdateNeeded = true;
$this->selectStreams( $this->selectStreams(
$this->readStreams, $this->readStreams,
$this->writeStreams, $this->writeStreams,
$blocking ? $this->getTimeout() : 0 $blocking ? $this->getTimeout() : 0
); );
$this->now = (int) (\microtime(true) * self::MILLISEC_PER_SEC);
if (!empty($this->timerExpires)) { if (!empty($this->timerExpires)) {
$scheduleQueue = []; $scheduleQueue = [];
@ -89,14 +108,14 @@ class NativeDriver extends Driver
continue; continue;
} }
if ($this->timerExpires[$id] > $this->now) { // Timer at top of queue has not expired. if ($this->timerExpires[$id] > $this->now()) { // Timer at top of queue has not expired.
break; break;
} }
$this->timerQueue->extract(); $this->timerQueue->extract();
if ($watcher->type & Watcher::REPEAT) { if ($watcher->type & Watcher::REPEAT) {
$expiration = $this->now + $watcher->value; $expiration = $this->now() + $watcher->value;
$this->timerExpires[$watcher->id] = $expiration; $this->timerExpires[$watcher->id] = $expiration;
$scheduleQueue[] = [$watcher, $expiration]; $scheduleQueue[] = [$watcher, $expiration];
} else { } else {
@ -283,7 +302,7 @@ class NativeDriver extends Driver
case Watcher::DELAY: case Watcher::DELAY:
case Watcher::REPEAT: case Watcher::REPEAT:
$expiration = $this->now + $watcher->value; $expiration = $this->now() + $watcher->value;
$this->timerExpires[$watcher->id] = $expiration; $this->timerExpires[$watcher->id] = $expiration;
$this->timerQueue->insert([$watcher, $expiration], -$expiration); $this->timerQueue->insert([$watcher, $expiration], -$expiration);
break; break;

View File

@ -166,6 +166,14 @@ class UvDriver extends Driver
return \extension_loaded("uv"); return \extension_loaded("uv");
} }
/**
* {@inheritdoc}
*/
public function now(): int
{
return \uv_now($this->handle);
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -1555,6 +1555,23 @@ abstract class DriverTest extends TestCase
$this->assertNotSame(0, $j); $this->assertNotSame(0, $j);
} }
public function testNow()
{
$now = $this->loop->now();
$this->loop->delay(100, function () use ($now) {
$now += 100;
$new = $this->loop->now();
// Allow a few milliseconds of inaccuracy.
$this->assertGreaterThanOrEqual($now - 5, $new);
$this->assertLessThanOrEqual($now + 5, $new);
// Same time should be returned from later call.
$this->assertSame($new, $this->loop->now());
});
$this->loop->run();
}
public function testBug163ConsecutiveDelayed() public function testBug163ConsecutiveDelayed()
{ {
$emits = 3; $emits = 3;

View File

@ -46,12 +46,30 @@ class LoopTest extends TestCase
}); });
} }
public function testNow()
{
Loop::run(function () {
$now = Loop::now();
Loop::delay(100, function () use ($now) {
$now += 100;
$new = Loop::now();
// Allow a few milliseconds of inaccuracy.
$this->assertGreaterThanOrEqual($now - 5, $new);
$this->assertLessThanOrEqual($now + 5, $new);
// Same time should be returned from later call.
$this->assertSame($new, Loop::now());
});
});
}
public function testGet() public function testGet()
{ {
$this->assertInstanceOf(Loop\Driver::class, Loop::get()); $this->assertInstanceOf(Loop\Driver::class, Loop::get());
} }
public function testGetInto() public function testGetInfo()
{ {
$this->assertSame(Loop::get()->getInfo(), Loop::getInfo()); $this->assertSame(Loop::get()->getInfo(), Loop::getInfo());
} }