── Sensor Pipeline (Modules 07, 10, 12: RAII + Classes + Threads) ──
| 87 | |
| 88 | // ── Sensor Pipeline (Modules 07, 10, 12: RAII + Classes + Threads) ── |
| 89 | class SensorPipeline { |
| 90 | public: |
| 91 | explicit SensorPipeline(std::string_view name) |
| 92 | : name_(name) {} |
| 93 | |
| 94 | // RAII: destructor guarantees clean shutdown (Module 07) |
| 95 | ~SensorPipeline() { stop(); } |
| 96 | |
| 97 | // Non-copyable, non-movable (owns threads — Module 10) |
| 98 | SensorPipeline(const SensorPipeline&) = delete; |
| 99 | SensorPipeline& operator=(const SensorPipeline&) = delete; |
| 100 | |
| 101 | void start() { |
| 102 | running_ = true; |
| 103 | producer_ = std::thread(&SensorPipeline::producerLoop, this); |
| 104 | consumer_ = std::thread(&SensorPipeline::consumerLoop, this); |
| 105 | } |
| 106 | |
| 107 | void stop() { |
| 108 | if (!running_.exchange(false)) return; // Already stopped |
| 109 | queue_.shutdown(); |
| 110 | if (producer_.joinable()) producer_.join(); |
| 111 | if (consumer_.joinable()) consumer_.join(); |
| 112 | } |
| 113 | |
| 114 | [[nodiscard]] std::size_t processedCount() const noexcept { |
| 115 | return processed_count_.load(std::memory_order_relaxed); |
| 116 | } |
| 117 | |
| 118 | private: |
| 119 | void producerLoop() { |
| 120 | // Thread-local RNG — each thread gets its own engine (no data race) |
| 121 | std::mt19937 rng(std::random_device{}()); |
| 122 | std::uniform_real_distribution<double> noise(0.0, 0.1); |
| 123 | |
| 124 | while (running_) { |
| 125 | IMUReading reading{}; |
| 126 | reading.timestamp = std::chrono::steady_clock::now(); |
| 127 | reading.accel_z = -9.81 + noise(rng); |
| 128 | |
| 129 | if (!queue_.push(reading)) break; |
| 130 | std::this_thread::sleep_for(config::IMU_PERIOD); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | void consumerLoop() { |
| 135 | // Rely solely on pop()'s internal lock + shutdown signal, |
| 136 | // NOT on an unguarded queue_.size() check which would be a data race. |
| 137 | while (true) { |
| 138 | auto maybe_reading = queue_.pop(); |
| 139 | if (!maybe_reading) break; // shutdown() was called and queue is drained |
| 140 | |
| 141 | // Simple magnitude filter (Module 03: arithmetic) |
| 142 | const auto& r = *maybe_reading; |
| 143 | [[maybe_unused]] double magnitude = std::sqrt( |
| 144 | r.accel_x * r.accel_x + |
| 145 | r.accel_y * r.accel_y + |
| 146 | r.accel_z * r.accel_z); |
nothing calls this directly
no outgoing calls
no test coverage detected