From 5ee5c5cb2e5ff81ef13c9cadc40f544bf7ab9ab3 Mon Sep 17 00:00:00 2001 From: Attila Body Date: Sun, 1 Jun 2025 19:20:28 +0200 Subject: [PATCH] Initial commit --- .clang-format | 33 +++++++++ .gitignore | 6 ++ .vscode/launch.json | 25 +++++++ .vscode/tasks.json | 58 ++++++++++++++++ CMakeLists.txt | 18 +++++ CMakePresets.json | 65 ++++++++++++++++++ f1ll/ringbuffer.h | 70 +++++++++++++++++++ main.cpp | 26 +++++++ ringbuffer.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 461 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CMakeLists.txt create mode 100644 CMakePresets.json create mode 100644 f1ll/ringbuffer.h create mode 100644 main.cpp create mode 100644 ringbuffer.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..233409f --- /dev/null +++ b/.clang-format @@ -0,0 +1,33 @@ +BasedOnStyle: LLVM +UseTab: Never +IndentWidth: 2 +TabWidth: 2 +BreakBeforeBraces: Custom +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLambdasOnASingleLine: true +AllowAllArgumentsOnNextLine: true +IndentCaseLabels: true +AccessModifierOffset: -2 +NamespaceIndentation: None +FixNamespaceComments: false +PackConstructorInitializers: Never +AlignAfterOpenBracket: AlwaysBreak +InsertBraces: true +BraceWrapping: + AfterClass: true # false + AfterControlStatement: false + AfterEnum: true # false + AfterFunction: true # false + AfterNamespace: false + AfterObjCDeclaration: true # false + AfterStruct: true # false + AfterUnion: true # false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +ColumnLimit: 140 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf3f647 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/build/ +/.cache/ +CMakeFiles/ +CMakeCache.txt +cmake_install.cmake +build.ninja diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3a888e4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,25 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug RingBufferTest", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/ringbuffer_test", // Adjust path if your build directory is different + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", // Or "lldb" if you're on macOS/Linux and prefer lldb + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + "preLaunchTask": "build_debug" // This task will build your project before debugging + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..7f764e4 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,58 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "configure_debug", + "type": "shell", + "command": "cmake -B ${workspaceFolder}/build -S ${workspaceFolder} -DCMAKE_BUILD_TYPE=Debug", + "group": "build", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "build_debug", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --config Debug", + "group": { + "kind": "build", + "isDefault": true + }, + "dependsOn": "configure_debug", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [ + "$gcc" // Or "$msCompile" for MSVC + ] + }, + { + "label": "configure_release", + "type": "shell", + "command": "cmake -B ${workspaceFolder}/build -S ${workspaceFolder} -DCMAKE_BUILD_TYPE=Release", + "group": "build", + "presentation": { + "reveal": "silent", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "build_release", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --config Release", + "group": "build", + "dependsOn": "configure_release", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [ + "$gcc" + ] + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9443bda --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.10) # Or a newer version if you prefer + +project(RingBufferTest CXX) + +# Specify the C++ standard to use (e.g., C++17) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Add your executable +add_executable(ringbuffer_test main.cpp ringbuffer.cpp) + +# Add include directories for your header files +target_include_directories(ringbuffer_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +# Add debugging flags (important for debugging) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_options(ringbuffer_test PRIVATE -g) +endif() \ No newline at end of file diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..17a8c4d --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,65 @@ +{ + "version": 6, + "cmakeMinimumRequired": { + "major": 3, + "minor": 23, + "patch": 0 + }, + "configurePresets": [ + { + "name": "base", + "hidden": true, + "generator": "Ninja", // Or "Unix Makefiles", "MinGW Makefiles", etc. + "binaryDir": "${sourceDir}/build/${presetName}", + "cacheVariables": { + "CMAKE_CXX_STANDARD": "17", + "CMAKE_CXX_STANDARD_REQUIRED": "ON", + "CMAKE_EXPORT_COMPILE_COMMANDS": "ON" + } + }, + { + "name": "debug", + "displayName": "Debug Build", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "release", + "displayName": "Release Build", + "inherits": "base", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + } + ], + "buildPresets": [ + { + "name": "debug", + "displayName": "Debug Build", + "configurePreset": "debug", + "targets": ["ringbuffer_test"] + }, + { + "name": "release", + "displayName": "Release Build", + "configurePreset": "release", + "targets": ["ringbuffer_test"] + } + ], + "testPresets": [ + { + "name": "default", + "displayName": "Run Tests", + "configurePreset": "debug", // Use the debug configuration for running tests + "output": { + "outputOnFailure": true + }, + "execution": { + "noTestsAction": "error", + "stopOnFailure": true + } + } + ] + } \ No newline at end of file diff --git a/f1ll/ringbuffer.h b/f1ll/ringbuffer.h new file mode 100644 index 0000000..ccdaab2 --- /dev/null +++ b/f1ll/ringbuffer.h @@ -0,0 +1,70 @@ +/* + * ringbuffer.h + * + * Created on: Sep 14, 2021 + * Author: Attila Body + */ + +#pragma once + +#include +#include + +namespace f1ll { + +class ringbuffer +{ +public: + ringbuffer(uint8_t *buffer, uint16_t size); + + /// @brief Copies data to the ring buffer (without committing it) The amount of the committed data in the buffer + /// should not exceed the size of the buffer. + /// @param data Pointer to the data to copy + /// @param len Length of the data to copy + uint16_t put(uint8_t const *data_buffer, uint16_t len); + + /// @brief Commits the data already placed into the buffer and notifies the consumer about it's availability + void commit(); + + /// @brief Waits until all the data from the ring buffer gets consumed. + // void flush(); + + /// @brief Gets a pointer to the next chunk of committed data in the buffer without registering the consumption. + /// The caller should also call report_consumption using the returned chunk length after + /// it finished processing the data. + /// @param[in] len_requested Length of the data requested from the buffer. The length of the actual data provided + /// might be actually smaller (because either reaching the end of the buffer or not enough data in the + /// buffer). + /// @param[out] data Receives a pointer to the first byte of the available data in the buffer + /// @param[out] len Receives the length of the chunk available in the buffer. Will not exceed len_requested. + /// @retval true if the buffer has more available data, false otherwise + bool get_chunk(uint16_t len_requested, uint8_t **data, uint16_t *len); + + /// @brief Marks the chunk returned by ringbuffer_GetChunk as available + /// @param consumed The length of the chunk as returned by ringbuffer_GetChunk(..., len) + void report_consumption(uint16_t consumed); + + /// @brief Returns the number of uncommited bytes in the ring buffer + uint16_t uncommited() const; + + /// @brief Returns the number of commited bytes in the ring buffer + uint16_t commited() const; + + /// @brief Discards the uncommited data in the ring buffer + void discard(); + +private: + uint8_t *m_buffer; //!< Pointer to the phisical memory bufer + uint16_t m_size; //!< Size of the memory buffer in bytes + uint16_t m_head; //!< Write position + uint16_t m_head_shadow; //!< Shadowed write position for collecting data before committing it + uint16_t m_tail; //!< Read position + + static uint16_t buffer_used(uint16_t size, uint16_t head, uint16_t tail); + static uint16_t buffer_free(uint16_t size, uint16_t head, uint16_t tail); + + uint16_t buffer_used() const; + uint16_t buffer_free() const; +}; + +} // namespace \ No newline at end of file diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..006d0a2 --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include + +#include + +using namespace std; + +int main() +{ + uint8_t rb_buffer[8]; + uint8_t dst_buffer[sizeof(rb_buffer) - 1]; + uint8_t data1[4] = {0, 1, 2, 3}; + uint8_t data2[4] = {4, 5, 6, 7}; + + f1ll::ringbuffer rb(rb_buffer, sizeof(rb_buffer)); + + int16_t added = rb.put(data1, sizeof(data1)); + cout << "Added " << added << " bytes to the buffer. It reports " << rb.uncommited() << " uncommited and " << rb.commited() + << " commited bytes." << endl; + rb.commit(); + cout << "After commit, there are " << rb.commited() << " commited and " << rb.uncommited() << " uncommited bytes in the buffer" << endl; + added = rb.put(data1, sizeof(data1)); + cout << "Added " << added << " bytes to the buffer. It reports " << rb.uncommited() << " uncommited and " << rb.commited() + << " commited bytes." << endl; + rb.commit(); + cout << "After commit, there are " << rb.commited() << " commited and " << rb.uncommited() << " uncommited bytes in the buffer" << endl; +} diff --git a/ringbuffer.cpp b/ringbuffer.cpp new file mode 100644 index 0000000..d2c42b1 --- /dev/null +++ b/ringbuffer.cpp @@ -0,0 +1,160 @@ +/* + * ringbuffer.c + * + * Created on: Sep 14, 2021 + * Author: Attila Body + */ + +#include +// #include "macro_utils.h" +// #include "print_string.h" +// #include "taskregistry.h" + +#include + +#define __ASSERT(x) assert_param(x) + +namespace f1ll { + +ringbuffer::ringbuffer(uint8_t *buffer, uint16_t size) + : m_buffer(buffer), + m_size(size), + m_head(0), + m_head_shadow(0), + m_tail(0) +{ + // __ASSERT(handle); + // __ASSERT(buffer); + // __ASSERT(buffer_size); +} + +uint16_t ringbuffer::buffer_used(uint16_t size, uint16_t head, uint16_t tail) +{ + return head >= tail ? head - tail : size - tail + head; +} + +uint16_t ringbuffer::buffer_free(uint16_t size, uint16_t head, uint16_t tail) +{ + return size - buffer_used(size, head, tail) - 1; +} + +uint16_t ringbuffer::buffer_used() const +{ + return m_head_shadow >= m_tail ? m_head_shadow - m_tail : m_size - m_tail + m_head_shadow; +} + +uint16_t ringbuffer::buffer_free() const +{ + return m_size - buffer_used() - 1; +} + +uint16_t ringbuffer::put(uint8_t const *data_buffer, uint16_t len) +{ + uint16_t chunk1 = 0; + uint16_t chunk2 = 0; + + if (!data_buffer || !len) { + return 0; + } + + uint16_t max_len = buffer_free(m_size, m_head_shadow, m_tail); + len = len < max_len ? len : max_len; + + chunk1 = m_size - m_head_shadow; + if (chunk1 >= len) { + chunk1 = len; + } else { + chunk2 = len - chunk1; + } + + std::memcpy(m_buffer + m_head_shadow, data_buffer, chunk1); + m_head_shadow += chunk1; + if (m_head_shadow == m_size) { + m_head_shadow = 0; + } + + if (chunk2) { + std::memcpy(m_buffer, data_buffer + chunk1, chunk2); + m_head_shadow += chunk2; + if (m_head_shadow == m_size) { + m_head_shadow = 0; + } + } + + return len; +} + +void ringbuffer::commit() +{ + + uint16_t uncommitted = buffer_used(m_size, m_head_shadow, m_head); + + if (!uncommitted) { + return; + } + + m_head = m_head_shadow; +} + +// void ringbuffe::flush() +// { +// while (handle->m_head != handle->m_tail) { +// } +// } + +bool ringbuffer::get_chunk(uint16_t len_requested, uint8_t **data, uint16_t *len) +{ + if (!len_requested || !data || !*data || !len) { + return false; + } + + uint16_t head = m_head; + uint16_t tail = m_tail; + uint16_t chunk_size = head >= tail ? head - tail : m_size - tail; + + if (!chunk_size) { + *len = 0; + return false; + } + + if (chunk_size > len_requested) { + chunk_size = len_requested; + } + *data = m_buffer + tail; + *len = chunk_size; + + tail += chunk_size; + if (tail == m_size) { + tail = 0; + } + + return tail != head; +} + +void ringbuffer::report_consumption(uint16_t consumed) +{ + if (!consumed) { + return; + } + m_tail += consumed; + if (m_tail == m_size) { + m_tail = 0; + } +} + +uint16_t ringbuffer::commited() const +{ + return buffer_used(m_size, m_head, m_tail); +} + +uint16_t ringbuffer::uncommited() const +{ + return buffer_used(m_size, m_head_shadow, m_head); +} + +void ringbuffer::discard() +{ + m_head_shadow = m_head; +} + +} // namespace f1ll \ No newline at end of file