mirror of
https://github.com/danog/loop.git
synced 2024-11-26 20:04:44 +01:00
243 lines
8.5 KiB
Markdown
243 lines
8.5 KiB
Markdown
# Loop
|
|
|
|
![Build status](https://github.com/danog/loop/workflows/build/badge.svg)
|
|
[![codecov](https://codecov.io/gh/danog/loop/branch/master/graph/badge.svg)](https://codecov.io/gh/danog/loop)
|
|
[![Psalm coverage](https://shepherd.dev/github/danog/loop/coverage.svg)](https://shepherd.dev/github/vimeo/shepherd)
|
|
![License](https://img.shields.io/badge/license-MIT-blue.svg)
|
|
|
|
`danog/loop` provides a set of powerful async loop APIs for executing operations periodically or on demand, in background loops a-la threads.
|
|
A more flexible and powerful alternative to [AMPHP](https://amphp.org)'s [repeat](https://amphp.org/amp/event-loop/api#repeat) function, allowing dynamically changeable repeat periods, resumes and signaling.
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
composer require danog/loop
|
|
```
|
|
|
|
## API
|
|
|
|
* Basic
|
|
* [GenericLoop](#genericloop)
|
|
* [PeriodicLoop](#periodicloop)
|
|
* Advanced
|
|
* [Loop](#loop)
|
|
* [ResumableLoop](#resumableloop)
|
|
* [SignalLoop](#signalloop)
|
|
* [ResumableSignalLoop](#resumablesignalloop)
|
|
|
|
All loop APIs are defined by a set of [interfaces](https://github.com/danog/loop/tree/master/lib/Interfaces): however, to use them, you would usually have to extend only one of the [abstract class implementations](https://github.com/danog/loop/tree/master/lib).
|
|
|
|
### Loop
|
|
|
|
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/LoopInterface.php) - [Example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/Loop.php)
|
|
|
|
A basic loop, capable of running in background (asynchronously) the code contained in the `loop` function.
|
|
|
|
API:
|
|
```php
|
|
namespace danog\Loop;
|
|
|
|
abstract class Loop
|
|
{
|
|
abstract public function loop();
|
|
abstract public function __toString(): string;
|
|
|
|
public function start(): bool;
|
|
public function isRunning(): bool;
|
|
|
|
protected function startedLoop(): void;
|
|
protected function exitedLoop(): void;
|
|
}
|
|
```
|
|
|
|
#### loop()
|
|
|
|
The `loop` [async coroutine](https://amphp.org/amp/coroutines/) will be run only once, every time the `start` method is called.
|
|
|
|
#### __toString()
|
|
|
|
This method should return the loop's name.
|
|
It's useful for implementing the [log methods](#startedloop).
|
|
|
|
#### start()
|
|
|
|
Asynchronously starts the `loop` methods once in background.
|
|
Multiple calls to `start` will be ignored, returning `false` instead of `true`.
|
|
|
|
#### isRunning()
|
|
|
|
You can use the `isRunning` method to check if the loop is already running.
|
|
|
|
#### startedLoop()
|
|
|
|
Optionally, you can override this method to detect and log when the loop is started.
|
|
Make sure to always call the parent `startedLoop()` method to avoid issues.
|
|
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__tostring) method.
|
|
|
|
#### exitedLoop()
|
|
|
|
Optionally, you can override this method to detect and log when the loop is ended.
|
|
Make sure to always call the parent `exitedLoop()` method to avoid issues.
|
|
You can use directly `$this` as loop name when logging, thanks to the custom [__toString](#__tostring) method.
|
|
|
|
|
|
### ResumableLoop
|
|
|
|
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableLoopInterface.php) - [Example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableLoop.php)
|
|
|
|
A way more useful loop that exposes APIs to pause and resume the execution of the loop, both from outside of the loop, and in a cron-like manner from inside of the loop.
|
|
|
|
```php
|
|
namespace danog\Loop;
|
|
|
|
abstract class ResumableLoop extends Loop
|
|
{
|
|
public function pause(?int $time = null): Future;
|
|
public function resume(): Future;
|
|
}
|
|
```
|
|
|
|
All methods from [Loop](#loop), plus:
|
|
|
|
#### pause()
|
|
|
|
Pauses the loop for the specified number of milliseconds, or forever if `null` is provided.
|
|
|
|
#### resume()
|
|
|
|
Forcefully resume the loop from the outside.
|
|
Returns a promise that is resolved when the loop is paused again.
|
|
|
|
|
|
### SignalLoop
|
|
|
|
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/SignalLoopInterface.php) - [Example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/SignalLoop.php)
|
|
|
|
Yet another loop interface that exposes APIs to send signals to the loop, useful to force the termination of a loop from the outside, or to send data into it.
|
|
|
|
```php
|
|
namespace danog\Loop;
|
|
|
|
abstract class SignalLoop extends Loop
|
|
{
|
|
public function signal($what): void;
|
|
public function waitSignal($promise): Future;
|
|
}
|
|
|
|
```
|
|
|
|
All methods from [Loop](#loop), plus:
|
|
|
|
#### signal()
|
|
|
|
Sends a signal to the loop: can be anything, but typically `true` is often used as termination signal.
|
|
Signaling can be used as a message exchange mechanism a-la IPC, and can also be used to throw exceptions inside the loop.
|
|
|
|
#### waitSignal()
|
|
|
|
Resolve the provided promise or return|throw passed signal.
|
|
|
|
### ResumableSignalLoop
|
|
|
|
[Interface](https://github.com/danog/loop/blob/master/lib/Interfaces/ResumableSignalLoopInterface.php) - [Example](https://github.com/danog/loop/blob/master/examples/2.%20Advanced/ResumableSignalLoop.php)
|
|
|
|
This is what you would usually use to build a full async loop.
|
|
All loop interfaces and loop implementations are combined into a single class you can extend.
|
|
|
|
```php
|
|
namespace danog\Loop;
|
|
|
|
abstract class ResumableSignalLoop extends SignalLoop, ResumableSignalLoop
|
|
{
|
|
}
|
|
```
|
|
|
|
The class is actually composited using traits to feature all methods from [SignalLoop](#signalloop) and [ResumableSignalLoop](#resumablesignalloop).
|
|
|
|
### GenericLoop
|
|
|
|
[Class](https://github.com/danog/loop/blob/master/lib/Generic/GenericLoop.php) - [Example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/GenericLoop.php)
|
|
|
|
If you want a simpler way to use the `ResumableSignalLoop`, you can use the GenericLoop.
|
|
```php
|
|
namespace danog\Loop\Generic;
|
|
class GenericLoop extends ResumableSignalLoop
|
|
{
|
|
/**
|
|
* Stop the loop.
|
|
*/
|
|
const STOP = -1;
|
|
/**
|
|
* Pause the loop.
|
|
*/
|
|
const PAUSE = null;
|
|
/**
|
|
* Rerun the loop.
|
|
*/
|
|
const CONTINUE = 0;
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* If possible, the callable will be bound to the current instance of the loop.
|
|
*
|
|
* @param callable $callable Callable to run
|
|
* @param string $name Loop name
|
|
*/
|
|
public function __construct(callable $callable, string $name);
|
|
/**
|
|
* Report pause, can be overriden for logging.
|
|
*
|
|
* @param integer $timeout Pause duration, 0 = forever
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function reportPause(int $timeout): void;
|
|
/**
|
|
* Get loop name, provided to constructor.
|
|
*/
|
|
public function __toString(): string;
|
|
}
|
|
```
|
|
|
|
The callback will be bound to the `GenericLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by casting `$this` to a string, use the pause/waitSignal methods & so on).
|
|
The return value of the callable can be:
|
|
* A number - the loop will be paused for the specified number of seconds
|
|
* `GenericLoop::STOP` - The loop will stop
|
|
* `GenericLoop::PAUSE` - The loop will pause forever (or until the `resume` method is called on the loop object from outside the loop)
|
|
* `GenericLoop::CONTINUE` - Return this if you want to rerun the loop without waiting
|
|
|
|
If the callable does not return anything, the loop will behave is if `GenericLoop::PAUSE` was returned.
|
|
|
|
### PeriodicLoop
|
|
|
|
[Class](https://github.com/danog/loop/blob/master/lib/Generic/PeriodicLoop.php) - [Example](https://github.com/danog/loop/blob/master/examples/1.%20Basic/PeriodicLoop.php)
|
|
|
|
If you simply want to execute an action every N seconds, [PeriodicLoop](https://github.com/danog/MadelineProto/blob/master/src/danog/MadelineProto/Loop/Generic/PeriodicLoop.php) is the way to go.
|
|
```php
|
|
namespace danog\Loop\Generic;
|
|
|
|
class PeriodicLoop extends ResumableSignalLoop
|
|
{
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* If possible, the callable will be bound to the current instance of the loop.
|
|
*
|
|
* @param callable $callback Callback to call
|
|
* @param string $name Loop name
|
|
* @param ?int $interval Loop interval
|
|
*/
|
|
public function __construct(callable $callback, string $name, ?int $interval);
|
|
/**
|
|
* Get name of the loop, passed to the constructor.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function __toString(): string;
|
|
}
|
|
```
|
|
|
|
`PeriodicLoop` runs a callback at a periodic interval.
|
|
The callback will be bound to the `PeriodicLoop` instance: this means that you will be able to use `$this` as if the callback were actually the `loop` function (you can get the loop name by casting `$this` to a string, use the pause/waitSignal methods & so on).
|
|
The loop can be stopped from the outside or from the inside by signaling or returning `true`.
|