LCOV - code coverage report
Current view: top level - src/MIDI_Interfaces/BLEMIDI - BLERingBuf.hpp (source / functions) Coverage Total Hit
Test: 73449d9b107c772cf65493691543348214e5d5eb Lines: 97.3 % 73 71
Test Date: 2026-06-06 17:44:35 Functions: 100.0 % 21 21
Legend: Lines:     hit not hit

            Line data    Source code
       1              : #pragma once
       2              : 
       3              : #include <cstdint>
       4              : #include <cstring>
       5              : 
       6              : #include <Settings/NamespaceSettings.hpp>
       7              : 
       8              : #include <MIDI_Interfaces/BLEMIDI/BLEAPI.hpp>
       9              : 
      10              : BEGIN_CS_NAMESPACE
      11              : 
      12              : template <class T>
      13              : struct NonatomicBLERingBufSize {
      14              :     T value;
      15              :     /// Alignment for size, and read/write pointers to avoid false sharing.
      16              :     constexpr static size_t alignment = alignof(T);
      17           37 :     T load_acquire() const { return value; }
      18           10 :     void add_release(T t) { value += t; }
      19           19 :     void sub_release(T t) { value -= t; }
      20              : };
      21              : 
      22              : /// Circular FIFO buffer for buffering BLE packet data. It supports both
      23              : /// complete BLE packets and packets split over multiple chunks. Full packets
      24              : /// that are added to the FIFO might be split up over multiple chunks.
      25              : /// @tparam Capacity
      26              : ///         Buffer size (bytes). Note that the actual maximum data size may be
      27              : ///         up to 6 bytes less because of data structure overhead.
      28              : /// @tparam SizeT
      29              : ///         The type to use for the size of tbe buffer. Should be atomic if this
      30              : ///         buffer is to be used as a SPSC queue between two threads.
      31              : ///         See @ref NonatomicBLERingBufSize for an example.
      32              : template <uint_fast16_t Capacity,
      33              :           class SizeT = NonatomicBLERingBufSize<uint_fast16_t>>
      34              : class BLERingBuf {
      35              :   private:
      36              :     struct Header {
      37              :         uint16_t size : 14;
      38              :         uint8_t type : 2;
      39              :         Header() = default;
      40           80 :         Header(uint16_t size, BLEDataType type)
      41           80 :             : size {size}, type {static_cast<uint8_t>(type)} {}
      42           52 :         BLEDataType getType() const { return static_cast<BLEDataType>(type); }
      43              :     };
      44              :     constexpr static uint_fast16_t header_size = sizeof(Header);
      45              :     static_assert(header_size == 2, "");
      46              : 
      47              :     constexpr static uint_fast16_t capacity = Capacity;
      48              :     unsigned char buffer[capacity];
      49              :     alignas(SizeT::alignment) uint_fast16_t read_p = 0;
      50              :     alignas(SizeT::alignment) uint_fast16_t write_p = header_size;
      51              :     alignas(SizeT::alignment) SizeT size {header_size};
      52              : 
      53          198 :     constexpr static uint_fast16_t ceil_h(uint_fast16_t i) {
      54          198 :         return ((i + header_size - 1) / header_size) * header_size;
      55              :     }
      56              : 
      57              :   public:
      58           34 :     BLERingBuf() {
      59           34 :         Header header {0, BLEDataType::None};
      60              :         static_assert(capacity % header_size == 0, "");
      61           34 :         std::memcpy(buffer, &header, header_size);
      62           34 :     }
      63              : 
      64              :     /// Copy the given data into the buffer. May be split up into two chunks,
      65              :     /// in which case the type will be set to @ref BLEDataType::Continuation
      66              :     /// for the second chunk.
      67              :     /// @retval false   The buffer is full, nothing added to the buffer.
      68           27 :     bool push(BLEDataView data, BLEDataType type = BLEDataType::Packet) {
      69           27 :         if (type == BLEDataType::None)
      70            0 :             return false;
      71           27 :         uint_fast16_t loc_size = size.load_acquire();
      72           27 :         uint_fast16_t add_size = 0;
      73           27 :         assert(loc_size <= capacity);
      74           27 :         assert(write_p < capacity);
      75           27 :         assert(write_p % header_size == 0);
      76              :         // We need to write at least one header
      77           27 :         uint_fast16_t expected_req_size = loc_size + data.length + header_size;
      78           27 :         if (expected_req_size > capacity)
      79            2 :             return false; // not enough space
      80              :         // Contiguous size remaining (excluding header)
      81           25 :         uint_fast16_t contig_size = capacity - write_p - header_size;
      82           25 :         assert(contig_size < capacity);
      83              :         // We may need to write a second header
      84           25 :         uint_fast16_t worst_case_req_size = expected_req_size + header_size;
      85           25 :         if (data.length > contig_size && worst_case_req_size > capacity)
      86            0 :             return false; // not enough space
      87              :         // Write the first header for the packet
      88           25 :         uint16_t size_1 = std::min<uint_fast16_t>(contig_size, data.length);
      89           25 :         Header header {size_1, type};
      90           25 :         std::memcpy(buffer + write_p, &header, sizeof(header));
      91           25 :         write_p += header_size;
      92           25 :         add_size += header_size;
      93              :         // Write first data
      94           25 :         if (size_1 > 0) // avoid memcpy with nullptr
      95           24 :             std::memcpy(buffer + write_p, data.data, size_1);
      96           25 :         write_p += ceil_h(size_1);
      97           25 :         add_size += ceil_h(size_1);
      98           25 :         if (write_p == capacity)
      99            3 :             write_p = 0;
     100              :         // Now write the remainder at the beginning of the circular buffer
     101           25 :         uint16_t size_2 = data.length - size_1;
     102           25 :         if (size_2 > 0) {
     103              :             // Write the continuation header
     104            2 :             Header header {size_2, BLEDataType::Continuation};
     105            2 :             std::memcpy(buffer + write_p, &header, sizeof(header));
     106            2 :             write_p += header_size;
     107            2 :             add_size += header_size;
     108              :             // Write the remainder of the data
     109            2 :             std::memcpy(buffer + write_p, data.data + size_1, size_2);
     110            2 :             write_p += ceil_h(size_2);
     111            2 :             add_size += ceil_h(size_2);
     112              :         }
     113           25 :         size.add_release(add_size);
     114           25 :         return true;
     115              :     }
     116              : 
     117              :     /// Get a view to the next chunk of data. The view remains valid until the
     118              :     /// next call to @ref pop.
     119              :     /// @retval BLEDataType::None
     120              :     ///         No data available.
     121              :     /// @retval BLEDataType::Packet
     122              :     ///         The @p data output parameter points to the first chunk of a
     123              :     ///         packet.
     124              :     /// @retval BLEDataType::Continuation
     125              :     ///         The @p data output parameter points to a chunk of continuation
     126              :     ///         data of the same packet.
     127           52 :     BLEDataType pop(BLEDataView &data) {
     128           52 :         uint_fast16_t loc_size = size.load_acquire();
     129           52 :         assert(loc_size >= header_size);
     130           52 :         assert(read_p < capacity);
     131           52 :         assert(read_p % header_size == 0);
     132              :         // Read the old header
     133           52 :         Header old_header;
     134           52 :         std::memcpy(&old_header, buffer + read_p, sizeof(old_header));
     135              :         // If the previous chunk is the only thing left in the buffer
     136           52 :         if (loc_size - header_size == ceil_h(old_header.size)) {
     137              :             // If there is still actual data left in the buffer
     138           25 :             if (old_header.getType() != BLEDataType::None) {
     139              :                 // Free the old data, preserving space for a header
     140           19 :                 read_p += ceil_h(old_header.size);
     141           19 :                 size.sub_release(ceil_h(old_header.size));
     142              :                 // Write an empty header
     143           19 :                 Header header {0, BLEDataType::None};
     144           19 :                 std::memcpy(buffer + read_p, &header, sizeof(header));
     145              :             }
     146           25 :             data = {nullptr, 0};
     147           25 :             return BLEDataType::None;
     148              :         } else {
     149              :             // Otherwise, discard the old data and header
     150           27 :             read_p += header_size + ceil_h(old_header.size);
     151           27 :             size.sub_release(header_size + ceil_h(old_header.size));
     152           27 :             if (read_p == capacity)
     153            3 :                 read_p = 0;
     154              :             // Read the next header
     155           27 :             Header header;
     156           27 :             std::memcpy(&header, buffer + read_p, sizeof(header));
     157           27 :             data = {buffer + read_p + header_size, header.size};
     158           27 :             return header.getType();
     159              :         }
     160              :     }
     161              : };
     162              : 
     163              : END_CS_NAMESPACE
        

Generated by: LCOV version 2.4-beta