diff --git a/README.md b/README.md index 7a30ec15..65f4695f 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ For the code of the current stable 0.4.x release, checkout the * [ExtEventLoop](#exteventloop) * [ExtLibeventLoop](#extlibeventloop) * [ExtLibevLoop](#extlibevloop) + * [ExtEvLoop](#extevloop) * [LoopInterface](#loopinterface) * [addTimer()](#addtimer) * [addPeriodicTimer()](#addperiodictimer) @@ -201,6 +202,16 @@ It supports the same backends as libevent. This loop is known to work with PHP 5.4 through PHP 7+. +#### ExtEvLoop + +An `ext-ev` based event loop. + +This loop uses the [`ev` PECL extension](https://pecl.php.net/package/ev), that +provides an interface to `libev` library. + +This loop is known to work with PHP 5.4 through PHP 7+. + + #### ExtLibeventLoop An `ext-libevent` based event loop. diff --git a/src/ExtEvLoop.php b/src/ExtEvLoop.php new file mode 100644 index 00000000..74db6d02 --- /dev/null +++ b/src/ExtEvLoop.php @@ -0,0 +1,252 @@ +loop = new EvLoop(); + $this->futureTickQueue = new FutureTickQueue(); + $this->timers = new SplObjectStorage(); + $this->signals = new SignalsHandler(); + } + + public function addReadStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->readStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::READ, $callback); + $this->readStreams[$key] = $event; + } + + /** + * @param resource $stream + * @param callable $listener + * + * @return \Closure + */ + private function getStreamListenerClosure($stream, $listener) + { + return function () use ($stream, $listener) { + call_user_func($listener, $stream); + }; + } + + public function addWriteStream($stream, $listener) + { + $key = (int)$stream; + + if (isset($this->writeStreams[$key])) { + return; + } + + $callback = $this->getStreamListenerClosure($stream, $listener); + $event = $this->loop->io($stream, Ev::WRITE, $callback); + $this->writeStreams[$key] = $event; + } + + public function removeReadStream($stream) + { + $key = (int)$stream; + + if (!isset($this->readStreams[$key])) { + return; + } + + $this->readStreams[$key]->stop(); + unset($this->readStreams[$key]); + } + + public function removeWriteStream($stream) + { + $key = (int)$stream; + + if (!isset($this->writeStreams[$key])) { + return; + } + + $this->writeStreams[$key]->stop(); + unset($this->writeStreams[$key]); + } + + public function addTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, false); + + $that = $this; + $timers = $this->timers; + $callback = function () use ($timer, $timers, $that) { + call_user_func($timer->getCallback(), $timer); + + if ($timers->contains($timer)) { + $that->cancelTimer($timer); + } + }; + + $event = $this->loop->timer($timer->getInterval(), 0.0, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function addPeriodicTimer($interval, $callback) + { + $timer = new Timer($interval, $callback, true); + + $callback = function () use ($timer) { + call_user_func($timer->getCallback(), $timer); + }; + + $event = $this->loop->timer($interval, $interval, $callback); + $this->timers->attach($timer, $event); + + return $timer; + } + + public function cancelTimer(TimerInterface $timer) + { + if (!isset($this->timers[$timer])) { + return; + } + + $event = $this->timers[$timer]; + $event->stop(); + $this->timers->detach($timer); + } + + public function futureTick($listener) + { + $this->futureTickQueue->add($listener); + } + + public function run() + { + $this->running = true; + + while ($this->running) { + $this->futureTickQueue->tick(); + + $hasPendingCallbacks = !$this->futureTickQueue->isEmpty(); + $wasJustStopped = !$this->running; + $nothingLeftToDo = !$this->readStreams + && !$this->writeStreams + && !$this->timers->count() + && $this->signals->isEmpty(); + + $flags = Ev::RUN_ONCE; + if ($wasJustStopped || $hasPendingCallbacks) { + $flags |= Ev::RUN_NOWAIT; + } elseif ($nothingLeftToDo) { + break; + } + + $this->loop->run($flags); + } + } + + public function stop() + { + $this->running = false; + } + + public function __destruct() + { + /** @var TimerInterface $timer */ + foreach ($this->timers as $timer) { + $this->cancelTimer($timer); + } + + foreach ($this->readStreams as $key => $stream) { + $this->removeReadStream($key); + } + + foreach ($this->writeStreams as $key => $stream) { + $this->removeWriteStream($key); + } + } + + public function addSignal($signal, $listener) + { + $this->signals->add($signal, $listener); + + if (!isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal] = $this->loop->signal($signal, function() use ($signal) { + $this->signals->call($signal); + }); + } + } + + public function removeSignal($signal, $listener) + { + $this->signals->remove($signal, $listener); + + if (isset($this->signalEvents[$signal])) { + $this->signalEvents[$signal]->stop(); + unset($this->signalEvents[$signal]); + } + } +} diff --git a/src/Factory.php b/src/Factory.php index 1a56877d..b46fc074 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -26,6 +26,8 @@ public static function create() // @codeCoverageIgnoreStart if (class_exists('libev\EventLoop', false)) { return new ExtLibevLoop(); + } elseif (class_exists('EvLoop', false)) { + return new ExtEvLoop(); } elseif (class_exists('EventBase', false)) { return new ExtEventLoop(); } elseif (function_exists('event_base_new') && PHP_VERSION_ID < 70000) { diff --git a/tests/ExtEvLoopTest.php b/tests/ExtEvLoopTest.php new file mode 100644 index 00000000..ab41c9f3 --- /dev/null +++ b/tests/ExtEvLoopTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/tests/Timer/AbstractTimerTest.php b/tests/Timer/AbstractTimerTest.php index 15202e41..294e683f 100644 --- a/tests/Timer/AbstractTimerTest.php +++ b/tests/Timer/AbstractTimerTest.php @@ -2,10 +2,14 @@ namespace React\Tests\EventLoop\Timer; +use React\EventLoop\LoopInterface; use React\Tests\EventLoop\TestCase; abstract class AbstractTimerTest extends TestCase { + /** + * @return LoopInterface + */ abstract public function createLoop(); public function testAddTimerReturnsNonPeriodicTimerInstance() diff --git a/tests/Timer/ExtEvTimerTest.php b/tests/Timer/ExtEvTimerTest.php new file mode 100644 index 00000000..bfa91861 --- /dev/null +++ b/tests/Timer/ExtEvTimerTest.php @@ -0,0 +1,17 @@ +markTestSkipped('ExtEvLoop tests skipped because ext-ev extension is not installed.'); + } + + return new ExtEvLoop(); + } +} diff --git a/travis-init.sh b/travis-init.sh index 06f853fe..29ce884a 100755 --- a/travis-init.sh +++ b/travis-init.sh @@ -5,9 +5,10 @@ set -o pipefail if [[ "$TRAVIS_PHP_VERSION" != "hhvm" && "$TRAVIS_PHP_VERSION" != "hhvm-nightly" ]]; then - # install 'event' PHP extension + # install 'event' and 'ev' PHP extension if [[ "$TRAVIS_PHP_VERSION" != "5.3" ]]; then echo "yes" | pecl install event + echo "yes" | pecl install ev fi # install 'libevent' PHP extension (does not support php 7) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy