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