progress icon indicating copy to clipboard operation
progress copied to clipboard

Weird Behavior of MultiProgressBar

Open bjesuiter opened this issue 2 years ago • 2 comments

I'm still trying to get my multi-progress-bar to work with this library.

Simplified Working Example

The sleep() calls in there makes sure that my updates will be scheduled slower than the drawing interval.

import { MultiProgressBar } from "https://deno.land/x/[email protected]/mod.ts";
import { sleep } from "https://deno.land/x/sleep/mod.ts";

const multibar = new MultiProgressBar({
  title: "Multi-Progress Bars",
  complete: "=",
  incomplete: "-",
  interval: 1,
  display: "[:bar] :text :percent :time :completed/:total",
});

multibar.render([
  { text: "progress_1", completed: 1 },
]);

await sleep(0.002);

multibar.render([
  { text: "progress_1", completed: 2 },
  { text: "progress_2", completed: 1 },
]);

await sleep(0.002);

multibar.render([
  { text: "progress_1", completed: 2 },
  { text: "progress_2", completed: 2 },
]);

await sleep(0.002);

multibar.render([
  { text: "progress_1", completed: 3 },
  { text: "progress_2", completed: 2 },
  { text: "progress_3", completed: 1 },
]);

Output

Multi-Progress Bars
[==------------------------------------------------] progress_1 3.00% 0.0s 3/100
[=-------------------------------------------------] progress_2 2.00% 0.0s 2/100
[=-------------------------------------------------] progress_3 1.00% 0.0s 1/100

Problematic Code

Here is a file with 3 mocked progress readables: https://github.com/codemonument/deno_downstream/blob/main/test/units/multiProgressCliRenderer.test.ts

When I activate the simpleCallbackTarget(), I get a stream of all the state events based on the aforementioned format:

[
  { text: "progress_1", completed: 3 },
  { text: "progress_2", completed: 2 },
  { text: "progress_3", completed: 1 },
]

But when I activate the multiProgressCliRenderer() it only outputs two progress bars at first until they finished and then outputs the third one all at once, like this:

While running:

Multi-Progress Bars
[============================================------] progress_1 87.00% 4.5s 87/100
[============================================------] progress_2 87.00% 4.5s 87/100

When Finished:

Multi-Progress Bars
[==================================================] progress_1 100.00% 5.1s 100/100
[==================================================] progress_2 100.00% 5.1s 100/100
[==================================================] progress_3 100.00% 5.1s 100/100

Test: Adding a sleep

I also tried to add a await sleep(0.002) together with 'interval: 1' in 'multiProgressCliRenderer.ts': Source File: https://github.com/codemonument/deno_downstream/blob/main/lib/streamAdapters/MultiProgressCliRenderer.ts

return new WritableStream({
    start(_controller) {
      // do init logic, if needed
    },
    async write(state: MultiProgressState, _controller) {
      await sleep(0.002);
      multibar.render(state);
    },
    close() {
    },
    abort(reason) {
      console.error("Stream error:", reason);
    },
  });

But this did not work.

Current State

  1. I think, my sleep is not working correctly here, since it might run in 'parallel' to all the other invocations of 'write' on this writable stream. => So I have to figure something out for that
  2. But I also think that the API for drawing here is inconvenient and that It would be nice to brainstorm with you how to improve at least error reporting, when render requests are dropped by the progress library because of being smaller than the interval.

bjesuiter avatar Feb 21 '23 20:02 bjesuiter

You can try interval: 0

const multibar = new MultiProgressBar({
  title: "Multi-Progress Bars",
  complete: "=",
  incomplete: "-",
  interval: 0,
  display: "[:bar] :text :percent :time :completed/:total",
});

I have been out of JS/TS for more than three years. I can only deal with simple issue. Your pull requests are welcome!

Suggestion

Replace sleep to delay(which is in Deno Standard Modules)

setTimeout in deno test will throw error

https://deno.com/blog/v1.20#tracing-operations-while-using-deno-test

Replace https://deno.land/x/sleep/mod.ts to https://deno.land/[email protected]/async/delay.ts

import { sleep } from "https://deno.land/x/sleep/mod.ts";

https://github.com/michael-spengler/sleep/blob/master/sleep.ts

export function sleep(seconds: number) {
  return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}

import { delay } from "https://deno.land/[email protected]/async/delay.ts";

delay is better(clearTimeout,Deno.unrefTimer).

Deno Standard Modules These modules do not have external dependencies and they are reviewed by the Deno core team. The intention is to have a standard set of high quality code that all Deno projects can use fearlessly.

https://github.com/denoland/deno_std/blob/main/async/delay.ts

// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

export interface DelayOptions {
  /** Signal used to abort the delay. */
  signal?: AbortSignal;
  /** Indicates whether the process should continue to run as long as the timer exists.
   *
   * @default {true}
   */
  persistent?: boolean;
}

/**
 * Resolve a Promise after a given amount of milliseconds.
 *
 * @example
 *
 * ```typescript
 * import { delay } from "https://deno.land/std@$STD_VERSION/async/delay.ts";
 *
 * // ...
 * const delayedPromise = delay(100);
 * const result = await delayedPromise;
 * // ...
 * ```
 *
 * To allow the process to continue to run as long as the timer exists. Requires
 * `--unstable` flag.
 *
 * ```typescript
 * import { delay } from "https://deno.land/std@$STD_VERSION/async/delay.ts";
 *
 * // ...
 * await delay(100, { persistent: false });
 * // ...
 * ```
 */
export function delay(ms: number, options: DelayOptions = {}): Promise<void> {
  const { signal, persistent } = options;
  if (signal?.aborted) {
    return Promise.reject(new DOMException("Delay was aborted.", "AbortError"));
  }
  return new Promise((resolve, reject) => {
    const abort = () => {
      clearTimeout(i);
      reject(new DOMException("Delay was aborted.", "AbortError"));
    };
    const done = () => {
      signal?.removeEventListener("abort", abort);
      resolve();
    };
    const i = setTimeout(done, ms);
    signal?.addEventListener("abort", abort, { once: true });
    if (persistent === false) {
      try {
        // @ts-ignore For browser compatibility
        Deno.unrefTimer(i);
      } catch (error) {
        if (!(error instanceof ReferenceError)) {
          throw error;
        }
        console.error("`persistent` option is only available in Deno");
      }
    }
  });
}

fuxingZhang avatar Feb 22 '23 03:02 fuxingZhang

Ok, I understand this:

I have been out of JS/TS for more than three years. I can only deal with simple issue. Your pull requests are welcome!

Thank you for your suggestions, I'll try to find and fix the problem! If I have anything useful, I'll give you a pr :)

bjesuiter avatar Feb 24 '23 12:02 bjesuiter