├── logo.png ├── logo-icon.png ├── .gitignore ├── README.md ├── src ├── Exception.php ├── TypeException.php ├── LoopInitializedException.php ├── Timers │ ├── Timer.php │ └── TimerDevice.php ├── EventDeviceInterface.php ├── PosixSignalEventDevice.php ├── functions.php ├── LoopInterface.php ├── EventDeviceBinder.php ├── StreamEventDevice.php ├── Loop.php └── NativeLoop.php ├── CONTRIBUTING.md ├── composer.json ├── phpunit.xml.dist ├── tests ├── EventDeviceBinderTest.php ├── LoopTest.php └── NativeLoopTest.php ├── .scrutinizer.yml ├── LICENSE.md └── logo.svg /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagebind/evflow/HEAD/logo.png -------------------------------------------------------------------------------- /logo-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sagebind/evflow/HEAD/logo-icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore dependencies 2 | vendor/ 3 | 4 | # ignore lock file 5 | composer.lock 6 | 7 | # ignore api documentation cache and build files 8 | build/ 9 | cache/ 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![Evflow](logo.png) 2 | 3 | [![License](http://img.shields.io/badge/license-Apache--2.0-b57edc.svg?style=flat)](http://www.apache.org/licenses/LICENSE-2.0) 4 | [![Scrutinizer Issues](http://img.shields.io/badge/scrutinizer-issues-blue.svg?style=flat)](https://scrutinizer-ci.com/g/evflow/evflow/issues/master) 5 | 6 | **Notice: This project has been superceded by [Icicle](http://github.com/icicleio), a collaborative effort to bring asynchronous and multithreaded processing to PHP. Please check out Icicle instead.** 7 | 8 | ## License 9 | All Evflow documentation and source code is licensed under the Apache License, Version 2.0 (Apache-2.0). See [LICENSE.md](LICENSE.md) for details. 10 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * Package-level exception interface. 22 | */ 23 | interface Exception 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /src/TypeException.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | class TypeException extends \InvalidArgumentException implements Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/LoopInitializedException.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | class LoopInitializedException extends \LogicException implements Exception 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Evflow 2 | First, thanks for wanting to contribute to Evflow! This project would be nothing 3 | without you and the amazing PHP community. 4 | 5 | ## What to help out with 6 | Evflow is still in an early stage at the moment, so most of the contributions 7 | will be related to design and architecture rather than code. These documents can 8 | be found and contributed to in the [docs](http://github.com/evflow/docs) 9 | repository. 10 | 11 | ## Pull requests 12 | 1. Fork the main Evflow repository 13 | 2. Make your changes 14 | 3. Send a pull request to the master branch 15 | 16 | ## Style guide 17 | All pull requests must adhere to the [PSR-2 standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). 18 | 19 | ## Licensing 20 | All code contributed will be licensed under Evflow's Apache license, in 21 | accordance with section 5 of the license. See [LICENSE.md](LICENSE.md) for 22 | details. 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evflow/evflow", 3 | "description": "Open and extensible event loop library for asynchronous programming in PHP", 4 | "type": "library", 5 | "license": "Apache-2.0", 6 | "authors": [ 7 | { 8 | "name": "Stephen Coakley", 9 | "email": "me@stephencoakley.com", 10 | "homepage": "http://stephencoakley.com" 11 | } 12 | ], 13 | "require": { 14 | "evenement/evenement": "2.0.*", 15 | "php": ">=5.5.0", 16 | "psr/log": "~1.0", 17 | "react/promise": "~2.1" 18 | }, 19 | "require-dev": { 20 | "monolog/monolog": "~1.12", 21 | "phpunit/phpunit": "~4.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Evflow\\": "src" 26 | }, 27 | "files": [ 28 | "src/functions.php" 29 | ] 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Evflow\\Tests\\": "tests" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests 15 | 16 | 17 | 18 | 19 | src 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/EventDeviceBinderTest.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow\Tests; 19 | 20 | use Evflow\EventDeviceBinder; 21 | use Evflow\EventDeviceInterface; 22 | 23 | class EventDeviceBinderTest extends \PHPUnit_Framework_TestCase 24 | { 25 | public function setUp() 26 | { 27 | $this->manager = new EventDeviceBinder(); 28 | } 29 | 30 | public function testBindDevice() 31 | { 32 | $this->manager->bindDevice($this->getMock(EventDeviceInterface::class)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Timers/Timer.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow\Timers; 19 | 20 | use Evflow\DefaultLoop; 21 | use Evflow\LoopInterface; 22 | 23 | class Timer 24 | { 25 | protected $interval; 26 | protected $periodic; 27 | protected $callback; 28 | 29 | public function __construct($interval, callable $callback, $periodic = false, LoopInterface $loop = null) 30 | { 31 | $this->interval = $interval; 32 | $this->periodic = !!$periodic; 33 | $this->callback = $callback; 34 | $loop = $loop !== null ? $loop : DefaultLoop::instance(); 35 | $loop->fetchDevice(TimerDevice::class)->addTimer($this); 36 | } 37 | 38 | public function isPeriodic() 39 | { 40 | return $this->periodic; 41 | } 42 | 43 | public function getInterval() 44 | { 45 | return $this->interval; 46 | } 47 | 48 | public function getCallback() 49 | { 50 | return $this->callback; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/EventDeviceInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * An interface for event devices that can be polled by an event loop. 22 | */ 23 | interface EventDeviceInterface 24 | { 25 | /** 26 | * Polls the event device to process new incoming events. 27 | * 28 | * The event device should wait for new events until `$timeout` is reached. 29 | * If a 0 is given as the timeout, the event device should check for events 30 | * once and return immediately. If a timeout of -1 is given, the event device 31 | * should wait indefinitely for new events until at least one occurs. 32 | * 33 | * @param LoopInterface $loop The event loop context of the poll. 34 | * @param int $timeout The poll timeout in microseconds. 35 | */ 36 | public function poll(LoopInterface $loop, $timeout); 37 | 38 | /** 39 | * Checks if the event device is actively listening for events. 40 | * 41 | * An event device should be active if it still has more events that will 42 | * trigger any callbacks. That is, this function should return false only 43 | * if it is no longer possible for the event device to generate new tasks 44 | * to be scheduled. 45 | * 46 | * @return bool True if the event device is idle, otherwise false. 47 | */ 48 | public function isActive(); 49 | } 50 | -------------------------------------------------------------------------------- /src/PosixSignalEventDevice.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * Event device that captures POSIX process signals. 22 | * 23 | * @todo Old and messed up. Needs a-fixin'. 24 | */ 25 | class PosixSignalEventDevice implements EventDeviceInterface 26 | { 27 | protected static $signalRefCount = []; 28 | 29 | protected $signal; 30 | 31 | public function __construct($signal, LoopInterface $loop = null) 32 | { 33 | parent::__construct($loop); 34 | $this->signal = $signal; 35 | $this->incrementRefCount($signal); 36 | } 37 | 38 | public function poll($timeout) 39 | { 40 | return pcntl_sigtimedwait([$this->signal], $siginfo, 0, 0) === $this->signal; 41 | } 42 | 43 | public function __destruct() 44 | { 45 | $this->decrementRefCount($this->signal); 46 | } 47 | 48 | protected function getRefCount($signal) 49 | { 50 | if (isset(self::$signalRefCount[$signal])) { 51 | return self::$signalRefCount[$signal]; 52 | } else { 53 | return 0; 54 | } 55 | } 56 | 57 | protected function incrementRefCount($signal) 58 | { 59 | self::$signalRefCount[$signal] = $this->getRefCount($signal) + 1; 60 | 61 | if ($this->getRefCount($signal) === 1) { 62 | pcntl_sigprocmask(SIG_BLOCK, [$signal]); 63 | } 64 | } 65 | 66 | protected function decrementRefCount($signal) 67 | { 68 | if ($this->getRefCount($signal) > 0) { 69 | self::$signalRefCount[$signal] = $this->getRefCount($signal) - 1; 70 | } 71 | 72 | if ($this->getRefCount($signal) === 0) { 73 | pcntl_sigprocmask(SIG_UNBLOCK, [$signal]); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tests: 3 | override: 4 | - 5 | command: phpunit --coverage-clover=coverage.clover 6 | coverage: 7 | file: coverage.clover 8 | format: php-clover 9 | 10 | filter: 11 | excluded_paths: 12 | - tests/* 13 | 14 | checks: 15 | php: 16 | code_rating: true 17 | duplication: true 18 | variable_existence: true 19 | uppercase_constants: true 20 | unused_variables: true 21 | unreachable_code: true 22 | unused_methods: true 23 | unused_parameters: true 24 | unused_properties: true 25 | return_doc_comments: true 26 | require_scope_for_properties: true 27 | require_scope_for_methods: true 28 | require_php_tag_first: true 29 | precedence_in_conditions: true 30 | precedence_mistakes: true 31 | phpunit_assertions: true 32 | php5_style_constructor: true 33 | parameter_non_unique: true 34 | parameter_doc_comments: true 35 | no_non_implemented_abstract_methods: true 36 | no_debug_code: true 37 | useless_calls: true 38 | use_statement_alias_conflict: true 39 | sql_injection_vulnerabilities: true 40 | security_vulnerabilities: true 41 | no_property_on_interface: true 42 | deprecated_code_usage: true 43 | closure_use_not_conflicting: true 44 | closure_use_modifiable: true 45 | avoid_useless_overridden_methods: true 46 | avoid_conflicting_incrementers: true 47 | assignment_of_null_return: true 48 | argument_type_checks: true 49 | avoid_tab_indentation: true 50 | avoid_usage_of_logical_operators: true 51 | blank_line_after_namespace_declaration: true 52 | classes_in_camel_caps: true 53 | encourage_single_quotes: true 54 | foreach_traversable: true 55 | lowercase_basic_constants: true 56 | lowercase_php_keywords: true 57 | instanceof_class_exists: true 58 | function_in_camel_caps: true 59 | newline_at_end_of_file: true 60 | no_else_if_statements: true 61 | no_trailing_whitespace: true 62 | no_unnecessary_function_call_in_for_loop: true 63 | properties_in_camelcaps: true 64 | 65 | tools: 66 | php_changetracking: 67 | enabled: true 68 | bug_patterns: 69 | - '\bfix(?:es|ed)?\b' 70 | feature_patterns: 71 | - '\badd(?:s|ed)?\b' 72 | - '\bimplement(?:s|ed)?\b' 73 | filter: 74 | paths: { } 75 | excluded_paths: { } 76 | -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | use React\Promise\FulfilledPromise; 21 | use React\Promise\Promise; 22 | use React\Promise\PromiseInterface; 23 | 24 | /** 25 | * Creates and returns a promise to call a generator function asynchronously. 26 | * 27 | * @param callable $function 28 | * 29 | * @return Promise 30 | */ 31 | function async(callable $function) 32 | { 33 | // return a new promise 34 | return new Promise(function (callable $resolve, callable $reject, callable $progress) use ($function) { 35 | // get the generator 36 | $generator = $function(); 37 | 38 | // function to step the generator to the next yield statement 39 | $step = function () use (&$step, $generator, $resolve, $reject, $progress) { 40 | $value = null; 41 | 42 | // try to execute the next block of code 43 | try { 44 | $value = $generator->current(); 45 | } catch (\Exception $exception) { 46 | // exception rejects the promise 47 | $reject($exception); 48 | 49 | return; 50 | } 51 | 52 | // if the generator is complete, resolve the promise 53 | if (!$generator->valid()) { 54 | $resolve($value); 55 | 56 | return; 57 | } 58 | 59 | // wrap the value in a promise if it isn't already one 60 | if (!($value instanceof PromiseInterface)) { 61 | $value = new FulfilledPromise($value); 62 | } 63 | 64 | // run the next step when the current one resolves 65 | $value->then(function ($value) use (&$step, $generator) { 66 | $generator->send($value); 67 | $step(); 68 | }, function (\Exception $reason) use ($generator) { 69 | $generator->throw($reason); 70 | }); 71 | }; 72 | 73 | // run the first step in the next tick 74 | DefaultLoop::instance()->nextTick($step); 75 | }); 76 | } 77 | -------------------------------------------------------------------------------- /tests/LoopTest.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow\Tests; 19 | 20 | use Evflow\Loop; 21 | use Evflow\LoopInterface; 22 | 23 | /** 24 | * @runTestsInSeparateProcesses 25 | * @preserveGlobalState disabled 26 | */ 27 | class LoopTest extends \PHPUnit_Framework_TestCase 28 | { 29 | /** 30 | * @var LoopInterface A mock loop instance. 31 | */ 32 | protected $loop; 33 | 34 | public function setUp() 35 | { 36 | $this->loop = $this->getMockBuilder(LoopInterface::class) 37 | ->getMock(); 38 | } 39 | 40 | public function testInstanceReturnsInstance() 41 | { 42 | Loop::init($this->loop); 43 | $this->assertSame($this->loop, Loop::instance()); 44 | } 45 | 46 | /** 47 | * @expectedException Evflow\LoopInitializedException 48 | */ 49 | public function testInitCanOnlyBeCalledOnce() 50 | { 51 | Loop::init($this->loop); 52 | Loop::init($this->loop); 53 | } 54 | 55 | public function testInitCalledAutomatically() 56 | { 57 | $this->assertInstanceOf(LoopInterface::class, Loop::instance()); 58 | } 59 | 60 | public function testIsRunningIsCalled() 61 | { 62 | $this->loop->expects($this->once()) 63 | ->method('isRunning'); 64 | 65 | Loop::init($this->loop); 66 | Loop::isRunning(); 67 | } 68 | 69 | public function testTickIsCalled() 70 | { 71 | $this->loop->expects($this->once()) 72 | ->method('tick'); 73 | 74 | Loop::init($this->loop); 75 | Loop::tick(); 76 | } 77 | 78 | public function testRunIsCalled() 79 | { 80 | $this->loop->expects($this->once()) 81 | ->method('run'); 82 | 83 | Loop::init($this->loop); 84 | Loop::run(); 85 | } 86 | 87 | public function testStopIsCalled() 88 | { 89 | $this->loop->expects($this->once()) 90 | ->method('stop'); 91 | 92 | Loop::init($this->loop); 93 | Loop::stop(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/NativeLoopTest.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow\Tests; 19 | 20 | use Evflow\NativeLoop; 21 | use Evflow\StreamEventDevice; 22 | use Evflow\Timers\Timer; 23 | use Evflow\Timers\TimerDevice; 24 | use Monolog\Handler\StreamHandler; 25 | use Monolog\Logger; 26 | 27 | class NativeLoopTest extends \PHPUnit_Framework_TestCase 28 | { 29 | /** 30 | * @var NativeLoop A native loop instance to test. 31 | */ 32 | protected $loop; 33 | 34 | public function setUp() 35 | { 36 | fwrite(STDOUT, PHP_EOL); 37 | 38 | // set up logging so we can ses what is going on 39 | $log = new Logger('EventLoop'); 40 | $log->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG)); 41 | 42 | // create a loop instance and set the logger 43 | $this->loop = new NativeLoop(); 44 | $this->loop->setLogger($log); 45 | } 46 | 47 | public function testTimer() 48 | { 49 | // bind the timer device 50 | $this->loop->bindDevice(new TimerDevice); 51 | $ran = false; 52 | 53 | $timer = new Timer(1000000, function () use (&$ran) { 54 | $ran = !$ran; 55 | }, false, $this->loop); 56 | 57 | $this->loop->run(); 58 | $this->assertTrue($ran); 59 | } 60 | 61 | public function testStreams() 62 | { 63 | $this->loop->bindDevice(new StreamEventDevice); 64 | $device = $this->loop->fetchDevice(StreamEventDevice::class); 65 | 66 | $ran = false; 67 | // open a socket 68 | $socket = fsockopen('fake-response.appspot.com', 80, $errno, $errstr, 30); 69 | 70 | // send an HTTP request 71 | $out = "GET / HTTP/1.1\r\n"; 72 | $out .= "Host: fake-response.appspot.com\r\n"; 73 | $out .= "Connection: Close\r\n\r\n"; 74 | fwrite($socket, $out); 75 | 76 | $device->addStream($socket, StreamEventDevice::READ, function ($stream) use (&$ran) { 77 | fread($stream, 1024); 78 | fclose($stream); 79 | $ran = true; 80 | }); 81 | 82 | $this->loop->run(); 83 | $this->assertTrue($ran); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/LoopInterface.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * A generic loop interface that supports execution control and a task double queue. 22 | */ 23 | interface LoopInterface 24 | { 25 | /** 26 | * Binds an event device instance to a type, or to itself if no 27 | * interface is specified. 28 | * 29 | * @param EventDeviceInterface $instance The object instance to bind. 30 | * @param string $type The type to bind to. 31 | */ 32 | public function bindDevice(EventDeviceInterface $instance, $type = null); 33 | 34 | /** 35 | * Fetches an event device bound to a given type. 36 | * 37 | * @param string $type The type name to fetch. 38 | * 39 | * @return EventDeviceInterface The device instance bound to the given type. 40 | */ 41 | public function fetchDevice($type); 42 | 43 | /** 44 | * Schedules a callback to be executed in the future. 45 | * 46 | * This function is typically used to queue up callbacks for asynchronous 47 | * events, usually from an event device. 48 | * 49 | * @param callable $callback 50 | */ 51 | public function futureTick(callable $callback); 52 | 53 | /** 54 | * Schedules a callback to be executed immediately in the next tick. 55 | * 56 | * This function should be used when a callback needs to be executed later, 57 | * but needs to do so before any more event callbacks are invoked. 58 | * 59 | * @param callable $callback 60 | */ 61 | public function nextTick(callable $callback); 62 | 63 | /** 64 | * Checks if the event loop is currently running. 65 | * 66 | * @return bool True if the event loop is running, otherwise false. 67 | */ 68 | public function isRunning(); 69 | 70 | /** 71 | * Executes a single iteration of the event loop. 72 | */ 73 | public function tick(); 74 | 75 | /** 76 | * Runs the event loop until there are no more events to process. 77 | */ 78 | public function run(); 79 | 80 | /** 81 | * Stops the event loop execution. 82 | */ 83 | public function stop(); 84 | } 85 | -------------------------------------------------------------------------------- /src/Timers/TimerDevice.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow\Timers; 19 | 20 | use Evflow\EventDeviceInterface; 21 | use Evflow\LoopInterface; 22 | 23 | /** 24 | * Event device for scheduling timers to be triggered at certain time intervals. 25 | */ 26 | class TimerDevice implements EventDeviceInterface 27 | { 28 | const MICROSECONDS_PER_SECOND = 1000000; 29 | 30 | private $timers; 31 | private $timerQueue; 32 | private $currentTime; 33 | 34 | /** 35 | * Creates a new timer event device instance. 36 | */ 37 | public function __construct() 38 | { 39 | $this->timers = new \SplObjectStorage(); 40 | $this->timerQueue = new \SplPriorityQueue(); 41 | } 42 | 43 | public function addTimer(Timer $timer) 44 | { 45 | $this->updateTime(); 46 | $callbackTime = $this->currentTime + $timer->getInterval(); 47 | $this->timers->attach($timer, $callbackTime); 48 | $this->timerQueue->insert($timer, -$callbackTime); 49 | } 50 | 51 | public function updateTime() 52 | { 53 | $this->currentTime = microtime(true) * self::MICROSECONDS_PER_SECOND; 54 | } 55 | 56 | /** 57 | * Polls the event device to process new incoming events. 58 | * 59 | * @see http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#The_special_problem_of_being_too_ear 60 | */ 61 | public function poll(LoopInterface $loop, $timeout) 62 | { 63 | // update internal clock 64 | $this->updateTime(); 65 | 66 | if ($timeout !== 0) { 67 | $nextTimer = $this->timerQueue->top(); 68 | $timeUntilNextTimer = $this->timers[$nextTimer] - $this->currentTime + 1; 69 | $sleepAmount = max(0, $timeUntilNextTimer); 70 | 71 | if ($timeout > 0) { 72 | $sleepAmount = min($timeout, $sleepAmount); 73 | } 74 | 75 | usleep($sleepAmount); 76 | } 77 | 78 | // check for ready timers 79 | while (!$this->timerQueue->isEmpty()) { 80 | $timer = $this->timerQueue->top(); 81 | 82 | // if the target time has passed, call the callback 83 | if ($this->currentTime > $this->timers[$timer]) { 84 | // add callback to future tick queue 85 | $loop->futureTick(function () use ($timer) { 86 | $callback = $timer->getCallback(); 87 | $callback(); 88 | }); 89 | 90 | // remove timer from device 91 | //if (!$timer->isPeriodic()) { 92 | $this->timers->detach($timer); 93 | $this->timerQueue->extract(); 94 | //} 95 | } else { 96 | break; 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * @inheritDoc 103 | */ 104 | public function isActive() 105 | { 106 | return $this->timers->count() > 0; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/EventDeviceBinder.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * Manages a group of event devices on behalf of an event loop. 22 | */ 23 | class EventDeviceBinder implements \IteratorAggregate 24 | { 25 | /** 26 | * @var array A map of device types to the instances bound to them. 27 | */ 28 | protected $deviceBindings = []; 29 | 30 | /** 31 | * Gets the number of active event devices. 32 | * 33 | * @return int 34 | */ 35 | public function activeDeviceCount() 36 | { 37 | $count = 0; 38 | foreach ($this->deviceBindings as $device) { 39 | if ($device->isActive()) { 40 | $count++; 41 | } 42 | } 43 | 44 | return $count; 45 | } 46 | 47 | /** 48 | * Binds an event device instance to a type, or to itself if no 49 | * interface is specified. 50 | * 51 | * @param EventDeviceInterface $device The object instance to bind. 52 | * @param string $type The type name to bind to. 53 | */ 54 | public function bindDevice(EventDeviceInterface $instance, $type = null) 55 | { 56 | if ($type === null) { 57 | $type = get_class($instance); 58 | } else { 59 | // check if the given instance implements the interface being bound to 60 | if (!($instance instanceof $type)) { 61 | throw new TypeException('Given instance does not implement the class or interface "'.$type.'".'); 62 | } 63 | 64 | // check if the type or class exists 65 | if (!class_exists($type) && !interface_exists($type)) { 66 | throw new TypeException('The class or interface "'.$type.'" does not exist.'); 67 | } 68 | } 69 | 70 | $this->deviceBindings[$type] = $instance; 71 | } 72 | 73 | /** 74 | * Gets an attached event device instance of a given type, or creates a new 75 | * instance of one cannot be found. 76 | * 77 | * @param string $type The type of the event device. 78 | * @param string $defaultType The type to use if an instance of the given type cannot be found. 79 | * 80 | * @return EventDeviceInterface An event device instance. 81 | * 82 | * @throws TypeException Thrown if a new instance of a type could not be created. 83 | */ 84 | public function fetchDevice($type) 85 | { 86 | if (isset($this->deviceBindings[$type])) { 87 | return $this->deviceBindings[$type]; 88 | } 89 | 90 | throw new \Exception('No instance for "'.$type.'" bound.'); 91 | } 92 | 93 | /** 94 | * Unbinds a device instance from a type if bound. 95 | * 96 | * @param string $type The type name to unbind. 97 | */ 98 | public function unbindDevice($type) 99 | { 100 | // check if the device exists 101 | if (isset($this->deviceBindings[$type])) { 102 | unset($this->deviceBindings[$type]); 103 | } 104 | } 105 | 106 | /** 107 | * Gets an iterator for looping over each event device. 108 | * 109 | * @return \Iterator 110 | */ 111 | public function getIterator() 112 | { 113 | return new \ArrayIterator($this->deviceBindings); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/StreamEventDevice.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * An event device that watches streams for read and write activity. 22 | */ 23 | class StreamEventDevice implements EventDeviceInterface 24 | { 25 | const READ = 1; 26 | const WRITE = 2; 27 | 28 | /** 29 | * @var array An array of streams to poll for reading. 30 | */ 31 | protected $readStreams = []; 32 | 33 | /** 34 | * @var array An array of streams to poll for writing. 35 | */ 36 | protected $writeStreams = []; 37 | 38 | /** 39 | * @var array A map of streams to callbacks to invoke when an event occurs. 40 | */ 41 | protected $callbacks = []; 42 | 43 | /** 44 | * Registers interest in status changes on a stream. 45 | * 46 | * @param resource $stream A stream resource to watch for status changes. 47 | * @param int $mode The stream modes to watch for. 48 | * @param callable $callback A callback to invoke when an event on the stream occurs. 49 | */ 50 | public function addStream($stream, $mode, $callback) 51 | { 52 | if (($mode & self::READ) === self::READ) { 53 | $this->readStreams[(int)$stream] = $stream; 54 | } 55 | 56 | if (($mode & self::WRITE) === self::WRITE) { 57 | $this->writeStreams[(int)$stream] = $stream; 58 | } 59 | 60 | $this->callbacks[(int)$stream] = $callback; 61 | } 62 | 63 | /** 64 | * Removes a stream from the device, stopping any listening. 65 | * 66 | * @param resource $stream The stream to remove. 67 | * @param int $mode The modes to stop watching for. 68 | */ 69 | public function removeStream($stream, $mode = 3) 70 | { 71 | if (($mode & self::READ) === self::READ && isset($this->readStreams[(int)$stream])) { 72 | unset($this->readStreams[(int)$stream]); 73 | } 74 | 75 | if (($mode & self::WRITE) === self::WRITE && isset($this->writeStreams[(int)$stream])) { 76 | unset($this->writeStreams[(int)$stream]); 77 | } 78 | 79 | if (isset($this->callbacks[(int)$stream])) { 80 | unset($this->callbacks[(int)$stream]); 81 | } 82 | } 83 | 84 | /** 85 | * @inheritDoc 86 | */ 87 | public function poll(LoopInterface $loop, $timeout) 88 | { 89 | $read = array_values($this->readStreams); 90 | $write = array_values($this->writeStreams); 91 | $except = []; 92 | 93 | // calculate timeout values 94 | $tv_sec = $timeout === -1 ? null : 0; 95 | $tv_usec = $timeout === -1 ? null : $timeout; 96 | 97 | if (stream_select($read, $write, $except, $tv_sec, $tv_usec) !== false) { 98 | foreach (array_merge($read, $write) as $stream) { 99 | $loop->futureTick(function () use ($stream) { 100 | $this->callbacks[(int)$stream]($stream); 101 | if (!is_resource($stream)) { 102 | $this->removeStream($stream); 103 | } 104 | }); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * @inheritDoc 111 | */ 112 | public function isActive() 113 | { 114 | return count($this->readStreams) + count($this->writeStreams) > 0; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Loop.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | /** 21 | * Facade for accessing the global event loop. 22 | */ 23 | final class Loop 24 | { 25 | /** 26 | * The event loop instance being used as the default loop. 27 | * 28 | * @var LoopInterface 29 | */ 30 | private static $loopInstance; 31 | 32 | /** 33 | * Indicates if the event loop should automatically begin running at the 34 | * end of the current process' main code execution. 35 | * 36 | * @var bool 37 | */ 38 | private static $autoStart = true; 39 | 40 | /** 41 | * Initializes the global event loop. 42 | * 43 | * By default the event loop will register itself to run just before the 44 | * program exits. If this method is never called (and thus the loop never 45 | * used), then the loop will never get registered to run and won't disturb 46 | * normal execution flow at all. 47 | * 48 | * @param LoopInterface $loop The loop instance to use as the global event loop. 49 | */ 50 | public static function init(LoopInterface $loop = null) 51 | { 52 | // check if the loop was already initialized 53 | if (self::$loopInstance instanceof LoopInterface) { 54 | throw new LoopInitializedException('Loop already initialized.'); 55 | } 56 | 57 | // use the given instance, or create a new one if none given 58 | self::$loopInstance = !!$loop ? $loop : new NativeLoop(); 59 | 60 | // run the global event loop just before the program exits 61 | register_shutdown_function(function () { 62 | if (self::$autoStart) { 63 | self::run(); 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * Gets the default event loop instance being used. 70 | * 71 | * If the event loop has not been initialized it will be initialized with 72 | * default values. 73 | * 74 | * @return LoopInterface 75 | */ 76 | public static function instance() 77 | { 78 | if (!self::$loopInstance) { 79 | self::init(); 80 | } 81 | 82 | return self::$loopInstance; 83 | } 84 | 85 | /** 86 | * Enables the automatic execution of the event loop at the end of the current thread. 87 | */ 88 | public static function enableAutoStart() 89 | { 90 | self::$autoStart = true; 91 | } 92 | 93 | /** 94 | * Disables the automatic execution of the event loop at the end of the current thread. 95 | */ 96 | public static function disableAutoStart() 97 | { 98 | self::$autoStart = false; 99 | } 100 | 101 | /** 102 | * Checks if the event loop is currently running. 103 | * 104 | * @return bool True if the event loop is running, otherwise false. 105 | */ 106 | public static function isRunning() 107 | { 108 | return self::instance()->isRunning(); 109 | } 110 | 111 | /** 112 | * Executes a single iteration of the event loop. 113 | */ 114 | public static function tick() 115 | { 116 | self::instance()->tick(); 117 | } 118 | 119 | /** 120 | * Runs all tasks in the global event loop. 121 | */ 122 | public static function run() 123 | { 124 | self::instance()->run(); 125 | } 126 | 127 | /** 128 | * Stops the event loop execution. 129 | */ 130 | public static function stop() 131 | { 132 | self::instance()->stop(); 133 | } 134 | 135 | // prevents instantiation 136 | private function __construct() 137 | { 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/NativeLoop.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 6 | * use this file except in compliance with the License. You may obtain a copy 7 | * of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | * License for the specific language governing permissions and limitations 15 | * under the License. 16 | */ 17 | 18 | namespace Evflow; 19 | 20 | use Psr\Log\LoggerAwareInterface; 21 | use Psr\Log\LoggerInterface; 22 | use Psr\Log\LogLevel; 23 | 24 | /** 25 | * The default event loop implementation that has no external dependencies. 26 | */ 27 | class NativeLoop implements LoopInterface, LoggerAwareInterface 28 | { 29 | /** 30 | * @var EventDeviceBinder A collection of event devices attached to this event loop. 31 | */ 32 | protected $devices; 33 | 34 | /** 35 | * @var \SplQueue A queue of callbacks to be invoked in the next tick. 36 | */ 37 | private $nextTickQueue; 38 | 39 | /** 40 | * @var \SplQueue A queue of callbacks to be invoked in a future tick. 41 | */ 42 | private $futureTickQueue; 43 | 44 | /** 45 | * @var int The current running tick count. 46 | */ 47 | private $tickCount = 0; 48 | 49 | /** 50 | * @var int The minimum allowed time between ticks in microseconds. 51 | */ 52 | private $minPollInterval = 1000; 53 | 54 | /** 55 | * @var LoggerInterface A logger for sending log information to. 56 | */ 57 | private $logger; 58 | 59 | /** 60 | * @var bool Flag that indicates if the event loop is currently running. 61 | */ 62 | private $running = false; 63 | 64 | /** 65 | * @var bool A flag indicating if the event loop is in the idle state. 66 | */ 67 | private $idle = false; 68 | 69 | /** 70 | * Creates a new event loop instance. 71 | * 72 | * The created event loop operates completely independently from the global 73 | * event loop and other event loop instances. 74 | */ 75 | public function __construct() 76 | { 77 | $this->nextTickQueue = new \SplQueue(); 78 | $this->futureTickQueue = new \SplQueue(); 79 | $this->devices = new EventDeviceBinder(); 80 | } 81 | 82 | /** 83 | * {@inheritDoc} 84 | */ 85 | public function bindDevice(EventDeviceInterface $instance, $type = null) 86 | { 87 | $this->devices->bindDevice($instance, $type); 88 | $this->log(LogLevel::DEBUG, 'New event device of type '.get_class($instance).' bound.'); 89 | } 90 | 91 | public function fetchDevice($type) 92 | { 93 | return $this->devices->fetchDevice($type); 94 | } 95 | 96 | /** 97 | * Checks if the event loop is in the idle state. 98 | * 99 | * The event loop enters the idle state when there are no pending or future 100 | * callback functions to invoke. There may still be active event devices 101 | * attached to the loop while it is idle, and will not exit until all event 102 | * devices are inactive. 103 | * 104 | * @return bool 105 | */ 106 | public function isIdle() 107 | { 108 | return $this->idle; 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | public function isRunning() 115 | { 116 | return $this->running; 117 | } 118 | 119 | /** 120 | * Gets the current tick count of the event loop. 121 | * 122 | * The tick count is an integer that indicates how many iterations the event 123 | * loop has made since it started. The first tick begins at tick 0. The tick 124 | * count is not reset if the event loop is stopped and resumed multiple 125 | * times. 126 | * 127 | * @return int 128 | */ 129 | public function getTickCount() 130 | { 131 | return $this->tickCount; 132 | } 133 | 134 | /** 135 | * Sets a logger instance to send event log info to. 136 | * 137 | * @param LoggerInterface $logger 138 | */ 139 | public function setLogger(LoggerInterface $logger) 140 | { 141 | $this->logger = $logger; 142 | $this->log(LogLevel::DEBUG, 'New logger registered. Hello logger!'); 143 | } 144 | 145 | /** 146 | * @inheritDoc 147 | */ 148 | public function nextTick(callable $callback) 149 | { 150 | $this->nextTickQueue->enqueue($callback); 151 | $this->log(LogLevel::INFO, 'Enqueued new next tick callback.'); 152 | } 153 | 154 | /** 155 | * @inheritDoc 156 | */ 157 | public function futureTick(callable $callback) 158 | { 159 | $this->futureTickQueue->enqueue($callback); 160 | $this->log(LogLevel::INFO, 'Enqueued new future tick callback.'); 161 | } 162 | 163 | /** 164 | * @inheritDoc 165 | */ 166 | public function tick() 167 | { 168 | // invoke all callbacks scheduled for this tick 169 | while (!$this->nextTickQueue->isEmpty()) { 170 | $callback = $this->nextTickQueue->dequeue(); 171 | $this->log(LogLevel::INFO, 'Invoking next scheduled tick callback.'); 172 | $callback(); 173 | } 174 | 175 | // invoke the next future tick callback 176 | if (!$this->futureTickQueue->isEmpty()) { 177 | $callback = $this->futureTickQueue->dequeue(); 178 | $this->log(LogLevel::INFO, 'Invoking next future tick callback.'); 179 | $callback(); 180 | } 181 | 182 | // poll each device & return immediately 183 | $timeout = 0; 184 | 185 | // if the loop is idle and only one device is active we can poll it forever 186 | if ($this->devices->activeDeviceCount() === 1 && $this->idle) { 187 | $timeout = -1; 188 | $this->log(LogLevel::DEBUG, 'Only one active event device. Polling indefinitely.'); 189 | } 190 | 191 | // poll all event devices for new events 192 | foreach ($this->devices as $device) { 193 | // only poll the device if it is active 194 | if ($device->isActive()) { 195 | $device->poll($this, $timeout); 196 | } 197 | } 198 | 199 | $this->tickCount++; 200 | } 201 | 202 | /** 203 | * @inheritDoc 204 | */ 205 | public function run() 206 | { 207 | $this->running = true; 208 | $this->log(LogLevel::INFO, 'Event loop started.'); 209 | 210 | // run the event loop until instructed otherwise 211 | while ($this->running) { 212 | // execute a single tick 213 | $this->tick(); 214 | 215 | // update idle state 216 | if ($this->nextTickQueue->isEmpty() && $this->futureTickQueue->isEmpty()) { 217 | if (!$this->idle) { 218 | $this->idle = true; 219 | $this->log(LogLevel::INFO, 'Entered idle state.'); 220 | } 221 | } elseif ($this->idle) { 222 | $this->idle = false; 223 | $this->log(LogLevel::INFO, 'Leaving idle state.'); 224 | } 225 | 226 | // if we have no more work to do, stop wasting time 227 | if ($this->idle && $this->devices->activeDeviceCount() === 0) { 228 | $this->log(LogLevel::DEBUG, 'Nothing left to do.'); 229 | $this->stop(); 230 | } 231 | 232 | usleep($this->minPollInterval); 233 | } 234 | 235 | $this->log(LogLevel::INFO, 'Event loop stopped.'); 236 | } 237 | 238 | /** 239 | * @inheritDoc 240 | */ 241 | public function stop() 242 | { 243 | $this->log(LogLevel::DEBUG, 'Stopping event loop.'); 244 | $this->running = false; 245 | } 246 | 247 | /** 248 | * Outputs a logging message to a configured logger, if any. 249 | * 250 | * @param mixed $level 251 | * @param string $message 252 | * @param array $context 253 | */ 254 | protected function log($level, $message, array $context = array()) 255 | { 256 | if ($this->logger instanceof LoggerInterface) { 257 | $context['tick'] = $this->getTickCount(); 258 | $this->logger->log($level, $message, $context); 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Apache License 2 | Version 2.0, January 2004 3 | 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | ## 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, and 11 | distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 14 | owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all other entities 17 | that control, are controlled by, or are under common control with that entity. 18 | For the purposes of this definition, "control" means (i) the power, direct or 19 | indirect, to cause the direction or management of such entity, whether by 20 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity exercising 24 | permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, including 27 | but not limited to software source code, documentation source, and configuration 28 | files. 29 | 30 | "Object" form shall mean any form resulting from mechanical transformation or 31 | translation of a Source form, including but not limited to compiled object code, 32 | generated documentation, and conversions to other media types. 33 | 34 | "Work" shall mean the work of authorship, whether in Source or Object form, made 35 | available under the License, as indicated by a copyright notice that is included 36 | in or attached to the work (an example is provided in the Appendix below). 37 | 38 | "Derivative Works" shall mean any work, whether in Source or Object form, that 39 | is based on (or derived from) the Work and for which the editorial revisions, 40 | annotations, elaborations, or other modifications represent, as a whole, an 41 | original work of authorship. For the purposes of this License, Derivative Works 42 | shall not include works that remain separable from, or merely link (or bind by 43 | name) to the interfaces of, the Work and Derivative Works thereof. 44 | 45 | "Contribution" shall mean any work of authorship, including the original version 46 | of the Work and any modifications or additions to that Work or Derivative Works 47 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 48 | by the copyright owner or by an individual or Legal Entity authorized to submit 49 | on behalf of the copyright owner. For the purposes of this definition, 50 | "submitted" means any form of electronic, verbal, or written communication sent 51 | to the Licensor or its representatives, including but not limited to 52 | communication on electronic mailing lists, source code control systems, and 53 | issue tracking systems that are managed by, or on behalf of, the Licensor for 54 | the purpose of discussing and improving the Work, but excluding communication 55 | that is conspicuously marked or otherwise designated in writing by the copyright 56 | owner as "Not a Contribution." 57 | 58 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 59 | of whom a Contribution has been received by Licensor and subsequently 60 | incorporated within the Work. 61 | 62 | ## 2. Grant of Copyright License. 63 | 64 | Subject to the terms and conditions of this License, each Contributor hereby 65 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 66 | irrevocable copyright license to reproduce, prepare Derivative Works of, 67 | publicly display, publicly perform, sublicense, and distribute the Work and such 68 | Derivative Works in Source or Object form. 69 | 70 | ## 3. Grant of Patent License. 71 | 72 | Subject to the terms and conditions of this License, each Contributor hereby 73 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 74 | irrevocable (except as stated in this section) patent license to make, have 75 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 76 | such license applies only to those patent claims licensable by such Contributor 77 | that are necessarily infringed by their Contribution(s) alone or by combination 78 | of their Contribution(s) with the Work to which such Contribution(s) was 79 | submitted. If You institute patent litigation against any entity (including a 80 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 81 | Contribution incorporated within the Work constitutes direct or contributory 82 | patent infringement, then any patent licenses granted to You under this License 83 | for that Work shall terminate as of the date such litigation is filed. 84 | 85 | ## 4. Redistribution. 86 | 87 | You may reproduce and distribute copies of the Work or Derivative Works thereof 88 | in any medium, with or without modifications, and in Source or Object form, 89 | provided that You meet the following conditions: 90 | 91 | 1. You must give any other recipients of the Work or Derivative Works a copy of 92 | this License; and 93 | 94 | 2. You must cause any modified files to carry prominent notices stating that 95 | You changed the files; and 96 | 97 | 3. You must retain, in the Source form of any Derivative Works that You 98 | distribute, all copyright, patent, trademark, and attribution notices from 99 | the Source form of the Work, excluding those notices that do not pertain to 100 | any part of the Derivative Works; and 101 | 102 | 4. If the Work includes a "NOTICE" text file as part of its distribution, then 103 | any Derivative Works that You distribute must include a readable copy of the 104 | attribution notices contained within such NOTICE file, excluding those 105 | notices that do not pertain to any part of the Derivative Works, in at least 106 | one of the following places: within a NOTICE text file distributed as part 107 | of the Derivative Works; within the Source form or documentation, if 108 | provided along with the Derivative Works; or, within a display generated by 109 | the Derivative Works, if and wherever such third-party notices normally 110 | appear. The contents of the NOTICE file are for informational purposes only 111 | and do not modify the License. You may add Your own attribution notices 112 | within Derivative Works that You distribute, alongside or as an addendum to 113 | the NOTICE text from the Work, provided that such additional attribution 114 | notices cannot be construed as modifying the License. 115 | 116 | You may add Your own copyright statement to Your modifications and may provide 117 | additional or different license terms and conditions for use, reproduction, or 118 | distribution of Your modifications, or for any such Derivative Works as a whole, 119 | provided Your use, reproduction, and distribution of the Work otherwise complies 120 | with the conditions stated in this License. 121 | 122 | ## 5. Submission of Contributions. 123 | 124 | Unless You explicitly state otherwise, any Contribution intentionally submitted 125 | for inclusion in the Work by You to the Licensor shall be under the terms and 126 | conditions of this License, without any additional terms or conditions. 127 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 128 | any separate license agreement you may have executed with Licensor regarding 129 | such Contributions. 130 | 131 | ## 6. Trademarks. 132 | 133 | This License does not grant permission to use the trade names, trademarks, 134 | service marks, or product names of the Licensor, except as required for 135 | reasonable and customary use in describing the origin of the Work and 136 | reproducing the content of the NOTICE file. 137 | 138 | ## 7. Disclaimer of Warranty. 139 | 140 | Unless required by applicable law or agreed to in writing, Licensor provides the 141 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 142 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 143 | including, without limitation, any warranties or conditions of TITLE, 144 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 145 | solely responsible for determining the appropriateness of using or 146 | redistributing the Work and assume any risks associated with Your exercise of 147 | permissions under this License. 148 | 149 | ## 8. Limitation of Liability. 150 | 151 | In no event and under no legal theory, whether in tort (including negligence), 152 | contract, or otherwise, unless required by applicable law (such as deliberate 153 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 154 | liable to You for damages, including any direct, indirect, special, incidental, 155 | or consequential damages of any character arising as a result of this License or 156 | out of the use or inability to use the Work (including but not limited to 157 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 158 | any and all other commercial damages or losses), even if such Contributor has 159 | been advised of the possibility of such damages. 160 | 161 | ## 9. Accepting Warranty or Additional Liability. 162 | 163 | While redistributing the Work or Derivative Works thereof, You may choose to 164 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 165 | other liability obligations and/or rights consistent with this License. However, 166 | in accepting such obligations, You may act only on Your own behalf and on Your 167 | sole responsibility, not on behalf of any other Contributor, and only if You 168 | agree to indemnify, defend, and hold each Contributor harmless for any liability 169 | incurred by, or claims asserted against, such Contributor by reason of your 170 | accepting any such warranty or additional liability. 171 | 172 | END OF TERMS AND CONDITIONS 173 | 174 | ## APPENDIX: How to apply the Apache License to your work. 175 | 176 | To apply the Apache License to your work, attach the following boilerplate 177 | notice, with the fields enclosed by brackets "{}" replaced with your own 178 | identifying information. (Don't include the brackets!) The text should be 179 | enclosed in the appropriate comment syntax for the file format. We also 180 | recommend that a file or class name and description of purpose be included on 181 | the same "printed page" as the copyright notice for easier identification within 182 | third-party archives. 183 | 184 | Copyright {yyyy} {name of copyright owner} 185 | 186 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 187 | use this file except in compliance with the License. You may obtain a copy 188 | of the License at 189 | 190 | http://www.apache.org/licenses/LICENSE-2.0 191 | 192 | Unless required by applicable law or agreed to in writing, software 193 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 194 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 195 | License for the specific language governing permissions and limitations 196 | under the License. 197 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 42 | 49 | 50 | 52 | 53 | 55 | image/svg+xml 56 | 58 | 59 | 60 | 61 | 62 | 90 | 96 | 100 | 106 | 111 | 116 | 122 | 123 | 127 | 132 | 137 | 138 | 144 | 150 | 156 | 157 | 158 | --------------------------------------------------------------------------------