From f8d10e61e9be7b38e186e659226936cae7f9f46b Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 7 Aug 2024 06:22:10 +0100 Subject: [PATCH] Add `PrintBuffer` classes (#2873) Using ArduinoJson to serialize directly to a file stream performs poorly because writes are done byte-by-byte. This issue is mentioned under [performance](https://arduinojson.org/v6/api/json/serializejson/). The mentioned [ArduinoStreamUtils](https://github.com/bblanchon/ArduinoStreamUtils) library is a bit hefty and doesn't particular fit with the existing Sming stream classes. This PR adds a very simple `PrintBuffer` class which operates on a `Print` output and can be employed very easily if additional generic buffering is required. Note that there is little benefit to using this with other memory-based streams so best left to the library or application to decide when its appropriate. --- Sming/Core/Data/Buffer/PrintBuffer.cpp | 43 +++++ Sming/Core/Data/Buffer/PrintBuffer.h | 155 ++++++++++++++++++ docs/source/framework/core/data/buffering.rst | 15 ++ 3 files changed, 213 insertions(+) create mode 100644 Sming/Core/Data/Buffer/PrintBuffer.cpp create mode 100644 Sming/Core/Data/Buffer/PrintBuffer.h create mode 100644 docs/source/framework/core/data/buffering.rst diff --git a/Sming/Core/Data/Buffer/PrintBuffer.cpp b/Sming/Core/Data/Buffer/PrintBuffer.cpp new file mode 100644 index 0000000000..33b63a1e8f --- /dev/null +++ b/Sming/Core/Data/Buffer/PrintBuffer.cpp @@ -0,0 +1,43 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * PrintBuffer.cpp + * + ****/ + +#include "PrintBuffer.h" + +size_t BasePrintBuffer::write(uint8_t c) +{ + buffer[writeOffset++] = c; + if(writeOffset == bufferSize) { + flush(); + } + return 1; +} + +size_t BasePrintBuffer::write(const uint8_t* data, size_t size) +{ + size_t written{0}; + while(size != 0) { + auto copySize = std::min(bufferSize - writeOffset, size); + memcpy(&buffer[writeOffset], data, copySize); + writeOffset += copySize; + written += copySize; + data += copySize; + size -= copySize; + if(writeOffset == bufferSize) { + flush(); + } + } + return written; +} + +void BasePrintBuffer::flush() +{ + output.write(buffer, writeOffset); + writeOffset = 0; +} diff --git a/Sming/Core/Data/Buffer/PrintBuffer.h b/Sming/Core/Data/Buffer/PrintBuffer.h new file mode 100644 index 0000000000..09d683d1b9 --- /dev/null +++ b/Sming/Core/Data/Buffer/PrintBuffer.h @@ -0,0 +1,155 @@ +/**** + * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. + * Created 2015 by Skurydin Alexey + * http://github.com/SmingHub/Sming + * All files of the Sming Core are provided under the LGPL v3 license. + * + * PrintBuffer.h + * + ****/ + +#pragma once + +#include +#include + +/** + * @brief Generic write-through buffer class + * @ingroup stream + * @note Call flush() at end of write operation to ensure all data is output + * This is done automatically when the buffer is destroyed. + */ +class BasePrintBuffer : public Print +{ +public: + /** + * @brief Create buffer + * @param output Destination stream + * @param buffer buffer to use + * @param size Size of buffer + */ + BasePrintBuffer(Print& output, uint8_t buffer[], size_t bufferSize) + : output(output), buffer(buffer), bufferSize(bufferSize) + { + } + + ~BasePrintBuffer() + { + flush(); + } + + size_t write(uint8_t c) override; + + size_t write(const uint8_t* data, size_t size) override; + + /** + * @brief Write any buffered content to output + */ + void flush(); + +private: + Print& output; + uint8_t* buffer; + size_t bufferSize; + size_t writeOffset{}; +}; + +/** + * @brief Write-through buffer using stack only + * @tparam size Size of buffer + * + * Example usage: + * + * FileStream stream("file.txt", File::ReadWrite); + * { + * StaticPrintBuffer<256> buffer(stream); + * writeSomeData(buffer); + * } // Buffer flushed and destroyed when it goes out of scope + */ +template class StaticPrintBuffer : public BasePrintBuffer +{ +public: + /** + * @brief Construct a stack-based buffer + * @param output Print destination + */ + StaticPrintBuffer(Print& output) : BasePrintBuffer(output, buffer, size) + { + } + +private: + uint8_t buffer[size]; +}; + +/** + * @brief Write-through buffer using heap storage + * + * Example usage: + * + * FileStream stream("file.txt", File::ReadWrite); + * { + * HeapPrintBuffer buffer(stream, 512); + * writeSomeData(buffer); + * } // Buffer flushed and destroyed when it goes out of scope + */ +class HeapPrintBuffer : public BasePrintBuffer +{ +public: + /** + * @brief Construct a stack-based buffer + * @param output Print destination + * @param size Buffer size + */ + HeapPrintBuffer(Print& output, size_t size) : HeapPrintBuffer(output, new uint8_t[size], size) + { + } + +private: + HeapPrintBuffer(Print& output, uint8_t* buffer, size_t size) : BasePrintBuffer(output, buffer, size), buffer(buffer) + { + } + + std::unique_ptr buffer; +}; + +/** + * @brief Write-through buffer using heap storage and owned stream pointer + * + * Example usage: + * + * auto stream = std::make_unique("file.txt", File::ReadWrite); + * auto bufferedStream = new DynamicPrintBuffer(std::move(stream), 512); + * + * // write to bufferedStream as required via callbacks, etc. + * ... + * + * // This destroys both buffer *and* the file stream + * delete bufferedStream; + */ +class DynamicPrintBuffer : public BasePrintBuffer +{ +public: + /** + * @brief Construct a stack-based buffer + * @param output Print destination, will take ownership of this + * @param size Buffer size + */ + DynamicPrintBuffer(std::unique_ptr&& output, size_t size) + : DynamicPrintBuffer(output.release(), new uint8_t[size], size) + { + } + + ~DynamicPrintBuffer() + { + flush(); + } + +private: + DynamicPrintBuffer(Print* output, uint8_t* buffer, size_t size) + : BasePrintBuffer(*output, buffer, size), output(output), buffer(buffer) + { + } + + std::unique_ptr output; + std::unique_ptr buffer; +}; diff --git a/docs/source/framework/core/data/buffering.rst b/docs/source/framework/core/data/buffering.rst new file mode 100644 index 0000000000..fe5d1054d8 --- /dev/null +++ b/docs/source/framework/core/data/buffering.rst @@ -0,0 +1,15 @@ +Buffering +========= + +In general, writing efficiently to files is best done in chunks, such as by building a line of data in a :cpp:class:`String` and writing it in one go. + +Sming offers a simple write-through buffering mechanism which can be used where necessary. The :library:`ArduinoJson` can benefit greatly from this. + +.. doxygenclass:: StaticPrintBuffer + :members: + +.. doxygenclass:: HeapPrintBuffer + :members: + +.. doxygenclass:: DynamicPrintBuffer + :members: