Back to Index

Counter

Inherits from Task

Contents

The Counter class is a versatile component in the task framework that provides a signal-emitting numeric counter with configurable bounds. It extends the base Task class to integrate with the signal-slot system, allowing other components to react to counter value changes and limit events. The Counter maintains an integer value that can be incremented, decremented, or directly set, with optional minimum and maximum bounds to constrain its range.

Features

Class Interface

class Counter : public Task {
public:
    // Constructor
    Counter(int initialValue = 0, std::optional<int> minValue = std::nullopt,
            std::optional<int> maxValue = std::nullopt);
    
    // Value access and modification
    int getValue() const;
    bool setValue(int value);
    int increment(int amount = 1);
    int decrement(int amount = 1);
    int reset();
    
    // Range checking
    bool isAtMinimum() const;
    bool isAtMaximum() const;
    
    // Bound management
    std::optional<int> getMinValue() const;
    std::optional<int> getMaxValue() const;
    bool setMinValue(std::optional<int> min);
    bool setMaxValue(std::optional<int> max);
    
private:
    int m_value;
    int m_initialValue;
    std::optional<int> m_minValue;
    std::optional<int> m_maxValue;
    
    bool isInRange(int value) const;
    void emitChangeSignals(int oldValue, int newValue);
};

Signals

Counter emits the following signals:

Signal Type Description Arguments
valueChanged Data Emitted when the counter value changes oldValue (int), newValue (int)
limitReached Data Emitted when counter hits min or max limit isMinimum (bool), value (int)
reset Simple Emitted when counter is reset None
log Data Reports counter operations std::string (log message)
warn Data Reports warnings (e.g., out-of-range) std::string (warning message)

Usage Examples

Basic Counter Usage

Simple Counter Operations
// Create a counter with initial value 0, min 0, max 10
auto counter = std::make_shared<Counter>(0, 0, 10);

// Connect to signals
counter->connectData("valueChanged", [](const ArgumentPack& args) {
    int oldValue = args.get<int>(0);
    int newValue = args.get<int>(1);
    std::cout << "Counter changed from " << oldValue 
              << " to " << newValue << std::endl;
});

counter->connectData("limitReached", [](const ArgumentPack& args) {
    bool isMin = args.get<bool>(0);
    int value = args.get<int>(1);
    std::cout << "Counter reached " << (isMin ? "minimum" : "maximum") 
              << " value: " << value << std::endl;
});

counter->connectSimple("reset", []() {
    std::cout << "Counter was reset" << std::endl;
});

// Use the counter
counter->increment(5);     // 0 -> 5
counter->increment(10);    // 5 -> 10 (max)
std::cout << "Is at max: " << counter->isAtMaximum() << std::endl;
counter->decrement(5);     // 10 -> 5
counter->setValue(0);      // 5 -> 0 (min)
std::cout << "Is at min: " << counter->isAtMinimum() << std::endl;
counter->reset();          // Resets to initial value (0)

Using Counter with a Progress Bar

Progress Tracking Example
// Create a counter for tracking progress (0-100)
auto progress = std::make_shared<Counter>(0, 0, 100);
auto progressBar = std::make_shared<ProgressBar>();  // Hypothetical component

// Connect counter changes to update progress bar
progress->connectData("valueChanged", [progressBar](const ArgumentPack& args) {
    int newValue = args.get<int>(1);
    progressBar->setProgress(newValue / 100.0f);
});

// Connect to limit signal to detect completion
progress->connectData("limitReached", [](const ArgumentPack& args) {
    bool isMin = args.get<bool>(0);
    if (!isMin) {  // Max reached
        std::cout << "Process completed 100%" << std::endl;
    }
});

// Update progress as work is done
for (int i = 0; i <= 10; i++) {
    // Do work...
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // Update progress (0-100)
    progress->setValue(i * 10);
}

Counter with Different Constraint Types

Various Counter Configurations
// Counter with no constraints
auto unboundedCounter = std::make_shared<Counter>(0);

// Counter with only minimum value (>= 0)
auto nonNegativeCounter = std::make_shared<Counter>(0, 0, std::nullopt);

// Counter with only maximum value (<= 100)
auto limitedCounter = std::make_shared<Counter>(0, std::nullopt, 100);

// Counter with both min and max values (0-100)
auto boundedCounter = std::make_shared<Counter>(0, 0, 100);

// Test with various operations
unboundedCounter->increment(10);  // No limit
unboundedCounter->decrement(20);  // Can go negative

nonNegativeCounter->decrement(20);  // Will clamp to 0
std::cout << nonNegativeCounter->getValue() << std::endl;  // Outputs: 0

limitedCounter->increment(200);   // Will clamp to 100
std::cout << limitedCounter->getValue() << std::endl;  // Outputs: 100

// Dynamically changing bounds
boundedCounter->setMaxValue(50);  // Change upper bound to 50
if(boundedCounter->getValue() > 50) {
    std::cout << "Value was automatically adjusted to maximum" << std::endl;
}

Range Constraints

The Counter supports optional minimum and maximum value constraints:

When changing bounds, the counter ensures that min <= max and adjusts the current value if needed to stay within the new bounds.

Range Constraint Example
// Configure bounds and observe behavior
void demonstrateBounds() {
    auto counter = std::make_shared<Counter>(50);  // Start at 50, no bounds
    
    // Initially no bounds
    counter->setValue(1000);  // Works fine
    counter->setValue(-1000); // Works fine
    
    // Set a minimum
    counter->setMinValue(0);
    counter->setValue(-10);   // Won't go below 0
    std::cout << "After min constraint: " << counter->getValue() << std::endl;  // 0
    
    // Set a maximum
    counter->setMaxValue(100);
    counter->setValue(200);   // Won't go above 100
    std::cout << "After max constraint: " << counter->getValue() << std::endl;  // 100
    
    // Try setting min > max (should fail)
    bool success = counter->setMinValue(200);
    std::cout << "Set min > max success: " << success << std::endl;  // false
    
    // Remove constraints
    counter->setMinValue(std::nullopt);
    counter->setMaxValue(std::nullopt);
    counter->setValue(-500);  // Now works
    std::cout << "After removing constraints: " << counter->getValue() << std::endl;  // -500
}

Value Validation

The Counter validates values against configured bounds:

The isInRange(int value) private method checks if a value is within the configured bounds.

Signal Emission on Changes

The Counter emits signals when its value changes:

  1. valueChanged: Emitted whenever the value changes, with old and new values
  2. limitReached: Emitted when the counter reaches minimum or maximum limit
  3. reset: Emitted when the counter is reset to its initial value

The emitChangeSignals(int oldValue, int newValue) private method handles all signal emissions related to value changes.

Signal Handling Example
// Set up a counter with comprehensive signal handling
void monitorCounter() {
    auto counter = std::make_shared<Counter>(5, 0, 10);
    
    // Track all value changes
    counter->connectData("valueChanged", [](const ArgumentPack& args) {
        int oldValue = args.get<int>(0);
        int newValue = args.get<int>(1);
        std::cout << "Value changed: " << oldValue << " -> " << newValue << std::endl;
    });
    
    // Special handling for limit events
    counter->connectData("limitReached", [](const ArgumentPack& args) {
        bool isMin = args.get<bool>(0);
        int value = args.get<int>(1);
        
        if (isMin) {
            std::cout << "⚠️ Minimum limit reached: " << value << std::endl;
        } else {
            std::cout << "🏁 Maximum limit reached: " << value << std::endl;
        }
    });
    
    // Monitor reset events
    counter->connectSimple("reset", []() {
        std::cout << "Counter has been reset" << std::endl;
    });
    
    // Test various operations
    counter->increment(3);   // 5 -> 8
    counter->increment(5);   // 8 -> 10 (max)
    counter->decrement(15);  // 10 -> 0 (min)
    counter->reset();        // 0 -> 5
}

Initial Value Management

The Counter maintains an initial value that can be reset to:

Initial Value Example
// Initial value scenarios
void initialValueDemo() {
    // Normal case - initial value within bounds
    auto counter1 = std::make_shared<Counter>(5, 0, 10);
    
    // Initial value outside bounds - will be adjusted
    auto counter2 = std::make_shared<Counter>(20, 0, 10);
    std::cout << "Initial value adjusted: " << counter2->getValue() << std::endl;  // 10
    
    // Initial value becomes new value after bounds adjustment
    counter2->reset();
    std::cout << "After reset: " << counter2->getValue() << std::endl;  // 10, not 20
    
    // Setting bounds after creation that conflict with initial value
    auto counter3 = std::make_shared<Counter>(50);
    counter3->setValue(100);
    counter3->setMaxValue(75);  // Current value (100) will be adjusted to 75
    counter3->reset();
    std::cout << "Reset goes to original: " << counter3->getValue() << std::endl;  // 50
}

Integration with Other Components

The Counter can be integrated with various components:

With ProgressBar

Progress Tracking
// Use counter to drive progress visualization
void showProgress(std::shared_ptr<ProgressBar> progressBar) {
    auto counter = std::make_shared<Counter>(0, 0, 100);
    
    // Connect counter to progress bar
    counter->connectData("valueChanged", [progressBar](const ArgumentPack& args) {
        int newValue = args.get<int>(1);
        float progress = newValue / 100.0f;
        progressBar->setProgress(progress);
    });
    
    // Simulate work with progress updates
    for (int i = 0; i <= 10; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
        counter->setValue(i * 10);
    }
}

With For Loop

Iteration Counting
// Use For loop with Counter
void countedIteration() {
    auto counter = std::make_shared<Counter>(0);
    auto loop = std::make_shared<For>(ForParameters(0, 10, 1));
    
    // Connect loop ticks to counter
    loop->connectData("tick", [counter](const ArgumentPack& args) {
        int current = args.get<int>(2);  // Current iteration value
        counter->setValue(current);
    });
    
    // Run the loop
    loop->start();
    
    std::cout << "Final counter value: " << counter->getValue() << std::endl;
}

With StateMachine

State Transitions with Counter
// Hypothetical StateMachine integration
void stateMachineExample(std::shared_ptr<StateMachine> stateMachine) {
    auto counter = std::make_shared<Counter>(0, 0, 5);
    
    // Trigger state transitions based on counter values
    counter->connectData("valueChanged", [stateMachine](const ArgumentPack& args) {
        int newValue = args.get<int>(1);
        
        switch (newValue) {
            case 1:
                stateMachine->transition("initialize");
                break;
            case 3:
                stateMachine->transition("process");
                break;
            case 5:
                stateMachine->transition("complete");
                break;
        }
    });
    
    // Increment counter to trigger transitions
    for (int i = 1; i <= 5; i++) {
        counter->setValue(i);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
}

Best Practices

Thread Safety

The Counter class provides thread-safe operations for concurrent access:

Note: While individual operations are thread-safe, sequences of operations may still require external synchronization for correctness in multi-threaded environments.

Implementation Details