From 7a5df8238a5fd1d1b0a7b3bae3e5b35d781a91ce Mon Sep 17 00:00:00 2001
From: rstein <r.steinhagen@gsi.de>
Date: Tue, 14 Jan 2025 12:54:27 +0100
Subject: [PATCH] HistoryBuffer<T> flipped push_back<->push_front naming

should (hopefully) simplify and provide a more intuitive the naming.

### Storage
 - Dynamic (`N == std::dynamic_extent`): uses `std::vector<T>` (size = 2 * capacity).
 - Fixed (`N != std::dynamic_extent`): uses `std::array<T, N * 2>`.

### Key Operations
 - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is newest.
 - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest.
 - `pop_front()`, `pop_back()`: Remove from logical front/back of the ring.
 - `front()`, `back()`: Returns the first/last item in logical order.
 - `operator[](i)`, `at(i)`: Unchecked/checked access.
 - `resize(...)` (dynamic-only): Adjust capacity, preserving existing data.
 - `get_span(...)`: Obtain a contiguous view across wrap boundaries.

 ### Code Examples
 ```cpp
 gr::history_buffer<int, 5> hb_newest;   // use push_front -> index[0] is newest
 hb_newest.push_front(1); // [1]
 hb_newest.push_front(2); // [2, 1]
 hb_newest.push_front(3); // [3, 2, 1]
 // => index[0] == 3, newest item

gr::history_buffer<int, 5> hb_oldest;   // use push_back -> index[0] is oldest
hb_oldest.push_back(10); // [10]
hb_oldest.push_back(20); // [10, 20]
hb_oldest.push_back(30); // [10, 20, 30]
// => index[0] == 10, oldest item

hb_newest.pop_front();  // remove newest => now hb_newest: [2, 1]
hb_oldest.pop_front();  // remove oldest => now hb_oldest: [20, 30]

auto val = hb_newest.front();  // val == 2
auto last = hb_newest.back();  // last == 1
 ```

 Also added/extended te `HistoryBuffer<T>` performance benchmark

Signed-off-by: rstein <r.steinhagen@gsi.de>
Signed-off-by: Ralph J. Steinhagen <r.steinhagen@gsi.de>
---
 .../gnuradio-4.0/algorithm/SchmittTrigger.hpp |  6 +-
 .../algorithm/filter/FilterTool.hpp           | 20 +++---
 algorithm/test/qa_FilterTool.cpp              |  2 +-
 .../include/gnuradio-4.0/basic/DataSink.hpp   |  4 +-
 .../gnuradio-4.0/basic/StreamToDataSet.hpp    |  6 +-
 .../include/gnuradio-4.0/basic/SyncBlock.hpp  |  1 -
 .../electrical/PowerEstimators.hpp            |  1 -
 .../filter/time_domain_filter.hpp             | 14 ++--
 .../gnuradio-4.0/testing/ImChartMonitor.hpp   | 29 ++++----
 core/benchmarks/bm_HistoryBuffer.cpp          | 53 +++++++++++++--
 core/include/gnuradio-4.0/HistoryBuffer.hpp   | 66 ++++++++-----------
 core/include/gnuradio-4.0/Scheduler.hpp       |  2 +-
 core/test/qa_buffer.cpp                       | 44 ++++++-------
 13 files changed, 133 insertions(+), 115 deletions(-)

diff --git a/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp b/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp
index bcfadee3..5c1f53f9 100644
--- a/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp
+++ b/algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp
@@ -103,7 +103,7 @@ struct SchmittTrigger {
         }
 
         if constexpr (Method == BASIC_LINEAR_INTERPOLATION) {
-            _historyBuffer.push_back(input);
+            _historyBuffer.push_front(input);
             if (_historyBuffer.size() < 2) {
                 return EdgeDetection::NONE;
             }
@@ -113,7 +113,7 @@ struct SchmittTrigger {
 
             auto computeEdgePosition = [&](const EdgeType& y1, const EdgeType& y2) -> std::pair<std::int32_t, EdgeType> {
                 if (y1 == y2) {
-                    return {static_cast<std::int32_t>(0), static_cast<EdgeType>(0)};
+                    return {static_cast<std::int32_t>(0U), static_cast<EdgeType>(0)};
                 }
                 const EdgeType offset   = (EdgeType(_offset) - y1) / (y2 - y1);
                 std::int32_t   intPart  = static_cast<std::int32_t>(std::floor(gr::value(offset)));
@@ -145,7 +145,7 @@ struct SchmittTrigger {
         }
 
         if constexpr (Method == LINEAR_INTERPOLATION) {
-            _historyBuffer.push_back(input);
+            _historyBuffer.push_front(input);
 
             if (_historyBuffer.size() < 2) {
                 return EdgeDetection::NONE;
diff --git a/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp b/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp
index b146a798..b288ef04 100644
--- a/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp
+++ b/algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp
@@ -124,35 +124,35 @@ template<typename T, std::size_t bufferSize, Form form = std::is_floating_point_
     if constexpr (form == Form::DF_I) {
         // y[n] = b[0]·x[n]   + b[1]·x[n-1] + … + b[N]·x[n-N]
         //      - a[1]·y[n-1] - a[2]·y[n-2] - … - a[M]·y[n-M]
-        inputHistory.push_back(input);
+        inputHistory.push_front(input);
         T output = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>())                // feed-forward path
                    - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()); // feedback path
-        outputHistory.push_back(output);
+        outputHistory.push_front(output);
         return output;
     } else if constexpr (form == Form::DF_II) {
         // w[n] = x[n] - a[1]·w[n-1] - a[2]·w[n-2] - … - a[M]·w[n-M]
         // y[n] =        b[0]·w[n]   + b[1]·w[n-1] + … + b[N]·w[n-N]
         if (a.size() > 1) {
             const T w = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
-            inputHistory.push_back(w);
+            inputHistory.push_front(w);
             return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
         } else {
-            inputHistory.push_back(input);
+            inputHistory.push_front(input);
             return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
         }
     } else if constexpr (form == Form::DF_I_TRANSPOSED) {
         // w_1[n] = x[n] - a[1]·w_2[n-1] - a[2]·w_2[n-2] - … - a[M]·w_2[n-M]
         // y[n]   = b[0]·w_2[n] + b[1]·w_2[n-1] + … + b[N]·w_2[n-N]
         T v0 = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>());
-        outputHistory.push_back(v0);
+        outputHistory.push_front(v0);
         return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
     } else if constexpr (form == Form::DF_II_TRANSPOSED) {
         // y[n] = b_0·f[n] + \sum_(k=1)^N(b_k·f[n−k] − a_k·y[n−k])
         T output = b[0] * input                                                                                                                                       //
                    + std::transform_reduce(execPolicy, std::next(b.cbegin()), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()) //
                    - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>());
-        inputHistory.push_back(input);
-        outputHistory.push_back(output);
+        inputHistory.push_front(input);
+        outputHistory.push_front(output);
         return output;
     } else {
         static_assert(gr::meta::always_false<T>, "should not reach here");
@@ -276,12 +276,12 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> {
         const auto& autocorrelationFunction = section.autoCorrelation;
 
         // Feed-forward path (uncorrelated uncertainties)
-        inputHistory.push_back(inputUncertainty * inputUncertainty);
+        inputHistory.push_front(inputUncertainty * inputUncertainty);
         TBaseType feedForwardUncertainty = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<TBaseType>(0), //
             std::plus<>(), [](TBaseType bVal, TBaseType sigma2) { return bVal * bVal * sigma2; });
 
         if (a.size() <= 1 || autocorrelationFunction.empty()) {
-            outputHistory.push_back(feedForwardUncertainty);
+            outputHistory.push_front(feedForwardUncertainty);
             return feedForwardUncertainty;
         }
 
@@ -296,7 +296,7 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> {
         }
 
         TBaseType totalUncertainty = feedForwardUncertainty + feedbackUncertainty;
-        outputHistory.push_back(totalUncertainty);
+        outputHistory.push_front(totalUncertainty);
         return totalUncertainty;
     }
 
diff --git a/algorithm/test/qa_FilterTool.cpp b/algorithm/test/qa_FilterTool.cpp
index 86c928d0..1f4f52ca 100644
--- a/algorithm/test/qa_FilterTool.cpp
+++ b/algorithm/test/qa_FilterTool.cpp
@@ -839,7 +839,7 @@ const boost::ut::suite<"IIR & FIR Benchmarks"> filterBenchmarks = [] {
             gr::HistoryBuffer<T, 8> buffer;
             ::benchmark::benchmark<10>(fmt::format("HistoryBuffer<{}>", gr::meta::type_name<T>()), nSamples) = [&actualGain, &buffer, &yValues] {
                 for (auto& yValue : yValues) {
-                    buffer.push_back(yValue);
+                    buffer.push_front(yValue);
                     actualGain = std::max(actualGain, buffer[0]);
                 }
             };
diff --git a/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp b/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp
index 5497abef..f2c06703 100644
--- a/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp
+++ b/blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp
@@ -591,7 +591,7 @@ This block type is mean for non-data set input streams. For input streams of typ
                 listener->process(historyView, inData, tagData);
             }
             if (_history) {
-                _history->push_back_bulk(inData.begin(), inData.end());
+                _history->push_front(inData);
             }
         }
         return work::Status::OK;
@@ -609,7 +609,7 @@ This block type is mean for non-data set input streams. For input streams of typ
 
         auto new_history = gr::HistoryBuffer<T>(new_size);
         if (_history) {
-            new_history.push_back_bulk(_history->begin(), _history->end());
+            new_history.push_front(_history->begin(), _history->end());
         }
         _history = std::move(new_history);
     }
diff --git a/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp b/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp
index 0b93c4eb..c165b2f9 100644
--- a/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp
+++ b/blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp
@@ -124,9 +124,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS
                     throw gr::exception("n_pre must be <= output port CircularBuffer size");
                 }
             }
-            auto newBuffer = HistoryBuffer<T>(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre")));
-            newBuffer.push_back_bulk(_history);
-            _history = std::move(newBuffer);
+            _history.resize(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre")));
         }
 
         if constexpr (!streamOut) {
@@ -321,7 +319,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS
     void copyInputSamplesToHistory(InputSpanLike auto& inSamples, std::size_t maxSamplesToCopy) {
         if (n_pre > 0) {
             const auto samplesToCopy = std::min(maxSamplesToCopy, inSamples.size());
-            _history.push_back_bulk(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy)));
+            _history.push_front(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy)));
         }
     }
 
diff --git a/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp b/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp
index 2d94cb30..0fd75c87 100644
--- a/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp
+++ b/blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp
@@ -2,7 +2,6 @@
 #define GNURADIO_SYNC_BLOCK_HPP
 
 #include <gnuradio-4.0/Block.hpp>
-#include <gnuradio-4.0/HistoryBuffer.hpp>
 
 namespace gr::basic {
 
diff --git a/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp b/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp
index 4ecca564..ae37f74f 100644
--- a/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp
+++ b/blocks/electrical/include/gnuradio-4.0/electrical/PowerEstimators.hpp
@@ -9,7 +9,6 @@
 #include <gnuradio-4.0/Block.hpp>
 #include <gnuradio-4.0/BlockRegistry.hpp>
 
-#include <gnuradio-4.0/HistoryBuffer.hpp>
 #include <gnuradio-4.0/algorithm/filter/FilterTool.hpp>
 #include <gnuradio-4.0/meta/UncertainValue.hpp>
 
diff --git a/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp b/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp
index 8eaa65a0..f5c3c413 100644
--- a/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp
+++ b/blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp
@@ -42,7 +42,7 @@ H(z) = b[0] + b[1]*z^-1 + b[2]*z^-2 + ... + b[N]*z^-N
     }
 
     constexpr T processOne(T input) noexcept {
-        inputHistory.push_back(input);
+        inputHistory.push_front(input);
         return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
     }
 };
@@ -85,32 +85,32 @@ a are the feedback coefficients
         if constexpr (form == IIRForm::DF_I) {
             // y[n] = b[0] * x[n]   + b[1] * x[n-1] + ... + b[N] * x[n-N]
             //      - a[1] * y[n-1] - a[2] * y[n-2] - ... - a[M] * y[n-M]
-            inputHistory.push_back(input);
+            inputHistory.push_front(input);
             const T feedforward = std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
             const T feedback    = std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
             const T output      = feedforward - feedback;
-            outputHistory.push_back(output);
+            outputHistory.push_front(output);
             return output;
         } else if constexpr (form == IIRForm::DF_II) {
             // w[n] = x[n] - a[1] * w[n-1] - a[2] * w[n-2] - ... - a[M] * w[n-M]
             // y[n] =        b[0] * w[n]   + b[1] * w[n-1] + ... + b[N] * w[n-N]
             const T w = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
-            inputHistory.push_back(w);
+            inputHistory.push_front(w);
 
             return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
         } else if constexpr (form == IIRForm::DF_I_TRANSPOSED) {
             // w_1[n] = x[n] - a[1] * w_2[n-1] - a[2] * w_2[n-2] - ... - a[M] * w_2[n-M]
             // y[n]   = b[0] * w_2[n] + b[1] * w_2[n-1] + ... + b[N] * w_2[n-N]
             const T v0 = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
-            outputHistory.push_back(v0);
+            outputHistory.push_front(v0);
 
             return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
         } else if constexpr (form == IIRForm::DF_II_TRANSPOSED) {
             // y[n] = b_0 * f[n] + Σ (b_k * f[n−k] − a_k * y[n−k]) for k = 1 to N
             const T output = b[0] * input + std::transform_reduce(std::execution::unseq, b.cbegin() + 1, b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}) - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
 
-            inputHistory.push_back(input);
-            outputHistory.push_back(output);
+            inputHistory.push_front(input);
+            outputHistory.push_front(output);
             return output;
         }
     }
diff --git a/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp b/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp
index d93a5e3c..8a0c908d 100644
--- a/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp
+++ b/blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp
@@ -24,9 +24,9 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
 
     GR_MAKE_REFLECTABLE(ImChartMonitor, in, sample_rate, signal_name);
 
-    HistoryBuffer<T>   _historyBufferX{1000};
-    HistoryBuffer<T>   _historyBufferY{1000};
-    HistoryBuffer<Tag> _historyBufferTags{1000};
+    HistoryBuffer<T> _historyBufferX{1000UZ};
+    HistoryBuffer<T> _historyBufferY{1000UZ};
+    HistoryBuffer<T> _historyBufferTags{1000UZ};
 
     void start() {
         fmt::println("started sink {} aka. '{}'", this->unique_name, this->name);
@@ -44,11 +44,14 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
         _historyBufferY.push_back(input);
 
         if (this->inputTagsPresent()) { // received tag
-            _historyBufferTags.push_back(this->mergedInputTag());
-            _historyBufferTags[1].index = 0;
+            _historyBufferTags.push_back(_historyBufferY.back());
             this->_mergedInputTag.map.clear(); // TODO: provide proper API for clearing tags
         } else {
-            _historyBufferTags.push_back(Tag(0UZ, property_map()));
+            if constexpr (std::is_floating_point_v<T>) {
+                _historyBufferTags.push_back(std::numeric_limits<T>::quiet_NaN());
+            } else {
+                _historyBufferTags.push_back(std::numeric_limits<T>::lowest());
+            }
         }
     }
 
@@ -66,16 +69,6 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
                 gr::graphs::resetView();
             }
 
-            // create reversed copies -- draw(...) expects std::ranges::input_range ->
-            std::vector<T> reversedX(_historyBufferX.rbegin(), _historyBufferX.rend());
-            std::vector<T> reversedY(_historyBufferY.rbegin(), _historyBufferY.rend());
-            std::vector<T> reversedTag(_historyBufferX.size());
-            if constexpr (std::is_floating_point_v<T>) {
-                std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::quiet_NaN() : yValue; });
-            } else {
-                std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::lowest() : yValue; });
-            }
-
             auto adjustRange = [](T min, T max) {
                 min            = std::min(min, T(0));
                 max            = std::max(max, T(0));
@@ -84,8 +77,8 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
             };
 
             auto chart = gr::graphs::ImChart<130, 28>({{*xMin, *xMax}, adjustRange(*yMin, *yMax)});
-            chart.draw(reversedX, reversedY, signal_name);
-            chart.draw<gr::graphs::Style::Marker>(reversedX, reversedTag, "Tags");
+            chart.draw(_historyBufferX, _historyBufferY, signal_name);
+            chart.draw<gr::graphs::Style::Marker>(_historyBufferX, _historyBufferTags, "Tags");
             chart.draw();
         } else if constexpr (gr::DataSetLike<T>) {
             if (_historyBufferY.empty()) {
diff --git a/core/benchmarks/bm_HistoryBuffer.cpp b/core/benchmarks/bm_HistoryBuffer.cpp
index 1094e70d..c47d98b9 100644
--- a/core/benchmarks/bm_HistoryBuffer.cpp
+++ b/core/benchmarks/bm_HistoryBuffer.cpp
@@ -91,30 +91,51 @@ inline const boost::ut::suite _buffer_tests = [] {
     {
         HistoryBuffer<int> buffer(32);
 
-        "history_buffer<int>(32)"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+        "history_buffer<int>(32) - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
             static int counter = 0;
             for (std::size_t i = 0; i < samples; i++) {
-                buffer.push_back(counter);
+                buffer.push_front(counter);
                 if (const auto data = buffer[0] != counter) {
                     throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data));
                 }
                 counter++;
             }
         };
+        "history_buffer<int>(32) - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+            static int counter = 0;
+            for (std::size_t i = 0; i < samples; i++) {
+                buffer.push_back(counter);
+                if (const auto data = buffer[buffer.size() - 1UZ] != counter) {
+                    throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data));
+                }
+                counter++;
+            }
+        };
     }
     {
         HistoryBuffer<int> buffer(32);
 
-        "history_buffer<int, 32>"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+        "history_buffer<int, 32> - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
             static int counter = 0;
             for (std::size_t i = 0; i < samples; i++) {
-                buffer.push_back(counter);
+                buffer.push_front(counter);
                 if (const auto data = buffer[0] != counter) {
                     throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data));
                 }
                 counter++;
             }
         };
+
+        "history_buffer<int, 32> - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+            static int counter = 0;
+            for (std::size_t i = 0; i < samples; i++) {
+                buffer.push_back(counter);
+                if (const auto data = buffer[buffer.size() - 1UZ] != counter) {
+                    throw std::runtime_error(fmt::format("write {} read {} mismatch", counter, data));
+                }
+                counter++;
+            }
+        };
     }
 
     {
@@ -140,7 +161,16 @@ inline const boost::ut::suite _buffer_tests = [] {
     {
         HistoryBuffer<int, 32> buffer;
 
-        "history_buffer<int, 32>  - no checks"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+        "history_buffer<int, 32>  - no checks - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+            static int counter = 0;
+            for (std::size_t i = 0; i < samples; i++) {
+                buffer.push_front(counter);
+                [[maybe_unused]] const auto data = buffer[0];
+                counter++;
+            }
+        };
+
+        "history_buffer<int, 32>  - no checks - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
             static int counter = 0;
             for (std::size_t i = 0; i < samples; i++) {
                 buffer.push_back(counter);
@@ -152,7 +182,16 @@ inline const boost::ut::suite _buffer_tests = [] {
     {
         HistoryBuffer<int> buffer(32);
 
-        "history_buffer<int>(32)  - no checks"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+        "history_buffer<int>(32)  - no checks - push_front"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
+            static int counter = 0;
+            for (std::size_t i = 0; i < samples; i++) {
+                buffer.push_front(counter);
+                [[maybe_unused]] const auto data = buffer[0];
+                counter++;
+            }
+        };
+
+        "history_buffer<int>(32)  - no checks - push_back"_benchmark.repeat<n_repetitions>(samples) = [&buffer] {
             static int counter = 0;
             for (std::size_t i = 0; i < samples; i++) {
                 buffer.push_back(counter);
@@ -163,4 +202,4 @@ inline const boost::ut::suite _buffer_tests = [] {
     }
 };
 
-int main() { /* not needed by the UT framework */ }
\ No newline at end of file
+int main() { /* not needed by the UT framework */ }
diff --git a/core/include/gnuradio-4.0/HistoryBuffer.hpp b/core/include/gnuradio-4.0/HistoryBuffer.hpp
index 49d5cce5..5dd80de8 100644
--- a/core/include/gnuradio-4.0/HistoryBuffer.hpp
+++ b/core/include/gnuradio-4.0/HistoryBuffer.hpp
@@ -23,8 +23,8 @@ namespace gr {
  * - Fixed (`N != std::dynamic_extent`): uses `std::array<T, N * 2>`.
  *
  * ### Key Operations
- * - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is newest.
- * - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest.
+ * - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is newest.
+ * - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest.
  * - `pop_front()`, `pop_back()`: Remove from logical front/back of the ring.
  * - `front()`, `back()`: Returns the first/last item in logical order.
  * - `operator[](i)`, `at(i)`: Unchecked/checked access.
@@ -33,16 +33,16 @@ namespace gr {
  *
  * ### Examples
  * \code{.cpp}
- * gr::history_buffer<int, 5> hb_newest;   // Use push_back -> index[0] is newest
- * hb_newest.push_back(1); // [1]
- * hb_newest.push_back(2); // [2, 1]
- * hb_newest.push_back(3); // [3, 2, 1]
+ * gr::history_buffer<int, 5> hb_newest;   // use push_front -> index[0] is newest
+ * hb_newest.push_front(1); // [1]
+ * hb_newest.push_front(2); // [2, 1]
+ * hb_newest.push_front(3); // [3, 2, 1]
  * // => index[0] == 3, newest item
  *
- * gr::history_buffer<int, 5> hb_oldest;   // Use push_front -> index[0] is oldest
- * hb_oldest.push_front(10); // [10]
- * hb_oldest.push_front(20); // [10, 20]
- * hb_oldest.push_front(30); // [10, 20, 30]
+ * gr::history_buffer<int, 5> hb_oldest;   // use push_back -> index[0] is oldest
+ * hb_oldest.push_back(10); // [10]
+ * hb_oldest.push_back(20); // [10, 20]
+ * hb_oldest.push_back(30); // [10, 20, 30]
  * // => index[0] == 10, oldest item
  *
  * hb_newest.pop_front();  // remove newest => now hb_newest: [2, 1]
@@ -61,7 +61,7 @@ class HistoryBuffer {
 
     buffer_type _buffer{};
     std::size_t _capacity = N;
-    std::size_t _write_position{0};
+    std::size_t _write_position{0UZ};
     std::size_t _size{0UZ};
 
     /**
@@ -98,7 +98,7 @@ class HistoryBuffer {
     /**
      * @brief Adds an element to the end expiring the oldest element beyond the buffer's capacities.
      */
-    constexpr void push_back(const T& value) noexcept {
+    constexpr void push_front(const T& value) noexcept {
         if (_size < _capacity) [[unlikely]] {
             ++_size;
         }
@@ -114,36 +114,26 @@ class HistoryBuffer {
      * @brief Adds a range of elements the end expiring the oldest elements beyond the buffer's capacities.
      */
     template<std::input_iterator Iter>
-    constexpr void push_back(Iter cbegin, Iter cend) noexcept {
-        const auto nSamplesToCopy = std::distance(cbegin, cend);
-        Iter       optimizedBegin = static_cast<std::size_t>(nSamplesToCopy) > _capacity ? std::prev(cend, static_cast<std::ptrdiff_t>(_capacity)) : cbegin;
-        for (auto it = optimizedBegin; it != cend; ++it) {
-            push_back(*it);
+    constexpr void push_front(Iter begin, Iter end) noexcept {
+        const auto nSamplesToCopy = std::distance(begin, end);
+        Iter       optimizedBegin = static_cast<std::size_t>(nSamplesToCopy) > _capacity ? std::prev(end, static_cast<std::ptrdiff_t>(_capacity)) : begin;
+        for (auto it = optimizedBegin; it != end; ++it) {
+            push_front(*it);
         }
     }
 
-    template<std::input_iterator Iter>
-    constexpr void push_back_bulk(Iter cbegin, Iter cend) noexcept {
-        return push_back(cbegin, cend);
-    }
-
     /**
      * @brief Adds a range of elements the end expiring the oldest elements beyond the buffer's capacities.
      */
     template<std::ranges::range Range>
-    constexpr void push_back(const Range& range) noexcept {
-        push_back_bulk(std::ranges::begin(range), std::ranges::end(range));
-    }
-
-    template<std::ranges::range Range>
-    constexpr void push_back_bulk(const Range& range) noexcept {
-        push_back_bulk(range.cbegin(), range.cend());
+    constexpr void push_front(const Range& range) noexcept {
+        push_front(std::ranges::begin(range), std::ranges::end(range));
     }
 
     /**
      * @brief Adds an element to the front expiring the oldest element beyond the buffer's capacities.
      */
-    constexpr void push_front(const T& value) noexcept {
+    constexpr void push_back(const T& value) noexcept {
         if (_size == _capacity) {
             if (++_write_position == _capacity) {
                 _write_position = 0U;
@@ -166,7 +156,7 @@ class HistoryBuffer {
      *        the "newest" in the ring, with operator[](0) always the oldest.
      */
     template<std::input_iterator Iter>
-    constexpr void push_front(Iter first, Iter last) noexcept {
+    constexpr void push_back(Iter first, Iter last) noexcept {
         std::size_t n = static_cast<std::size_t>(std::distance(first, last));
         if (n == 0) {
             return;
@@ -212,8 +202,8 @@ class HistoryBuffer {
      *        the "newest" in the ring, with operator[](0) always the oldest.
      */
     template<std::ranges::range Range>
-    constexpr void push_front(const Range& r) noexcept {
-        push_front(std::ranges::begin(r), std::ranges::end(r));
+    constexpr void push_back(const Range& r) noexcept {
+        push_back(std::ranges::begin(r), std::ranges::end(r));
     }
 
     /**
@@ -222,8 +212,8 @@ class HistoryBuffer {
      * @throws std::out_of_range if the buffer is empty.
      *
      * @note
-     * - If you only call `push_back(...)`, `operator[](0)` is the newest element; hence `pop_front()` removes the newest.
-     * - If you only call `push_front(...)`, `operator[](0)` is the oldest element; hence `pop_front()` removes the oldest.
+     * - If you only call `push_front(...)`, `operator[](0)` is the newest element; hence `pop_front()` removes the newest.
+     * - If you only call `push_back(...)`, `operator[](0)` is the oldest element; hence `pop_front()` removes the oldest.
      * - Mixing both push modes can lead to non-intuitive behavior for front/back usage.
      */
     constexpr void pop_front() {
@@ -250,8 +240,8 @@ class HistoryBuffer {
      * @throws std::out_of_range if the buffer is empty.
      *
      * @note
-     * - If you only call `push_back(...)`, `operator[](size() - 1)` is the oldest element; hence `pop_back()` removes the oldest.
-     * - If you only call `push_front(...)`, `operator[](size() - 1)` is the newest element; hence `pop_back()` removes the newest.
+     * - If you only call `push_front(...)`, `operator[](size() - 1)` is the oldest element; hence `pop_back()` removes the oldest.
+     * - If you only call `push_back(...)`, `operator[](size() - 1)` is the newest element; hence `pop_back()` removes the newest.
      * - Mixing both push modes can lead to non-intuitive behavior for front/back usage.
      */
     constexpr void pop_back() {
@@ -280,7 +270,7 @@ class HistoryBuffer {
         std::swap(_buffer, newBuf);
         _capacity       = newCapacity;
         _size           = copyCount;
-        _write_position = 0; // implementation choice
+        _write_position = 0UZ; // implementation choice
     }
 
     /**
diff --git a/core/include/gnuradio-4.0/Scheduler.hpp b/core/include/gnuradio-4.0/Scheduler.hpp
index 9fe7c1e5..1f80d35e 100644
--- a/core/include/gnuradio-4.0/Scheduler.hpp
+++ b/core/include/gnuradio-4.0/Scheduler.hpp
@@ -446,7 +446,7 @@ class Simple : public SchedulerBase<Simple<execution, TProfiler>, execution, TPr
         std::lock_guard lock(base_t::_jobListsMutex);
 
         std::size_t blockCount = 0UZ;
-        this->forAllUnmanagedBlocks([&blockCount](auto&& block) { blockCount++; });
+        this->forAllUnmanagedBlocks([&blockCount](auto&& /*block*/) { blockCount++; });
         std::vector<BlockModel*> allBlocks;
         allBlocks.reserve(blockCount);
         this->forAllUnmanagedBlocks([&allBlocks](auto&& block) { allBlocks.push_back(block.get()); });
diff --git a/core/test/qa_buffer.cpp b/core/test/qa_buffer.cpp
index 4ae5c60d..29f332ee 100644
--- a/core/test/qa_buffer.cpp
+++ b/core/test/qa_buffer.cpp
@@ -707,7 +707,7 @@ const boost::ut::suite HistoryBufferTest = [] {
         expect(eq(hb.size(), 0UZ));
 
         for (std::size_t i = 1; i <= capacity + 1; ++i) {
-            hb.push_back(static_cast<int>(i));
+            hb.push_front(static_cast<int>(i));
         }
         expect(eq(hb.capacity(), capacity));
         expect(eq(hb.size(), capacity));
@@ -725,8 +725,8 @@ const boost::ut::suite HistoryBufferTest = [] {
 
     "HistoryBuffer - range tests"_test = [] {
         HistoryBuffer<int> hb(5);
-        hb.push_back_bulk(std::array{1, 2, 3});
-        hb.push_back_bulk(std::vector{4, 5, 6});
+        hb.push_front(std::array{1, 2, 3});
+        hb.push_front(std::vector{4, 5, 6});
         expect(eq(hb.capacity(), 5UZ));
         expect(eq(hb.size(), 5UZ));
 
@@ -761,8 +761,8 @@ const boost::ut::suite HistoryBufferTest = [] {
         HistoryBuffer<int, 8UZ> buffer8;
 
         for (std::size_t i = 0UZ; i <= buffer8.capacity(); ++i) {
-            buffer5.push_back(static_cast<int>(i));
-            buffer8.push_back(static_cast<int>(i));
+            buffer5.push_front(static_cast<int>(i));
+            buffer8.push_front(static_cast<int>(i));
         }
 
         expect(eq(buffer5[0], 8));
@@ -778,8 +778,8 @@ const boost::ut::suite HistoryBufferTest = [] {
         const auto&        const_hb_one = hb_one; // tests const access
         expect(eq(hb_one.capacity(), 1UZ));
         expect(eq(hb_one.size(), 0UZ));
-        hb_one.push_back(41);
-        hb_one.push_back(42);
+        hb_one.push_front(41);
+        hb_one.push_front(42);
         expect(eq(hb_one.capacity(), 1UZ));
         expect(eq(hb_one.size(), 1UZ));
         expect(eq(hb_one[0], 42));
@@ -790,17 +790,17 @@ const boost::ut::suite HistoryBufferTest = [] {
         // Push more elements than buffer size
         HistoryBuffer<int> hb_overflow(5);
         auto               in = std::vector{1, 2, 3, 4, 5, 6};
-        hb_overflow.push_back_bulk(in.begin(), in.end());
+        hb_overflow.push_front(in.begin(), in.end());
         expect(eq(hb_overflow[0], 6));
-        hb_overflow.push_back_bulk(std::vector{7, 8, 9, 10, 11, 12, 13, 14});
+        hb_overflow.push_front(std::vector{7, 8, 9, 10, 11, 12, 13, 14});
         expect(eq(hb_overflow[0], 14));
-        hb_overflow.push_back_bulk(std::array{15, 16, 17});
+        hb_overflow.push_front(std::array{15, 16, 17});
         expect(eq(hb_overflow[0], 17));
 
         // Test with different types, e.g., double
         HistoryBuffer<double> hb_double(5);
         for (int i = 0; i < 10; ++i) {
-            hb_double.push_back(i * 0.1);
+            hb_double.push_front(i * 0.1);
         }
         expect(eq(hb_double.capacity(), 5UZ));
         expect(eq(hb_double.size(), 5UZ));
@@ -823,13 +823,13 @@ const boost::ut::suite HistoryBufferTest = [] {
     };
 
     "HistoryBuffer - forward/reversed usage"_test = [] {
-        HistoryBuffer<int> forward(5);  // stores push_back(..) with forward[0] being newest sample
-        HistoryBuffer<int> backward(5); // stores push_front(..) with backward[0] being the oldest sample
+        HistoryBuffer<int> forward(5);  // stores push_front(..) with forward[0] being newest sample
+        HistoryBuffer<int> backward(5); // stores push_back(..) with backward[0] being the oldest sample
 
         // push {1,2,3,4,5,6} individually to both
         for (int i = 1; i <= 6; ++i) {
-            forward.push_back(i);
-            backward.push_front(i);
+            forward.push_front(i);
+            backward.push_back(i);
         }
 
         // expected content of forward:  [6,5,4,3,2]
@@ -848,7 +848,7 @@ const boost::ut::suite HistoryBufferTest = [] {
 
         // Bulk test:
         backward.reset();
-        backward.push_front(std::vector<int>{10, 11, 12, 13, 14, 15, 16}); // push more than capacity:
+        backward.push_back(std::vector<int>{10, 11, 12, 13, 14, 15, 16}); // push more than capacity:
         expect(eq(backward[0], 12));
         expect(eq(backward[4], 16));
 
@@ -862,7 +862,7 @@ const boost::ut::suite HistoryBufferTest = [] {
         // Only for dynamic-extent
         HistoryBuffer<int> hb(5);
         for (int i = 1; i <= 5; ++i) {
-            hb.push_back(i);
+            hb.push_front(i);
         }
         // now: [5,4,3,2,1]
         expect(eq(hb.size(), 5UZ));
@@ -876,7 +876,7 @@ const boost::ut::suite HistoryBufferTest = [] {
 
         // push more data
         for (int i = 6; i <= 10; ++i) {
-            hb.push_back(i); // if we keep pushing
+            hb.push_front(i); // if we keep pushing
         }
         expect(eq(hb.size(), 8UZ)); // now full at 8
         expect(eq(hb[0], 10));      // newest => 10
@@ -894,7 +894,7 @@ const boost::ut::suite HistoryBufferTest = [] {
         expect(eq(hb.empty(), true));
 
         for (int i = 1; i <= 6; ++i) {
-            hb.push_back(i);
+            hb.push_front(i);
         }
         // final ring => [6,5,4,3,2]
         expect(eq(hb.front(), 6)) << "front == [0] => newest sample in push_back orientation";
@@ -902,7 +902,7 @@ const boost::ut::suite HistoryBufferTest = [] {
 
         hb.reset();
         for (int i = 1; i <= 6; ++i) {
-            hb.push_front(i);
+            hb.push_back(i);
         }
         // final ring => [2,3,4,5,6] in logical terms
         expect(eq(hb.front(), 2)) << "front == [0] => oldest sample in push_front orientation";
@@ -915,7 +915,7 @@ const boost::ut::suite HistoryBufferTest = [] {
 
         HistoryBuffer<int> hb(5);
         for (int i = 1; i <= 5; ++i) { // push_back => newest @ [0]
-            hb.push_back(i);           // final ring => [5,4,3,2,1]
+            hb.push_front(i);          // final ring => [5,4,3,2,1]
         }
         expect(eq(hb.size(), 5UZ));
         expect(eq(hb[0], 5));
@@ -943,7 +943,7 @@ const boost::ut::suite HistoryBufferTest = [] {
         // test push_front orientation
         HistoryBuffer<int> hb2(5);
         for (int i = 1; i <= 5; ++i) {
-            hb2.push_front(i); // final ring => [1,2,3,4,5] logically
+            hb2.push_back(i); // final ring => [1,2,3,4,5] logically
         }
         expect(eq(hb2[0], 1));
         hb2.pop_front(); // remove '1'