/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: ByteBuffer.hpp Date: 2021-8-5 Author: Reece ***/ #pragma once #include #include #include namespace Aurora::Memory { enum class EStringType { eStringTerminated, eStringByte, eStringWord, eStringDword, eStringQword }; // TODO: rework scaling static const auto kBufferPageSize = 512; static const auto kBufferInitialPower = 9; /*** * A bytebuffer object represents a: * * optionally resizable buffer; * * -n infinite read/writer stream pair, in ring buffer mode; * * synonym for fulfill-once AuList & output parameters; * * stream of arbitrary serialization/deserialization, seekable read/writes; * * ...tuple of and some flags; * * ...method by which one can serialize data in-place via SharedByteBuffer * * EG (2022): * -> AuProtocol ProtocolPiece buffers * -> AuCompression internal buffer * -> A sockets' output stream / buffer for user-read/write and overlapped submission * -> ... * * Deprecates INetworkStream, fixes allocation issues around compression backends * Superseeds abuse of AuList for binary blobs alongside other fixed u8 arrays */ struct ByteBuffer { /////////////////////////////////////////////////////////////////////// // User storage: // /////////////////////////////////////////////////////////////////////// AURT_ADD_USR_DATA; /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // Stable ByteBuffer ABI Header; length and read/write head pointers // /////////////////////////////////////////////////////////////////////// /// Internal capacity to mitigate trivial reallocs AuUInt allocSize; /// Abstract size AuUInt length; /// Buffer pointer AuUInt8 *base {}; /// Read stream pointer AuUInt8 *readPtr {}; /// Write stream pointer AuUInt8 *writePtr {}; /////////////////////////////////////////////////////////////////////// // Memory protection // (resize/free/etc fails when this value is not zero. free-during-use protection) /////////////////////////////////////////////////////////////////////// AuAUInt32 uInUseCounter { 0 }; /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // Stable ByteBuffer ABI Header; u8 flags // /////////////////////////////////////////////////////////////////////// AuUInt8 flagCircular : 1; /// Is ring buffer? AuUInt8 flagExpandable : 1; /// Should resize linear buffer to accommodate additional writes AuUInt8 flagReadError : 1; /// Has error? Has read error? AuUInt8 flagWriteError : 1; /// Has error? Has write error? AuUInt8 flagNoFree : 1; /// Prevents all free operations AuUInt8 flagNoRealloc : 1; /// Prevents a subset of free options, specifically realloc, operations AuUInt8 flagAlwaysExpandable : 1; /// Internal flag. Do not use. AuUInt8 flagReserveA : 1; /// Placeholder AuUInt8 flagReservedB; /////////////////////////////////////////////////////////////////////// // Special flags/values /////////////////////////////////////////////////////////////////////// AuUInt8 alignment {}; // Internal value: keeps track of explicit Allocate of alignment values AuUInt8 scaleSize {}; // TODO: /////////////////////////////////////////////////////////////////////// /** * @brief Default constructor, allocates an auto-expanding linear bytebuffer */ inline ByteBuffer(); /** * @brief Move constructor * @param buffer */ inline ByteBuffer(ByteBuffer &&buffer); /** * @brief Copy with possible preserve pointers * @param buffer * @param preservePointers */ inline ByteBuffer(const ByteBuffer &buffer, bool preservePointers = true); /** * @brief Copy pointer range into a new ring or byte buffer * @param in * @param length * @param circular * @param expandable */ inline ByteBuffer(const void *in, AuUInt length, bool circular = false, bool expandable = false); inline ByteBuffer(const MemoryViewRead &readView, bool circular = false, bool expandable = false); inline ByteBuffer(const MemoryViewRead &readView, AuUInt uAlignment, bool circular = false, bool expandable = false); inline ByteBuffer(const AuList &vector, bool circular = false, bool expandable = false); inline ByteBuffer(AuUInt length, bool circular = false, bool expandable = false); inline ByteBuffer(AuUInt length, AuUInt alignment, bool circular = false, bool expandable = false); template ByteBuffer(T *base, T *end, bool circular = false, bool expandable = false); inline ~ByteBuffer() { if (this->base && !this->flagNoFree) { Free(this->base); } } inline void ResetPositions() { this->flagReadError = 0; this->flagWriteError = 0; this->readPtr = base; this->writePtr = base; } // utils: Iterator /** * @brief base of the bytebuffer * @return */ inline auline AuUInt8 * data() const; /** * @brief size of the byte array as requested by the caller (actual allocation may differ, ::allocSize) * @return */ inline auline AuUInt size() const; /** * @brief linear read begin * @warning writers should use ::GetLinearWriteable(uDesiredLength) or ::GetOrAllocateLinearWriteable(...) * @return */ inline auline const AuUInt8 * begin(); inline auline const AuUInt8 * cbegin() const; /** * @brief linear read end * @warning writers should use ::GetLinearWriteable(uDesiredLength) or ::GetOrAllocateLinearWriteable(...) * @return */ inline auline const AuUInt8 * end(); inline auline const AuUInt8 * cend() const; inline auline bool empty() const; inline void clear(); inline void resize(AuUInt size); inline void reserve(AuUInt size); // utils: Utils to alternative types inline auline AuList ToVector() const; inline AuUInt32 GetAllocationPower() const; inline operator MemoryViewRead() const; inline operator MemoryViewWrite(); // utils: Internal buffer comparison inline bool operator ==(const AuList &) const; inline bool operator ==(const MemoryViewRead &) const; inline bool operator ==(const ByteBuffer &) const; // utils: Move assignment inline ByteBuffer &operator =(ByteBuffer &&); inline ByteBuffer &operator =(const ByteBuffer &buffer); // utils: &byteArray[n] /** * @brief read u8 reference relative to the base * @warning not const safe * @param idx * @return */ inline AuUInt8 &operator [](AuUInt idx) const; // utils: if (byteArray) -> if (byteArray->IsValid()) inline operator bool() const; inline AuList RemainingBytesToVector(bool endAtWrite = true) const; // ... utils are mostly const functions that provide language intrinsics, access, and container-like compatibility // ByteBuffer specific utils can be found under Utilities // Seek / Position inline auline bool ReaderTryGoForward(AuUInt32 offset); inline auline bool ReaderTryGoBack(AuUInt32 offset); inline auline bool WriterTryGoForward(AuUInt32 offset); inline auline AuUInt RemainingWrite(bool endAtRead = true) const; inline auline AuUInt RemainingBytes(bool endAtWrite = true) const; inline auline bool Skip(AuUInt count); inline auline AuUInt GetReadOffset() const; inline auline AuUInt GetWriteOffset() const; inline auline void ResetReadPointer(); inline auline MemoryViewRead GetNextLinearRead(); inline auline MemoryViewWrite GetNextLinearWrite(); inline auline bool CanWrite(AuUInt length); inline auline bool CanRead(AuUInt length); /** * @brief Returns a linear amount of memory for exactly length of linear bytes * @param length * @return */ inline auline MemoryViewRead GetLinearReadableForExactly(AuUInt length) { return GetLinearReadable(length); } inline auline MemoryViewWrite GetLinearWriteableForExactly(AuUInt length) { return GetLinearWriteable(length); } /** * @brief Returns a linear amount of memory of at least length * @param length * @return */ inline auline MemoryViewRead GetLinearReadableForAtleast(AuUInt length); inline auline MemoryViewWrite GetLinearWriteableForAtleast(AuUInt length); /** * @brief Returns a linear amount of memory capped to length * @param length * @return */ inline auline MemoryViewRead GetLinearReadableForNoMore(AuUInt length); inline auline MemoryViewWrite GetLinearWriteableForNoMore(AuUInt length); /// legacy: ForExactly variant inline auline MemoryViewRead GetLinearReadable(AuUInt length); /// legacy: ForExactly variant inline auline MemoryViewWrite GetLinearWriteable(AuUInt length); /** * @brief * Expected use-case: * Consider the prototype: void MyFunction(AuByteBuffer &output); * * Where the valid expected uses fall in line with the following truth table: * * * PASS * AuByteBuffer defaultBuffer; * MyFunction(defaultBuffer); * if (defaultBuffer) {} * * * FAIL * AuByteBuffer defaultBuffer; * defaultBuffer.WriteTagged(32) * MyFunction(defaultBuffer); * if (defaultBuffer) {} * * * FAIL * AuByteBuffer defaultBuffer; * MyFunction(defaultBuffer); * MyFunction(defaultBuffer); // !!! * * * PASS * AuByteBuffer writeStream(512); * writeStream.WriteTagged(32) * MyFunction(defaultBuffer); * if (defaultBuffer) {} * * @param length * @return */ inline auline MemoryViewWrite GetOrAllocateLinearWriteable(AuUInt length); // Memory operations inline auline bool Allocate(AuUInt length); inline auline bool Allocate(AuUInt length, AuUInt alignment); inline auline bool SetBuffer(MemoryViewRead readView, bool bMoveWriteHeadForReaders = true); /** * @brief Releases excess memory (comparable to shrink to fit in the c++ stl) * @return */ inline auline void GC(); /** * @brief Releases all resources and resets the bytebuffer without an allocation */ inline void Reset(); /** * @brief Expands the underlying buffer allocation to at least length. * Does nothing on failure. Programs can try to allocate in real time * and handle the write error flag condition from there. To pull the * real buffer size, see member field allocSize. * @param length */ inline void Reserve(AuUInt length); /** * @brief Is allocated or dummy object? * @return */ inline auline bool IsEmpty() const; /** * @brief Is an error flag set? * @return */ inline auline bool HasStreamError() const; /** * @brief Returns true so long as * 1) an error has not occured or, * 1) the relevant error flag was reset; and * 2) there is a valid underlying buffer * * @return */ inline auline bool IsValid() const; /** * @brief Allocate at least length bytes, without adjusting the relative read/write head offsets * @param length * @return */ inline auline bool Resize(AuUInt length); // Basic Read Write inline auline AuUInt Write(const void *buffer, AuUInt requestLength); inline auline AuUInt Read(void *out, AuUInt requestedLength, bool peek = false); // String API inline bool WriteString(AuROString string, EStringType type = EStringType::eStringDword, Locale::ECodePage codepage = Locale::ECodePage::eUTF8); inline bool ReadString(AuString &string, EStringType type = EStringType::eStringDword, Locale::ECodePage codepage = Locale::ECodePage::eUTF8); // Copy, concat, etc inline AuUInt WriteFromEx(ByteBuffer &buffer, AuUInt uLength); inline bool WriteFrom(ByteBuffer &buffer); // Utilities inline bool Trim(AuUInt tail); inline bool Pad(AuUInt16 aPowOf2, AuUInt8 magicCharacter = '\x00', bool bUpdateWriteHead = true); inline bool Fill(AuUInt length, AuUInt8 magicCharacter = '\x00'); // Templated read/write for **PODs** // (yes, there's some support for non-primitive classes this early into the abstraction.) // (AuString and some other containers may work, but really, we need a typed bytebuffer.) template T Read(); // Panics if flagReadError is true by the end of deserialization template T ReadChecked(); template using ReadHack_t = AuConditional_t, AuUInt, bool>; template using WriteHack_t = AuConditional_t, AuUInt, bool>; template WriteHack_t Write(const T &in); template WriteHack_t Write(T &in); template ReadHack_t Read(T &out); inline auline AuUInt Read(const MemoryViewWrite &write) { return Read(write.ptr, write.length); } template bool WriteTagged(const T &in); template bool WriteTagged(T &in); template bool ReadTagged(T &out); template T ReadTagged(); // inline AuUInt calcDifferenceBetweenHeadsUnsigned(AuUInt8 *pHeadPointer, AuUInt8 *pSubtrahend) { if (pHeadPointer > pSubtrahend) { return pHeadPointer - pSubtrahend; } else { return (pSubtrahend - this->base) + ((this->base + this->length) - pHeadPointer); } } inline AuSInt calcDifferenceBetweenHeadsSigned(AuUInt8 *pHeadPointer, AuUInt8 *pSubtrahend) { if (pHeadPointer > pSubtrahend) { return pHeadPointer - pSubtrahend; } else { return -(pSubtrahend - this->base); } } private: inline auline bool Allocate2(AuUInt length, AuUInt alignment); inline auline bool Allocate2(AuUInt length); }; struct SharedByteBuffer : ByteBuffer { AuSPtr memory; inline SharedByteBuffer(const MemoryViewWrite &view, AuSPtr pRAIIParentOwner) : ByteBuffer(), __view(view) { this->allocSize = 0; this->base = (AuUInt8 *)view.ptr; this->length = view.length; this->readPtr = this->base; this->writePtr = this->base; this->flagNoFree = true; this->memory = pRAIIParentOwner; } inline SharedByteBuffer(const MemoryViewWrite &view) : ByteBuffer(), __view(view) { this->allocSize = 0; this->base = (AuUInt8 *)view.ptr; this->length = view.length; this->readPtr = this->base; this->writePtr = this->base; this->flagNoFree = true; } inline SharedByteBuffer(const MemoryViewRead &view, AuSPtr pRAIIParentOwner) : ByteBuffer(), __view(*(MemoryViewWrite *)&view) { this->allocSize = 0; this->base = (AuUInt8 *)view.ptr; this->length = view.length; this->readPtr = this->base; this->writePtr = this->base + this->length; this->flagNoFree = true; this->flagWriteError = true; this->memory = pRAIIParentOwner; } inline SharedByteBuffer(const MemoryViewRead &view) : ByteBuffer(), __view(*(MemoryViewWrite *)&view) { this->allocSize = 0; this->base = (AuUInt8 *)view.ptr; this->length = view.length; this->readPtr = this->base; this->writePtr = this->base + this->length; this->flagNoFree = true; this->flagWriteError = true; } private: MemoryViewWrite __view; }; struct SharableByteBuffer : ByteBuffer, AuEnableSharedFromThis { inline SharableByteBuffer() : ByteBuffer() { } inline SharableByteBuffer(AuUInt uLength, bool circular = false, bool expandable = false) : ByteBuffer(uLength, circular, expandable) { } inline SharableByteBuffer(AuUInt uLength, AuUInt uAlignment, bool circular = false, bool expandable = false) : ByteBuffer(uLength, uAlignment, circular, expandable) { } /// @deprecated (partially) (wont remove. AuSPtrs are now an anti-pattern) inline AuSPtr ToSharedWriteView() { MemoryViewWrite view = ByteBuffer::operator MemoryViewWrite(); if (!view) { return {}; } if (auto pView = AuMakeShared(view.ptr, view.length, &this->uInUseCounter, this->GetSharedBlock())) { return pView; } else { return {}; } } /// @deprecated (partially) (wont remove. AuSPtrs are now an anti-pattern) inline AuSPtr ToSharedReadView() const { MemoryViewRead view = ByteBuffer::operator MemoryViewRead(); if (!view) { return {}; } if (auto pView = AuMakeShared(view.ptr, view.length, (AuAUInt32 *)&this->uInUseCounter, this->GetSharedBlock())) { return pView; } else { return {}; } } inline MemoryViewWrite ToSafeWriteView() { MemoryViewWrite view = ByteBuffer::operator MemoryViewWrite(); if (!view) { return {}; } return MemoryViewWrite(view.ptr, view.length, &this->uInUseCounter, this->GetSharedBlock()); } inline MemoryViewRead ToSafeReadView() const { MemoryViewRead view = ByteBuffer::operator MemoryViewRead(); if (!view) { return {}; } return MemoryViewRead(view.ptr, view.length, (AuAUInt32 *)&this->uInUseCounter, this->GetSharedBlock()); } inline operator AuSPtr() { return this->ToSharedWriteView(); } inline operator AuSPtr() { return this->ToSharedReadView(); } inline operator MemoryViewWrite() { return this->ToSafeWriteView(); } inline operator MemoryViewRead() const { return this->ToSafeReadView(); } private: // TODO: under const methods, AuSharedFromThis, the static pointer cast, and the AuMemoryView constructor is hopelessly broken // I've always considered c++s constness fundamentally flawed and designed by idiots. I'm not sinking time into fixes these yet. // This should be just an AuSharedFromThis() // But, no, Java, C++, and everybody else gets constness wrong to solve a series of bugs * that can be solved with simple encapsulation * // (DOP + encapsulated API boundaries ftw) // I dont care to fix this, or use the stupid mutable keyword on the usage counter, for now. It's a waste of my time. AuSPtr GetSharedBlock() const { return AuSPtr(this->shared_from_this(), (void *)this); } }; static ByteBuffer NewResizableBuffer(AuUInt32 length = 0) { return ByteBuffer(length, false, true); } static ByteBuffer NewRingBuffer(AuUInt32 length = 1024 * 5) { return ByteBuffer(length, true, false); } static AuSPtr NewSharableResizableBuffer(AuUInt32 length = 0) { auto pThat = AuMakeShared(length, false, true); if (!(pThat && *pThat)) { return {}; } return pThat; } static AuSPtr NewSharableBuffer(AuUInt32 length = 0) { auto pThat = AuMakeShared(length); if (!(pThat && *pThat)) { return {}; } return pThat; } }