AuroraRuntime/Include/Aurora/Memory/ByteBuffer.hpp
Jamie Reece Wilson 978693559e [*] QOL / Hardening / Optimizations / Bug fixes
(but not really. this is just some patchwork)
2024-10-02 00:47:44 +01:00

668 lines
23 KiB
C++

/***
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 <Aurora/Locale/ECodePage.hpp>
#include <Aurora/Utility/PrivData.hpp>
#include <Aurora/Threading/Waitables/FutexWaitable.hpp>
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<AuUInt8> & output parameters;
* * stream of arbitrary serialization/deserialization, seekable read/writes;
* * ...tuple of <buffer base, read head, write head> 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<AuUInt8> 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<AuUInt8> &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<typename T>
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<AuUInt8> ToVector() const;
inline AuUInt32 GetAllocationPower() const;
inline operator MemoryViewRead() const;
inline operator MemoryViewWrite();
// utils: Internal buffer comparison
inline bool operator ==(const AuList<AuUInt8> &) 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<AuUInt8> 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<AuUInt32>(32)
* MyFunction(defaultBuffer);
* if (defaultBuffer) {}
*
* * FAIL
* AuByteBuffer defaultBuffer;
* MyFunction(defaultBuffer);
* MyFunction(defaultBuffer); // !!!
*
* * PASS
* AuByteBuffer writeStream(512);
* writeStream.WriteTagged<AuUInt32>(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<typename T>
T Read();
// Panics if flagReadError is true by the end of deserialization
template<typename T>
T ReadChecked();
template<typename T>
using ReadHack_t = AuConditional_t<AuIsSame_v<MemoryViewWrite, T>, AuUInt, bool>;
template<typename T>
using WriteHack_t = AuConditional_t<AuIsSame_v<MemoryViewRead, T>, AuUInt, bool>;
template<typename T>
WriteHack_t<T> Write(const T &in);
template<typename T>
WriteHack_t<T> Write(T &in);
template<typename T>
ReadHack_t<T> Read(T &out);
inline auline AuUInt Read(const MemoryViewWrite &write)
{
return Read(write.ptr, write.length);
}
template<typename T>
bool WriteTagged(const T &in);
template<typename T>
bool WriteTagged(T &in);
template<typename T>
bool ReadTagged(T &out);
template<typename T>
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<void> memory;
inline SharedByteBuffer(const MemoryViewWrite &view, AuSPtr<void> 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<void> 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<SharableByteBuffer>
{
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. AuSPtr<AuMemoryViewXXX>s are now an anti-pattern)
inline AuSPtr<MemoryViewWrite> ToSharedWriteView()
{
MemoryViewWrite view = ByteBuffer::operator MemoryViewWrite();
if (!view)
{
return {};
}
if (auto pView = AuMakeShared<MemoryViewWrite>(view.ptr, view.length, &this->uInUseCounter, this->GetSharedBlock()))
{
return pView;
}
else
{
return {};
}
}
/// @deprecated (partially) (wont remove. AuSPtr<AuMemoryViewXXX>s are now an anti-pattern)
inline AuSPtr<MemoryViewRead> ToSharedReadView() const
{
MemoryViewRead view = ByteBuffer::operator MemoryViewRead();
if (!view)
{
return {};
}
if (auto pView = AuMakeShared<MemoryViewRead>(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<MemoryViewWrite>()
{
return this->ToSharedWriteView();
}
inline operator AuSPtr<MemoryViewRead>()
{
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<void> GetSharedBlock() const
{
return AuSPtr<void>(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<SharableByteBuffer> NewSharableResizableBuffer(AuUInt32 length = 0)
{
auto pThat = AuMakeShared<SharableByteBuffer>(length, false, true);
if (!(pThat && *pThat))
{
return {};
}
return pThat;
}
static AuSPtr<SharableByteBuffer> NewSharableBuffer(AuUInt32 length = 0)
{
auto pThat = AuMakeShared<SharableByteBuffer>(length);
if (!(pThat && *pThat))
{
return {};
}
return pThat;
}
}