From 8ad34427f3906c74ca692fb62a4385693896e194 Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Mon, 7 Jul 2025 00:12:34 +0200 Subject: [PATCH] Made TypelessBuffer allocator-aware and split some of its functionality into MemoryView type. --- source/mijin/container/memory_view.hpp | 176 +++++++++++++++++---- source/mijin/container/typeless_buffer.hpp | 90 +++-------- source/mijin/debug/stacktrace.cpp | 1 + source/mijin/io/stream.cpp | 70 -------- source/mijin/io/stream.hpp | 90 ++++++++++- source/mijin/net/http.hpp | 4 +- 6 files changed, 252 insertions(+), 179 deletions(-) diff --git a/source/mijin/container/memory_view.hpp b/source/mijin/container/memory_view.hpp index 09ee688..c87ae4f 100644 --- a/source/mijin/container/memory_view.hpp +++ b/source/mijin/container/memory_view.hpp @@ -5,10 +5,10 @@ #define MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED 1 #include -#include #include +#include #include "../debug/assert.hpp" -#include "../util/traits.hpp" +#include "../internal/common.hpp" namespace mijin { @@ -25,60 +25,170 @@ namespace mijin // public types // -template -class MemoryViewBase +template +concept MemoryViewable = requires(const T& object) +{ + { object.data() } -> std::convertible_to; + { object.byteSize() } -> std::convertible_to; +}; + +template +concept RWMemoryViewable = MemoryViewable && requires(T& object) +{ + { object.data() } -> std::convertible_to; +}; + +template +class MixinMemoryView +{ +public: + static constexpr bool WRITABLE = requires(TConcrete& object) { { object.data() } -> std::convertible_to; }; + + template + [[nodiscard]] + auto makeSpan(); + + template + [[nodiscard]] + auto makeSpan() const; + + template> + [[nodiscard]] + std::basic_string_view makeStringView() const ; + + template + [[nodiscard]] + auto& dataAt(std::size_t offset) MIJIN_NOEXCEPT; + + template + [[nodiscard]] + auto& dataAt(std::size_t offset) const MIJIN_NOEXCEPT; + + [[nodiscard]] + auto bytes() MIJIN_NOEXCEPT + { + using return_t = mijin::copy_const_t(this)->data())>, std::byte>; + return static_cast(static_cast(this)->data()); + } + + [[nodiscard]] + auto bytes() const MIJIN_NOEXCEPT + { + using return_t = mijin::copy_const_t(this)->data())>, std::byte>; + return static_cast(static_cast(this)->data()); + } +private: + std::size_t byteSizeImpl() const MIJIN_NOEXCEPT + { + return static_cast(this)->byteSize(); + } +}; + +class MemoryView : public MixinMemoryView { public: using size_type = std::size_t; private: - std::span bytes_; + void* data_ = nullptr; + std::size_t byteSize_ = 0; public: - MemoryViewBase() noexcept = default; - MemoryViewBase(const MemoryViewBase&) noexcept = default; - MemoryViewBase(copy_const_t* data, std::size_t size) noexcept : bytes_(static_cast(data), size) {} + MemoryView() noexcept = default; + MemoryView(const MemoryView&) = default; + MemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {} + template requires(!std::is_const_v) + MemoryView(std::span span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {} + template + MemoryView(T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {} - MemoryViewBase& operator=(const MemoryViewBase&) noexcept = default; - - [[nodiscard]] void* data() noexcept { return bytes_.data(); } - [[nodiscard]] const void* data() const noexcept { return bytes_.data(); } - [[nodiscard]] size_type byteSize() const noexcept { return bytes_.size(); } - [[nodiscard]] bool empty() const noexcept { return bytes_.empty(); } + MemoryView& operator=(const MemoryView&) = default; - template - [[nodiscard]] std::span makeSpan(); + [[nodiscard]] + void* data() const MIJIN_NOEXCEPT { return data_; } - template - [[nodiscard]] std::span makeSpan() const; + [[nodiscard]] + size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; } +}; + +class ConstMemoryView : public MixinMemoryView +{ +public: + using size_type = std::size_t; +private: + const void* data_; + std::size_t byteSize_; +public: + ConstMemoryView() noexcept = default; + ConstMemoryView(const ConstMemoryView&) = default; + ConstMemoryView(void* data, std::size_t byteSize) MIJIN_NOEXCEPT : data_(data), byteSize_(byteSize) {} + template + ConstMemoryView(std::span span) MIJIN_NOEXCEPT : data_(span.data()), byteSize_(span.size_bytes()) {} + template + ConstMemoryView(const T& memoryViewable) MIJIN_NOEXCEPT : data_(memoryViewable.data()), byteSize_(memoryViewable.byteSize()) {} + + [[nodiscard]] + const void* data() const MIJIN_NOEXCEPT { return data_; } + + [[nodiscard]] + size_type byteSize() const MIJIN_NOEXCEPT { return byteSize_; } }; -using MemoryView = MemoryViewBase; -using ConstMemoryView = MemoryViewBase; // // public functions // -template +template template -std::span MemoryViewBase::makeSpan() +auto MixinMemoryView::makeSpan() { - static_assert(!std::is_const_v, "Cannot create writable spans from const memory views."); - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) + MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); + using return_t = mijin::copy_const_t; + return std::span{ + std::bit_cast(bytes()), + std::bit_cast(bytes() + byteSizeImpl()) }; } -template +template template -std::span MemoryViewBase::makeSpan() const +auto MixinMemoryView::makeSpan() const { - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "MemoryView cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) + MIJIN_ASSERT(byteSizeImpl() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); + using return_t = mijin::copy_const_t; + return std::span{ + std::bit_cast(bytes()), + std::bit_cast(bytes() + byteSizeImpl()) }; } + +template +template +std::basic_string_view MixinMemoryView::makeStringView() const +{ + MIJIN_ASSERT(byteSizeImpl() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type."); + return {std::bit_cast(bytes()), byteSizeImpl() / sizeof(TChar)}; +} + +template +template +auto& MixinMemoryView::dataAt(std::size_t offset) MIJIN_NOEXCEPT +{ + MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned."); + MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range."); + + using return_t = mijin::copy_const_t; + return *std::bit_cast(bytes().data() + offset); +} + +template +template +auto& MixinMemoryView::dataAt(std::size_t offset) const MIJIN_NOEXCEPT +{ + MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned."); + MIJIN_ASSERT(offset + sizeof(T) < byteSizeImpl(), "Buffer access out-of-range."); + + using return_t = mijin::copy_const_t; + return *std::bit_cast(bytes().data() + offset); +} } // namespace mijin #endif // !defined(MIJIN_CONTAINER_MEMORY_VIEW_HPP_INCLUDED) diff --git a/source/mijin/container/typeless_buffer.hpp b/source/mijin/container/typeless_buffer.hpp index 139d686..c6b8be2 100644 --- a/source/mijin/container/typeless_buffer.hpp +++ b/source/mijin/container/typeless_buffer.hpp @@ -10,7 +10,9 @@ #include #include #include +#include "./memory_view.hpp" #include "../debug/assert.hpp" +#include "../internal/common.hpp" namespace mijin { @@ -27,17 +29,26 @@ namespace mijin // public types // -template +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> class BufferView; -class TypelessBuffer +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +class BaseTypelessBuffer : public MixinMemoryView> { public: using size_type = std::size_t; private: - std::vector bytes_; + std::vector> bytes_; public: - auto operator<=>(const TypelessBuffer&) const noexcept = default; + BaseTypelessBuffer() noexcept = default; + BaseTypelessBuffer(const BaseTypelessBuffer&) = default; + BaseTypelessBuffer(BaseTypelessBuffer&&) = default; + explicit BaseTypelessBuffer(TAllocator allocator) : bytes_(std::move(allocator)) {} + + BaseTypelessBuffer& operator=(const BaseTypelessBuffer&) = default; + BaseTypelessBuffer& operator=(BaseTypelessBuffer&&) = default; + + auto operator<=>(const BaseTypelessBuffer&) const noexcept = default; [[nodiscard]] void* data() noexcept { return bytes_.data(); } [[nodiscard]] const void* data() const noexcept { return bytes_.data(); } @@ -49,27 +60,14 @@ public: template [[nodiscard]] BufferView makeBufferView() { return BufferView(this); } - - template - [[nodiscard]] std::span makeSpan(); - - template - [[nodiscard]] std::span makeSpan() const; - - template> - [[nodiscard]] std::basic_string_view makeStringView() const ; template void append(std::span data); - - template - [[nodiscard]] T& dataAt(size_type offset) MIJIN_NOEXCEPT; - - template - [[nodiscard]] const T& dataAt(size_type offset) const MIJIN_NOEXCEPT; }; -template +using TypelessBuffer = BaseTypelessBuffer<>; + +template typename TAllocator> class BufferView { public: @@ -84,10 +82,10 @@ public: using iterator = T*; using const_iterator = const T*; private: - class TypelessBuffer* buffer_ = nullptr; + class BaseTypelessBuffer* buffer_ = nullptr; public: BufferView() = default; - explicit BufferView(class TypelessBuffer* buffer) : buffer_(buffer) {} + explicit BufferView(class BaseTypelessBuffer* buffer) : buffer_(buffer) {} BufferView(const BufferView&) = default; BufferView& operator=(const BufferView&) = default; @@ -131,57 +129,13 @@ public: // public functions // +template typename TAllocator> template -std::span TypelessBuffer::makeSpan() -{ - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) - }; -} - -template -std::span TypelessBuffer::makeSpan() const -{ - MIJIN_ASSERT(bytes_.size() % sizeof(T) == 0, "Buffer cannot be divided into elements of this type."); - return { - std::bit_cast(bytes_.data()), - std::bit_cast(bytes_.data() + bytes_.size()) - }; -} - -template -std::basic_string_view TypelessBuffer::makeStringView() const -{ - MIJIN_ASSERT(bytes_.size() % sizeof(TChar) == 0, "Buffer cannot be divided into elements of this char type."); - return {std::bit_cast(bytes_.data()), bytes_.size() / sizeof(TChar)}; -} - -template -void TypelessBuffer::append(std::span data) +void BaseTypelessBuffer::append(std::span data) { bytes_.resize(bytes_.size() + data.size_bytes()); std::memcpy(bytes_.data() + bytes_.size() - data.size_bytes(), data.data(), data.size_bytes()); } - -template -T& TypelessBuffer::dataAt(size_type offset) MIJIN_NOEXCEPT -{ - MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned."); - MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range."); - - return *std::bit_cast(bytes_.data() + offset); -} - -template -const T& TypelessBuffer::dataAt(size_type offset) const MIJIN_NOEXCEPT -{ - MIJIN_ASSERT(offset % alignof(T) == 0, "Offset must be correctly aligned."); - MIJIN_ASSERT(offset + sizeof(T) < bytes_.size(), "Buffer access out-of-range."); - - return *std::bit_cast(bytes_.data() + offset); -} } // namespace mijin #endif // !defined(MIJIN_CONTAINER_TYPELESS_BUFFER_HPP_INCLUDED) diff --git a/source/mijin/debug/stacktrace.cpp b/source/mijin/debug/stacktrace.cpp index 5cdee55..777195a 100644 --- a/source/mijin/debug/stacktrace.cpp +++ b/source/mijin/debug/stacktrace.cpp @@ -4,6 +4,7 @@ #include #include #include "../detect.hpp" +#include "../util/string.hpp" #if MIJIN_TARGET_OS != MIJIN_OS_WINDOWS && (MIJIN_COMPILER == MIJIN_COMPILER_CLANG || MIJIN_COMPILER == MIJIN_COMPILER_GCC) #define MIJIN_USE_LIBBACKTRACE 1 diff --git a/source/mijin/io/stream.cpp b/source/mijin/io/stream.cpp index 4ee29b7..87c775d 100644 --- a/source/mijin/io/stream.cpp +++ b/source/mijin/io/stream.cpp @@ -161,76 +161,6 @@ StreamError Stream::getTotalLength(std::size_t& outLength) return StreamError::SUCCESS; } -StreamError Stream::readRest(TypelessBuffer& outBuffer) -{ - // first try to allocate everything at once - std::size_t length = 0; - if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) - { - MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); - length -= tell(); - outBuffer.resize(length); - if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS) - { - return error; - } - return StreamError::SUCCESS; - } - - // could not determine the size, read chunk-wise - static constexpr std::size_t CHUNK_SIZE = 4096; - std::array chunk = {}; - - while (!isAtEnd()) - { - std::size_t bytesRead = 0; - if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS) - { - return error; - } - - outBuffer.resize(outBuffer.byteSize() + bytesRead); - const std::span bufferBytes = outBuffer.makeSpan(); - std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); - } - return StreamError::SUCCESS; -} - -mijin::Task Stream::c_readRest(TypelessBuffer& outBuffer) -{ - // first try to allocate everything at once - std::size_t length = 0; - if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) - { - MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); - length -= tell(); - outBuffer.resize(length); - if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS) - { - co_return error; - } - co_return StreamError::SUCCESS; - } - - // could not determine the size, read chunk-wise - static constexpr std::size_t CHUNK_SIZE = 4096; - std::array chunk = {}; - - while (!isAtEnd()) - { - std::size_t bytesRead = 0; - if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS) - { - co_return error; - } - - outBuffer.resize(outBuffer.byteSize() + bytesRead); - std::span bufferBytes = outBuffer.makeSpan(); - std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); - } - co_return StreamError::SUCCESS; -} - StreamError Stream::readLine(std::string& outString) { MIJIN_ASSERT(getFeatures().readOptions.peek, "Stream needs to support peeking."); diff --git a/source/mijin/io/stream.hpp b/source/mijin/io/stream.hpp index 9eed8f9..3cc1cf5 100644 --- a/source/mijin/io/stream.hpp +++ b/source/mijin/io/stream.hpp @@ -132,8 +132,9 @@ public: const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t); return readRaw(&*range.begin(), bytes, {.partial = partial}, outBytesRead); } - - StreamError readRaw(TypelessBuffer& buffer, const ReadOptions& options = {}) + + template typename TAllocator> + StreamError readRaw(BaseTypelessBuffer& buffer, const ReadOptions& options = {}) { return readRaw(buffer.data(), buffer.byteSize(), options); } @@ -144,8 +145,9 @@ public: const std::size_t bytes = std::distance(range.begin(), range.end()) * sizeof(std::ranges::range_value_t); return c_readRaw(&*range.begin(), bytes, options, outBytesRead); } - - mijin::Task c_readRaw(TypelessBuffer& buffer, const ReadOptions& options = {}) + + template typename TAllocator> + mijin::Task c_readRaw(BaseTypelessBuffer& buffer, const ReadOptions& options = {}) { return c_readRaw(buffer.data(), buffer.byteSize(), options); } @@ -269,8 +271,12 @@ public: inline StreamError writeString(std::string_view str) { return writeBinaryString(str); } StreamError getTotalLength(std::size_t& outLength); - StreamError readRest(TypelessBuffer& outBuffer); - mijin::Task c_readRest(TypelessBuffer& outBuffer); + + template typename TAllocator> + StreamError readRest(BaseTypelessBuffer& outBuffer); + + template typename TAllocator> + mijin::Task c_readRest(BaseTypelessBuffer& outBuffer); StreamError readLine(std::string& outString); mijin::Task c_readLine(std::string& outString); @@ -360,6 +366,78 @@ using StreamResult = ResultBase; // public functions // +template typename TAllocator> +StreamError Stream::readRest(BaseTypelessBuffer& outBuffer) +{ + // first try to allocate everything at once + std::size_t length = 0; + if (const StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) + { + MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); + length -= tell(); + outBuffer.resize(length); + if (const StreamError error = readRaw(outBuffer.data(), length); error != StreamError::SUCCESS) + { + return error; + } + return StreamError::SUCCESS; + } + + // could not determine the size, read chunk-wise + static constexpr std::size_t CHUNK_SIZE = 4096; + std::array chunk = {}; + + while (!isAtEnd()) + { + std::size_t bytesRead = 0; + if (const StreamError error = readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS) + { + return error; + } + + outBuffer.resize(outBuffer.byteSize() + bytesRead); + const std::span bufferBytes = outBuffer.template makeSpan(); + std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); + } + return StreamError::SUCCESS; +} + +template typename TAllocator> +mijin::Task Stream::c_readRest(BaseTypelessBuffer& outBuffer) +{ + // first try to allocate everything at once + std::size_t length = 0; + if (StreamError lengthError = getTotalLength(length); lengthError == StreamError::SUCCESS) + { + MIJIN_ASSERT(getFeatures().tell, "How did you find the length if you cannot tell()?"); + length -= tell(); + outBuffer.resize(length); + if (StreamError error = co_await c_readRaw(outBuffer.data(), length); error != StreamError::SUCCESS) + { + co_return error; + } + co_return StreamError::SUCCESS; + } + + // could not determine the size, read chunk-wise + static constexpr std::size_t CHUNK_SIZE = 4096; + std::array chunk = {}; + + while (!isAtEnd()) + { + std::size_t bytesRead = 0; + if (StreamError error = co_await c_readRaw(chunk, {.partial = true}, &bytesRead); error != StreamError::SUCCESS) + { + co_return error; + } + + outBuffer.resize(outBuffer.byteSize() + bytesRead); + std::span bufferBytes = outBuffer.template makeSpan(); + std::copy_n(chunk.begin(), bytesRead, bufferBytes.end() - static_cast(bytesRead)); + } + co_return StreamError::SUCCESS; +} + template StreamError Stream::readAsString(std::basic_string& outString) { diff --git a/source/mijin/net/http.hpp b/source/mijin/net/http.hpp index ebcbf91..beb5424 100644 --- a/source/mijin/net/http.hpp +++ b/source/mijin/net/http.hpp @@ -44,7 +44,7 @@ struct HTTPRequestOptions { std::string method = "GET"; std::multimap headers; - TypelessBuffer body; + BaseTypelessBuffer body; }; struct HTTPResponse @@ -53,7 +53,7 @@ struct HTTPResponse unsigned status; std::string statusMessage; std::multimap headers; - TypelessBuffer body; + BaseTypelessBuffer body; }; class HTTPStream