diff --git a/README.md b/README.md index d67ad534..16f8d8a1 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,131 @@ All of the loops support these features: * Periodic timers * Deferred execution on future loop tick +### addTimer() + +The `addTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked once after the given interval. + +The timer callback function MUST be able to accept a single parameter, +the timer instance as also returned by this method or you MAY use a +function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure +the callback will be invoked only once after the given interval. +You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + +```php +$loop->addTimer(0.8, function () { + echo 'world!' . PHP_EOL; +}); + +$loop->addTimer(0.3, function () { + echo 'hello '; +}); +``` + +See also [example #1](examples). + +If you want to access any variables within your callback function, you +can bind arbitrary data to a callback closure like this: + +```php +function hello(LoopInterface $loop, $name) +{ + $loop->addTimer(1.0, function () use ($name) { + echo "hello $name\n"; + }); +} + +hello('Tester'); +``` + +The execution order of timers scheduled to execute at the same time is +not guaranteed. + +### addPeriodicTimer() + +The `addPeriodicTimer(float $interval, callable $callback): TimerInterface` method can be used to +enqueue a callback to be invoked repeatedly after the given interval. + +The timer callback function MUST be able to accept a single parameter, +the timer instance as also returned by this method or you MAY use a +function which has no parameters at all. + +The timer callback function MUST NOT throw an `Exception`. +The return value of the timer callback function will be ignored and has +no effect, so for performance reasons you're recommended to not return +any excessive data structures. + +Unlike [`addTimer()`](#addtimer), this method will ensure the the +callback will be invoked infinitely after the given interval or until you +invoke [`cancelTimer`](#canceltimer). + +```php +$timer = $loop->addPeriodicTimer(0.1, function () { + echo 'tick!' . PHP_EOL; +}); + +$loop->addTimer(1.0, function () use ($loop, $timer) { + $loop->cancelTimer($timer); + echo 'Done' . PHP_EOL; +}); +``` + +See also [example #2](examples). + +If you want to limit the number of executions, you can bind +arbitrary data to a callback closure like this: + +```php +function hello(LoopInterface $loop, $name) +{ + $n = 3; + $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + if ($n > 0) { + --$n; + echo "hello $name\n"; + } else { + $loop->cancelTimer($timer); + } + }); +} + +hello('Tester'); +``` + +The execution order of timers scheduled to execute at the same time is +not guaranteed. + +### cancelTimer() + +The `cancelTimer(TimerInterface $timer): void` method can be used to +cancel a pending timer. + +See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + +You can use the [`isTimerActive()`](#istimeractive) method to check if +this timer is still "active". After a timer is successfully canceled, +it is no longer considered "active". + +Calling this method on a timer instance that has not been added to this +loop instance or on a timer + +### isTimerActive() + +The `isTimerActive(TimerInterface $timer): bool` method can be used to +check if a given timer is active. + +A timer is considered "active" if it has been added to this loop instance +via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) +and has not been canceled via [`cancelTimer()`](#canceltimer) and is not +a non-periodic timer that has already been triggered after its interval. + ### futureTick() The `futureTick(callable $listener): void` method can be used to diff --git a/src/ExtEventLoop.php b/src/ExtEventLoop.php index d94e65db..244ca87d 100644 --- a/src/ExtEventLoop.php +++ b/src/ExtEventLoop.php @@ -112,7 +112,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->scheduleTimer($timer); @@ -124,7 +124,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->scheduleTimer($timer); diff --git a/src/LibEvLoop.php b/src/LibEvLoop.php index 929d2eb6..b4e7102c 100644 --- a/src/LibEvLoop.php +++ b/src/LibEvLoop.php @@ -108,7 +108,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer( $interval, $callback, false); $callback = function () use ($timer) { call_user_func($timer->getCallback(), $timer); @@ -130,7 +130,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $callback = function () use ($timer) { call_user_func($timer->getCallback(), $timer); diff --git a/src/LibEventLoop.php b/src/LibEventLoop.php index b2c771cf..af7e7943 100644 --- a/src/LibEventLoop.php +++ b/src/LibEventLoop.php @@ -116,7 +116,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->scheduleTimer($timer); @@ -128,7 +128,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->scheduleTimer($timer); diff --git a/src/LoopInterface.php b/src/LoopInterface.php index c2e5f75c..4290d2a8 100644 --- a/src/LoopInterface.php +++ b/src/LoopInterface.php @@ -46,6 +46,45 @@ public function removeStream($stream); /** * Enqueue a callback to be invoked once after the given interval. * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addPeriodicTimer()`](#addperiodictimer), this method will ensure + * the callback will be invoked only once after the given interval. + * You can invoke [`cancelTimer`](#canceltimer) to cancel a pending timer. + * + * ```php + * $loop->addTimer(0.8, function () { + * echo 'world!' . PHP_EOL; + * }); + * + * $loop->addTimer(0.3, function () { + * echo 'hello '; + * }); + * ``` + * + * See also [example #1](examples). + * + * If you want to access any variables within your callback function, you + * can bind arbitrary data to a callback closure like this: + * + * ```php + * function hello(LoopInterface $loop, $name) + * { + * $loop->addTimer(1.0, function () use ($name) { + * echo "hello $name\n"; + * }); + * } + * + * hello('Tester'); + * ``` + * * The execution order of timers scheduled to execute at the same time is * not guaranteed. * @@ -59,6 +98,52 @@ public function addTimer($interval, callable $callback); /** * Enqueue a callback to be invoked repeatedly after the given interval. * + * The timer callback function MUST be able to accept a single parameter, + * the timer instance as also returned by this method or you MAY use a + * function which has no parameters at all. + * + * The timer callback function MUST NOT throw an `Exception`. + * The return value of the timer callback function will be ignored and has + * no effect, so for performance reasons you're recommended to not return + * any excessive data structures. + * + * Unlike [`addTimer()`](#addtimer), this method will ensure the the + * callback will be invoked infinitely after the given interval or until you + * invoke [`cancelTimer`](#canceltimer). + * + * ```php + * $timer = $loop->addPeriodicTimer(0.1, function () { + * echo 'tick!' . PHP_EOL; + * }); + * + * $loop->addTimer(1.0, function () use ($loop, $timer) { + * $loop->cancelTimer($timer); + * echo 'Done' . PHP_EOL; + * }); + * ``` + * + * See also [example #2](examples). + * + * If you want to limit the number of executions, you can bind + * arbitrary data to a callback closure like this: + * + * ```php + * function hello(LoopInterface $loop, $name) + * { + * $n = 3; + * $loop->addPeriodicTimer(1.0, function ($timer) use ($name, $loop, &$n) { + * if ($n > 0) { + * --$n; + * echo "hello $name\n"; + * } else { + * $loop->cancelTimer($timer); + * } + * }); + * } + * + * hello('Tester'); + * ``` + * * The execution order of timers scheduled to execute at the same time is * not guaranteed. * @@ -72,13 +157,30 @@ public function addPeriodicTimer($interval, callable $callback); /** * Cancel a pending timer. * + * See also [`addPeriodicTimer()`](#addperiodictimer) and [example #2](examples). + * + * You can use the [`isTimerActive()`](#istimeractive) method to check if + * this timer is still "active". After a timer is successfully canceled, + * it is no longer considered "active". + * + * Calling this method on a timer instance that has not been added to this + * loop instance or on a timer that is not "active" (or has already been + * canceled) has no effect. + * * @param TimerInterface $timer The timer to cancel. + * + * @return void */ public function cancelTimer(TimerInterface $timer); /** * Check if a given timer is active. * + * A timer is considered "active" if it has been added to this loop instance + * via [`addTimer()`](#addtimer) or [`addPeriodicTimer()`](#addperiodictimer) + * and has not been canceled via [`cancelTimer()`](#canceltimer) and is not + * a non-periodic timer that has already been triggered after its interval. + * * @param TimerInterface $timer The timer to check. * * @return boolean True if the timer is still enqueued for execution. diff --git a/src/StreamSelectLoop.php b/src/StreamSelectLoop.php index 92fc5787..80916032 100644 --- a/src/StreamSelectLoop.php +++ b/src/StreamSelectLoop.php @@ -94,7 +94,7 @@ public function removeStream($stream) */ public function addTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, false); + $timer = new Timer($interval, $callback, false); $this->timers->add($timer); @@ -106,7 +106,7 @@ public function addTimer($interval, callable $callback) */ public function addPeriodicTimer($interval, callable $callback) { - $timer = new Timer($this, $interval, $callback, true); + $timer = new Timer($interval, $callback, true); $this->timers->add($timer); diff --git a/src/Timer/Timer.php b/src/Timer/Timer.php index f670ab3c..433f5be2 100644 --- a/src/Timer/Timer.php +++ b/src/Timer/Timer.php @@ -2,46 +2,38 @@ namespace React\EventLoop\Timer; -use React\EventLoop\LoopInterface; - -class Timer implements TimerInterface +/** + * The actual connection implementation for TimerInterface + * + * This class should only be used internally, see TimerInterface instead. + * + * @see TimerInterface + * @internal + */ +final class Timer implements TimerInterface { const MIN_INTERVAL = 0.000001; - protected $loop; - protected $interval; - protected $callback; - protected $periodic; - protected $data; + private $interval; + private $callback; + private $periodic; /** * Constructor initializes the fields of the Timer * - * @param LoopInterface $loop The loop with which this timer is associated * @param float $interval The interval after which this timer will execute, in seconds * @param callable $callback The callback that will be executed when this timer elapses * @param bool $periodic Whether the time is periodic - * @param mixed $data Arbitrary data associated with timer */ - public function __construct(LoopInterface $loop, $interval, callable $callback, $periodic = false, $data = null) + public function __construct($interval, callable $callback, $periodic = false) { if ($interval < self::MIN_INTERVAL) { $interval = self::MIN_INTERVAL; } - $this->loop = $loop; $this->interval = (float) $interval; $this->callback = $callback; $this->periodic = (bool) $periodic; - $this->data = null; - } - - /** - * {@inheritdoc} - */ - public function getLoop() - { - return $this->loop; } /** @@ -60,22 +52,6 @@ public function getCallback() return $this->callback; } - /** - * {@inheritdoc} - */ - public function setData($data) - { - $this->data = $data; - } - - /** - * {@inheritdoc} - */ - public function getData() - { - return $this->data; - } - /** * {@inheritdoc} */ @@ -83,20 +59,4 @@ public function isPeriodic() { return $this->periodic; } - - /** - * {@inheritdoc} - */ - public function isActive() - { - return $this->loop->isTimerActive($this); - } - - /** - * {@inheritdoc} - */ - public function cancel() - { - $this->loop->cancelTimer($this); - } } diff --git a/src/Timer/TimerInterface.php b/src/Timer/TimerInterface.php index d066f369..cf9028f9 100644 --- a/src/Timer/TimerInterface.php +++ b/src/Timer/TimerInterface.php @@ -2,17 +2,8 @@ namespace React\EventLoop\Timer; -use React\EventLoop\LoopInterface; - interface TimerInterface { - /** - * Get the loop with which this timer is associated - * - * @return LoopInterface - */ - public function getLoop(); - /** * Get the interval after which this timer will execute, in seconds * @@ -27,36 +18,10 @@ public function getInterval(); */ public function getCallback(); - /** - * Set arbitrary data associated with timer - * - * @param mixed $data - */ - public function setData($data); - - /** - * Get arbitrary data associated with timer - * - * @return mixed - */ - public function getData(); - /** * Determine whether the time is periodic * * @return bool */ public function isPeriodic(); - - /** - * Determine whether the time is active - * - * @return bool - */ - public function isActive(); - - /** - * Cancel this timer - */ - public function cancel(); } diff --git a/src/Timer/Timers.php b/src/Timer/Timers.php index bebe5520..81a21735 100644 --- a/src/Timer/Timers.php +++ b/src/Timer/Timers.php @@ -5,7 +5,15 @@ use SplObjectStorage; use SplPriorityQueue; -class Timers +/** + * A scheduler implementation that can hold multiple timer instances + * + * This class should only be used internally, see TimerInterface instead. + * + * @see TimerInterface + * @internal + */ +final class Timers { private $time; private $timers; diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index e930ad37..dc32a577 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -43,7 +43,7 @@ public function testAddPeriodicTimerWithCancel() usleep(1000); $this->tickLoop($loop); - $timer->cancel(); + $loop->cancelTimer($timer); usleep(1000); $this->tickLoop($loop); @@ -55,11 +55,11 @@ public function testAddPeriodicTimerCancelsItself() $loop = $this->createLoop(); - $loop->addPeriodicTimer(0.001, function ($timer) use (&$i) { + $loop->addPeriodicTimer(0.001, function ($timer) use (&$i, $loop) { $i++; if ($i == 2) { - $timer->cancel(); + $loop->cancelTimer($timer); } }); @@ -81,7 +81,7 @@ public function testIsTimerActive() $this->assertTrue($loop->isTimerActive($timer)); - $timer->cancel(); + $loop->cancelTimer($timer); $this->assertFalse($loop->isTimerActive($timer)); } diff --git a/tests/Timer/TimersTest.php b/tests/Timer/TimersTest.php index 0ca87e16..c70a39cd 100644 --- a/tests/Timer/TimersTest.php +++ b/tests/Timer/TimersTest.php @@ -13,14 +13,14 @@ public function testBlockedTimer() $loop = $this ->getMockBuilder('React\EventLoop\LoopInterface') ->getMock(); - + $timers = new Timers(); $timers->tick(); - + // simulate a bunch of processing on stream events, // part of which schedules a future timer... sleep(1); - $timers->add(new Timer($loop, 0.5, function () { + $timers->add(new Timer(0.5, function () { $this->fail("Timer shouldn't be called"); }));