2022-12-24 14:36:39 +01:00
|
|
|
<?php declare(strict_types=1);
|
2023-01-23 00:24:18 +01:00
|
|
|
|
2020-07-21 18:06:19 +02:00
|
|
|
/**
|
2023-01-23 00:24:18 +01:00
|
|
|
* Generic loop.
|
2020-07-21 18:06:19 +02:00
|
|
|
*
|
|
|
|
* @author Daniil Gentili <daniil@daniil.it>
|
|
|
|
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
|
|
|
* @license https://opensource.org/licenses/MIT MIT
|
|
|
|
*/
|
|
|
|
|
2020-07-21 21:45:22 +02:00
|
|
|
namespace danog\Loop;
|
2020-07-21 18:06:19 +02:00
|
|
|
|
2023-01-22 21:59:13 +01:00
|
|
|
use Revolt\EventLoop;
|
2023-01-23 00:24:18 +01:00
|
|
|
use Stringable;
|
2020-07-21 18:06:19 +02:00
|
|
|
|
|
|
|
/**
|
2023-01-23 00:24:18 +01:00
|
|
|
* Generic loop, runs single callable.
|
2020-07-21 18:06:19 +02:00
|
|
|
*
|
|
|
|
* @author Daniil Gentili <daniil@daniil.it>
|
|
|
|
*/
|
2023-01-23 00:24:18 +01:00
|
|
|
abstract class Loop implements Stringable
|
2020-07-21 18:06:19 +02:00
|
|
|
{
|
2023-01-22 21:59:13 +01:00
|
|
|
/**
|
2023-01-23 00:24:18 +01:00
|
|
|
* Stop the loop.
|
|
|
|
*/
|
|
|
|
const STOP = -1.0;
|
|
|
|
/**
|
|
|
|
* Pause the loop.
|
|
|
|
*/
|
|
|
|
const PAUSE = null;
|
|
|
|
/**
|
|
|
|
* Rerun the loop.
|
|
|
|
*/
|
|
|
|
const CONTINUE = 0.0;
|
|
|
|
/**
|
|
|
|
* Whether the loop is running.
|
|
|
|
*/
|
|
|
|
private bool $running = false;
|
|
|
|
/**
|
|
|
|
* Resume timer ID.
|
|
|
|
*/
|
|
|
|
private ?string $resumeTimer = null;
|
|
|
|
/**
|
|
|
|
* Resume deferred ID.
|
|
|
|
*/
|
|
|
|
private ?string $resumeDeferred = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Report pause, can be overriden for logging.
|
|
|
|
*
|
|
|
|
* @param float $timeout Pause duration, 0 = forever
|
|
|
|
*
|
2023-01-22 21:59:13 +01:00
|
|
|
*/
|
2023-01-23 00:24:18 +01:00
|
|
|
protected function reportPause(float $timeout): void
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2023-01-22 21:59:13 +01:00
|
|
|
/**
|
|
|
|
* Start the loop.
|
|
|
|
*
|
|
|
|
* Returns false if the loop is already running.
|
|
|
|
*/
|
|
|
|
public function start(): bool
|
|
|
|
{
|
2023-01-23 00:24:18 +01:00
|
|
|
if ($this->running) {
|
2023-01-22 21:59:13 +01:00
|
|
|
return false;
|
|
|
|
}
|
2023-01-23 00:24:18 +01:00
|
|
|
$this->running = true;
|
|
|
|
$this->startedLoop();
|
|
|
|
$this->resume();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Stops loop.
|
|
|
|
*
|
|
|
|
* Returns false if the loop is not running.
|
|
|
|
*/
|
|
|
|
public function stop(): bool
|
|
|
|
{
|
|
|
|
if (!$this->running) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$this->running = false;
|
|
|
|
$this->resume();
|
2023-01-22 21:59:13 +01:00
|
|
|
return true;
|
|
|
|
}
|
2023-01-23 00:24:18 +01:00
|
|
|
abstract protected function loop(): ?float;
|
|
|
|
|
|
|
|
private bool $paused = true;
|
|
|
|
private function loopInternal(): void
|
|
|
|
{
|
|
|
|
$this->paused = false;
|
|
|
|
if (!$this->running) {
|
|
|
|
$this->exitedLoopInternal();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
$timeout = $this->loop();
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
$this->exitedLoopInternal();
|
|
|
|
throw $e;
|
|
|
|
}
|
|
|
|
if (!$this->running || $timeout === self::STOP) {
|
|
|
|
$this->exitedLoopInternal();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->paused = true;
|
|
|
|
if ($timeout === self::PAUSE) {
|
|
|
|
$this->reportPause(0.0);
|
|
|
|
} else {
|
|
|
|
if (!$this->resumeDeferred) {
|
|
|
|
\assert($this->resumeTimer === null);
|
|
|
|
$this->resumeTimer = EventLoop::delay($timeout, function (): void {
|
|
|
|
$this->resumeTimer = null;
|
|
|
|
$this->loopInternal();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
$this->reportPause($timeout);
|
|
|
|
}
|
|
|
|
}
|
2023-01-22 21:22:34 +01:00
|
|
|
|
2023-01-23 00:24:18 +01:00
|
|
|
private function exitedLoopInternal(): void
|
|
|
|
{
|
|
|
|
$this->running = false;
|
|
|
|
if ($this->resumeTimer) {
|
|
|
|
$storedWatcherId = $this->resumeTimer;
|
|
|
|
EventLoop::cancel($storedWatcherId);
|
|
|
|
$this->resumeTimer = null;
|
|
|
|
}
|
|
|
|
if ($this->resumeDeferred) {
|
|
|
|
$storedWatcherId = $this->resumeDeferred;
|
|
|
|
EventLoop::cancel($storedWatcherId);
|
|
|
|
$this->resumeTimer = null;
|
|
|
|
}
|
|
|
|
$this->exitedLoop();
|
|
|
|
}
|
2023-01-22 21:59:13 +01:00
|
|
|
/**
|
2023-01-23 00:24:18 +01:00
|
|
|
* Signal that loop has running.
|
2023-01-22 21:59:13 +01:00
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function startedLoop(): void
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Signal that loop has exited.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
protected function exitedLoop(): void
|
|
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Check whether loop is running.
|
|
|
|
*/
|
|
|
|
public function isRunning(): bool
|
|
|
|
{
|
2023-01-23 00:24:18 +01:00
|
|
|
return $this->running;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resume the loop.
|
|
|
|
*/
|
|
|
|
public function resume(): void
|
|
|
|
{
|
|
|
|
if (!$this->resumeDeferred && $this->running && $this->paused) {
|
|
|
|
if ($this->resumeTimer) {
|
|
|
|
$timer = $this->resumeTimer;
|
|
|
|
$this->resumeTimer = null;
|
|
|
|
EventLoop::cancel($timer);
|
|
|
|
}
|
|
|
|
$this->resumeDeferred = EventLoop::defer(function (): void {
|
|
|
|
$this->resumeDeferred = null;
|
|
|
|
$this->loopInternal();
|
|
|
|
});
|
|
|
|
}
|
2023-01-22 21:22:34 +01:00
|
|
|
}
|
2020-07-21 18:06:19 +02:00
|
|
|
}
|