barkeep

repository·main·Indexed 42 days ago

https://github.com/oir/barkeep

A lightweight, single-header C++20 library and Python binding for asynchronous terminal animations, counters, and progress bars. Features include CounterDisplay, ProgressBarDisplay, IterableBar for range-based loops, and CompositeDisplay for monitoring multiple metrics concurrently. Supports RAII and explicit lifecycle control, custom formatting via fmt or std::format, and a no-tty mode for log-file compatibility.

Tokens
19K
Snippets
76
Records
100
Agent score
52%

What's inside barkeep

  1. Overview of barkeep classes

    main

    The barkeep namespace provides a suite of classes and structs designed for asynchronous, non-intrusive progress monitoring and display. Key capabilities include:

    • Progress Monitoring: CounterDisplay for numeric variables, ProgressBarDisplay for progress relative to a total, and IterableBar for automatic tracking of range-based loops.
    • Composite Displays: CompositeDisplay allows combining multiple child displays (e.g., a CounterDisplay and a ProgressBarDisplay) to monitor multiple variables concurrently.
    • Animations and Status: AnimationDisplay for simple messages and StatusDisplay for animations where the message can be updated dynamically.
    • Concurrency: AsyncDisplayer handles running display loops within a worker thread to ensure non-intrusive operation.
  2. Use barkeep::Speedometer to measure progress speed

    main

    The barkeep::Speedometer class is a helper template used to measure and display the rate of change (speed) of a numeric progress value.

    To use it, instantiate the class with a pointer to a numeric Progress object and a discount factor. The discount factor (range [0, 1]) determines how much weight is given to previous increments:

    • A discount of 0 weights all increments equally.
    • A discount of 1 considers only the most recent increment.
    • Values in between apply a weight of (1 - discount) to previous increments.

    Call start() to begin computing the speed based on changes in the progress object, and use speed() to retrieve the current rate or render_speed() to write it to an output stream.

    #include <barkeep.h>
    #include <iostream>
    
    // Assuming 'progress_val' is your numeric progress tracker
    double progress_val = 0.0;
    
    // Initialize Speedometer with progress pointer and a discount factor of 0.5
    barkeep::Speedometer<double> speedometer(&progress_val, 0.5);
    
    // Start computing speed
    speedometer.start();
    
    // ... update progress_val over time ...
    
    // Retrieve current speed
    double current_speed = speedometer.speed();
    
    // Render speed to stdout with a unit
    speedometer.render_speed(&std::cout, "items/s");
  3. Manage AnimationDisplay lifecycle with BaseDisplay methods

    main

    Since AnimationDisplay inherits from barkeep::BaseDisplay, you use the following methods to control the display lifecycle:

    • start(): Starts the display logic (e.g., measuring speed) without showing anything to the user.
    • show(): Starts the actual display/animation.
    • done(): Ends the display.
    • running(): Returns true if the display is currently active.
  4. Use barkeep::BaseDisplay for asynchronous displays

    main

    The barkeep::BaseDisplay class is the base class for all asynchronous displays in the library. It provides a common interface for managing the lifecycle of a display (starting, showing, and ending) and writing to an output stream.

    Common derived classes include:

    • barkeep::AnimationDisplay
    • barkeep::CompositeDisplay
    • barkeep::CounterDisplay< Progress >
    • barkeep::ProgressBarDisplay< Progress >

    To use a display, include <barkeep.h>.

    #include <barkeep.h>
    
    // Example usage pattern (conceptual):
    // barkeep::ProgressBarDisplay<float> display("Processing...");
    // display.show();
    // ... perform work ...
    // display.done();
  5. Important usage notes for barkeep

    main
    • Pointer Lifetime: Progress variables (and the total value for progress bars) are taken by pointer. You must ensure these variables outlive the display object.
    • Variable Types: Progress variables can be floating-point types. They can also be negative or decreasing (ensure the numeric type used can handle this without underflow).
    • Concurrency: The display runs on a separate thread and performs concurrent reads on your variables. Use std::atomic<T> if you need strict thread safety.
    • Configuration Syntax: The examples use C++20 designated initializers. For older C++ versions, initialize the configuration classes (e.g., ProgressBarConfig) manually.
  6. Caveat: Concurrent access to progress variables

    main

    Because the display runs on a separate thread, reading progress variables (e.g., i, total_chars) is a concurrent operation that races with your own modifications.

    While this is usually safe in practice (potentially resulting in a momentarily garbage value), if you require guaranteed non-racing reads and writes, you should use std::atomic<T> for your progress variables.

  7. Combine multiple displays using CompositeDisplay

    main

    You can show multiple displays concurrently by combining them into a CompositeDisplay. This is useful for monitoring multiple variables at once (e.g., a Counter and a ProgressBar).

    You can create a composite display in two ways:

    1. Using the barkeep::Composite factory function with a std::vector<std::shared_ptr<barkeep::BaseDisplay>>.
    2. Using the pipe operator | to combine two std::shared_ptr<barkeep::BaseDisplay> objects.

    The CompositeDisplay uses a delimiter (defaulting to a space) to separate the child displays.

    // Using the pipe operator to combine displays
    auto combined = display1 | display2;
    
    // Using the Composite factory function
    std::vector<std::shared_ptr<barkeep::BaseDisplay>> displays = {d1, d2, d3};
    auto composite = barkeep::Composite(displays, " | ");
  8. Compose multiple displays with `|` or `bk::Composite`

    main

    You can combine multiple displays (like a ProgressBar and a Counter) into a single multi-line display.

    1. Using the | operator: This is useful for simple horizontal or inline combinations. Note that components being composed should typically be initialized with .show = false to allow manual invocation of .show() on the composite object.
    2. Using bk::Composite: This allows you to pass a list of components and a delimiter (like \n) to create a structured multiline display.
    // Using the | operator
    std::atomic<size_t> sents{0}, toks{0};
    auto bar = bk::ProgressBar(&sents, {.total = 1010, .message = "Sents", .show = false}) |
               bk::Counter(&toks, {.message = "Toks", .speed = 1., .speed_unit = "tok/s", .show = false});
    bar->show();
    // ... work ...
    bar->done();
    
    // Using bk::Composite for multiline displays
    auto bars = bk::Composite({
        bk::ProgressBar(&linear, {.total = 100, .message = "Linear", .show = false}),
        bk::ProgressBar(&quad, {.total = 5050, .message = "Quadratic", .show = false})
    }, "\n");
    bars->show();
  9. Use CompositeDisplay to show multiple metrics

    main
    The CompositeDisplay class allows you to create a single unified display composed of multiple child displays. This is useful for monitoring different variables simultaneously, such as combining two CounterDisplay objects or a CounterDisplay with a ProgressBarDisplay.
  10. Combine multiple displays using the `|` operator

    main

    You can combine different display types (like a ProgressBar and a Counter) into a single composite display using the bitwise OR operator (|). This allows you to monitor multiple variables simultaneously in a single output line.

    Example of combining a progress bar for sentences and a counter for tokens:

    import random
    import time
    import barkeep as bk
    
    sents = bk.ProgressBar(total=1010, message="Sents")
    toks = bk.Counter(message="Toks", speed_unit="tok/s", speed=1.0)
    
    # Combine using | operator
    bar = sents | toks
    bar.show()
    
    for i in range(1010):
        time.sleep(0.013)
        sents += 1
        toks += 1 + random.randrange(5)
    
    bar.done()
  11. How barkeep's non-intrusive design works

    main

    barkeep is designed to be minimally intrusive by monitoring existing variables using pointers. Instead of requiring you to use custom data structures or specific increment methods (like operator++()), you simply pass the address of your existing variables (e.g., int, size_t, float) to bk::ProgressBar or bk::Counter.

    This allows you to add progress visualization to existing loops and logic with very little code change. The display runs on a separate concurrent thread, reading the values of the variables you provide.

    Key Lifecycle:

    1. Declare your progress variables (e.g., size_t i = 0;).
    2. Initialize the display objects (e.g., bk::ProgressBar(&i, ...)).
    3. Call .show() to start the display.
    4. Run your logic (the variables are updated normally in your code).
    5. Call .done() to stop the display.
    // Example of non-intrusive integration
    size_t chars = 0, tokens = 0, i = 0;
    
    auto bar = bk::ProgressBar(&i, {.total=docs.size(), .show=false}) |
               bk::Counter(&tokens, {.message="Tokens", .show=false}) |
               bk::Counter(&chars, {.message="Chars", .show=false});
    
    bar->show();
    for (i = 0; i < docs.size(); ++i) {
      process_document(docs[i], out, chars, tokens);
    }
    bar->done();
  12. How the `speed` discount factor works in `ProgressBarConfig`

    main

    The speed attribute in barkeep::ProgressBarConfig uses a discount factor in the range [0, 1] to calculate the progress rate. This factor determines how much weight is given to historical progress versus the most recent increment:

    • speed = 0: All increments are weighted equally (simple average).
    • speed = 1: Only the most recent increment is considered (most reactive).
    • 0 < speed < 1: Previous increments are weighted by (1 - speed).
    • speed = std::nullopt: Speed is not computed and will not be displayed.