feature #26339 [Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods (ostrolucky)
This PR was merged into the 4.4 branch.
Discussion
----------
[Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | no
| Tests pass? | yes
| Fixed tickets | -
| License | MIT
| Doc PR | TBA
The way ProgressBar redraw frequency works currently requires to know speed of progress beforehand, which is impossible to know in some situations, e.g. when showing progress of download, or I/O speed. Setting frequency too low relative to progress speed throttles I/O speed and makes progress bar flicker too much, setting it too high makes progress bar unresponsive. Current behaviour IMHO undermines usefulness of ProgressBar.
This is an attempt to replace this with more consistent experience, not requiring to know speed of progress.)
Commits
-------
83edac321e
[Console] Add ProgressBar::preventRedrawFasterThan() and forceRedrawSlowerThan() methods
This commit is contained in:
commit
c202e96cd6
@ -4,7 +4,8 @@ CHANGELOG
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
|
||||
* added `Question::setTrimmable` default to true to allow the answer to be trimmed
|
||||
* added method `preventRedrawFasterThan()` and `forceRedrawSlowerThan()` on `ProgressBar`
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
@ -32,6 +32,10 @@ final class ProgressBar
|
||||
private $format;
|
||||
private $internalFormat;
|
||||
private $redrawFreq = 1;
|
||||
private $writeCount;
|
||||
private $lastWriteTime;
|
||||
private $minSecondsBetweenRedraws = 0;
|
||||
private $maxSecondsBetweenRedraws = 1;
|
||||
private $output;
|
||||
private $step = 0;
|
||||
private $max;
|
||||
@ -51,7 +55,7 @@ final class ProgressBar
|
||||
* @param OutputInterface $output An OutputInterface instance
|
||||
* @param int $max Maximum steps (0 if unknown)
|
||||
*/
|
||||
public function __construct(OutputInterface $output, int $max = 0)
|
||||
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 0)
|
||||
{
|
||||
if ($output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
@ -61,12 +65,17 @@ final class ProgressBar
|
||||
$this->setMaxSteps($max);
|
||||
$this->terminal = new Terminal();
|
||||
|
||||
if (0 < $minSecondsBetweenRedraws) {
|
||||
$this->redrawFreq = null;
|
||||
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
|
||||
}
|
||||
|
||||
if (!$this->output->isDecorated()) {
|
||||
// disable overwrite when output does not support ANSI codes.
|
||||
$this->overwrite = false;
|
||||
|
||||
// set a reasonable redraw frequency so output isn't flooded
|
||||
$this->setRedrawFrequency($max / 10);
|
||||
$this->redrawFreq = null;
|
||||
}
|
||||
|
||||
$this->startTime = time();
|
||||
@ -183,6 +192,11 @@ final class ProgressBar
|
||||
return $this->percent;
|
||||
}
|
||||
|
||||
public function getBarOffset(): int
|
||||
{
|
||||
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
|
||||
}
|
||||
|
||||
public function setBarWidth(int $size)
|
||||
{
|
||||
$this->barWidth = max(1, $size);
|
||||
@ -238,9 +252,19 @@ final class ProgressBar
|
||||
*
|
||||
* @param int|float $freq The frequency in steps
|
||||
*/
|
||||
public function setRedrawFrequency(int $freq)
|
||||
public function setRedrawFrequency(?int $freq)
|
||||
{
|
||||
$this->redrawFreq = max($freq, 1);
|
||||
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
|
||||
}
|
||||
|
||||
public function preventRedrawFasterThan(float $intervalInSeconds): void
|
||||
{
|
||||
$this->minSecondsBetweenRedraws = $intervalInSeconds;
|
||||
}
|
||||
|
||||
public function forceRedrawSlowerThan(float $intervalInSeconds): void
|
||||
{
|
||||
$this->maxSecondsBetweenRedraws = $intervalInSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,11 +329,27 @@ final class ProgressBar
|
||||
$step = 0;
|
||||
}
|
||||
|
||||
$prevPeriod = (int) ($this->step / $this->redrawFreq);
|
||||
$currPeriod = (int) ($step / $this->redrawFreq);
|
||||
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
|
||||
$prevPeriod = (int) ($this->step / $redrawFreq);
|
||||
$currPeriod = (int) ($step / $redrawFreq);
|
||||
$this->step = $step;
|
||||
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
|
||||
if ($prevPeriod !== $currPeriod || $this->max === $step) {
|
||||
$timeInterval = microtime(true) - $this->lastWriteTime;
|
||||
|
||||
// Draw regardless of other limits
|
||||
if ($this->max === $step) {
|
||||
$this->display();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttling
|
||||
if ($timeInterval < $this->minSecondsBetweenRedraws) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw each step period, but not too late
|
||||
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
|
||||
$this->display();
|
||||
}
|
||||
}
|
||||
@ -413,8 +453,10 @@ final class ProgressBar
|
||||
}
|
||||
|
||||
$this->firstRun = false;
|
||||
$this->lastWriteTime = microtime(true);
|
||||
|
||||
$this->output->write($message);
|
||||
++$this->writeCount;
|
||||
}
|
||||
|
||||
private function determineBestFormat(): string
|
||||
@ -436,7 +478,7 @@ final class ProgressBar
|
||||
{
|
||||
return [
|
||||
'bar' => function (self $bar, OutputInterface $output) {
|
||||
$completeBars = floor($bar->getMaxSteps() > 0 ? $bar->getProgressPercent() * $bar->getBarWidth() : $bar->getProgress() % $bar->getBarWidth());
|
||||
$completeBars = $bar->getBarOffset();
|
||||
$display = str_repeat($bar->getBarCharacter(), $completeBars);
|
||||
if ($completeBars < $bar->getBarWidth()) {
|
||||
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
|
||||
|
@ -944,4 +944,58 @@ class ProgressBarTest extends TestCase
|
||||
$this->assertEquals(5, $bar->getBarWidth(), stream_get_contents($output->getStream()));
|
||||
putenv('COLUMNS=120');
|
||||
}
|
||||
|
||||
public function testForceRedrawSlowerThan(): void
|
||||
{
|
||||
$bar = new ProgressBar($output = $this->getOutputStream());
|
||||
$bar->setRedrawFrequency(4); // disable step based redraws
|
||||
$bar->start();
|
||||
$bar->setProgress(1); // No treshold hit, no redraw
|
||||
$bar->forceRedrawSlowerThan(2);
|
||||
sleep(1);
|
||||
$bar->setProgress(2); // Still no redraw because redraw is forced after 2 seconds only
|
||||
sleep(1);
|
||||
$bar->setProgress(3); // 1+1 = 2 -> redraw finally
|
||||
$bar->setProgress(4); // step based redraw freq hit, redraw even without sleep
|
||||
$bar->setProgress(5); // No treshold hit, no redraw
|
||||
$bar->preventRedrawFasterThan(3);
|
||||
sleep(2);
|
||||
$bar->setProgress(6); // No redraw even though 2 seconds passed. Throttling has priority
|
||||
$bar->preventRedrawFasterThan(2);
|
||||
$bar->setProgress(7); // Throttling relaxed, draw
|
||||
|
||||
rewind($output->getStream());
|
||||
$this->assertEquals(
|
||||
' 0 [>---------------------------]'.
|
||||
$this->generateOutput(' 3 [--->------------------------]').
|
||||
$this->generateOutput(' 4 [---->-----------------------]').
|
||||
$this->generateOutput(' 7 [------->--------------------]'),
|
||||
stream_get_contents($output->getStream())
|
||||
);
|
||||
}
|
||||
|
||||
public function testPreventRedrawFasterThan()
|
||||
{
|
||||
$bar = new ProgressBar($output = $this->getOutputStream());
|
||||
$bar->setRedrawFrequency(1);
|
||||
$bar->preventRedrawFasterThan(1);
|
||||
$bar->start();
|
||||
$bar->setProgress(1); // Too fast, should not draw
|
||||
sleep(1);
|
||||
$bar->setProgress(2); // 1 second passed, draw
|
||||
$bar->preventRedrawFasterThan(2);
|
||||
sleep(1);
|
||||
$bar->setProgress(3); // 1 second passed but we changed threshold, should not draw
|
||||
sleep(1);
|
||||
$bar->setProgress(4); // 1+1 seconds = 2 seconds passed which conforms threshold, draw
|
||||
$bar->setProgress(5); // No treshold hit, no redraw
|
||||
|
||||
rewind($output->getStream());
|
||||
$this->assertEquals(
|
||||
' 0 [>---------------------------]'.
|
||||
$this->generateOutput(' 2 [-->-------------------------]').
|
||||
$this->generateOutput(' 4 [---->-----------------------]'),
|
||||
stream_get_contents($output->getStream())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user