AuroraRuntime/Include/Aurora/Memory/ByteBuffer.hpp
Reece Wilson 473e7d30dd [+] AuMemoryView::[begin,end]() of u8 pointers
[*] Prevent AuByteBuffer::begin from exploding user code by providing normalized begin positions
[*] Update AuByteBuffer string write
[*] Redefine AuByteBuffer::data() as the bytebuffers base
[*] Various pointer normalization improvements in ByteBuffer_Position.inl
[*] Update comments
2022-09-27 10:01:35 +01:00

461 lines
16 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>
namespace Aurora::Memory
{
enum class EStringType
{
eStringTerminated,
eStringByte,
eStringWord,
eStringDword,
eStringQword
};
static const auto kBufferPageSize = 512;
//static const auto kBufferBasePower = 8;
static const auto kBufferInitialPower = 9;// -kBufferBasePower; // 4-bit integer
/***
* A bytebuffer object represents a "page" aligned (optionally) resizable buffer **or** a ring buffer.
* Trivial linear serialization use cases will follow the linear fast paths, not those of a ring buffer.
*
* Ring buffers are used to wrap streams when the consumer may expect arbitrary stream seeks of an otherwise
* limited consume-once stream
*
* EG (old):
* -> Peeking a header in a datagram, or tcp stream; where instead of freeing the datagram or double
* buffering the network stack when required, a ring buffer is used to prevent reallocation on each frame
* -> Peeking, or seeking back after, compression read. A compression api could be fed on-known-input-available
* or on-demand, writing to its write head pointer, while never running out of space so long as the
* decompressed ring read head continues moving
*
* EG (2022):
* -> AuProtocol ProtocolPiece buffers
* -> AuCompression internal buffer
* -> A socket's output stream / space for user-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
{
///////////////////////////////////////////////////////////////////////
// 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;
/// Stream pointer
AuUInt8 *readPtr;
/// Stream pointer
AuUInt8 *writePtr;
///////////////////////////////////////////////////////////////////////
// Stable ByteBuffer ABI Header; u32 flags //
///////////////////////////////////////////////////////////////////////
/// Is ring buffer?
AuUInt8 flagCircular : 1;
/// Should resize linear buffer to accommodate additional writes
AuUInt8 flagExpandable : 1;
AuUInt8 flagReadError : 1;
AuUInt8 flagWriteError : 1;
// TODO: flag: allow circular overrun to allow for 100% access of the buffer from either read head
// - implicit padding
AuUInt8 scaleSize;//
///////////////////////////////////////////////////////////////////////
/**
* @brief Move constructor
* @param buffer
*/
inline ByteBuffer(ByteBuffer &&buffer)
{
this->base = buffer.base;
this->length = buffer.length;
this->allocSize = buffer.length;
this->writePtr = this->base + (buffer.writePtr - buffer.base);
this->readPtr = this->base + (buffer.readPtr - buffer.base);
this->flagCircular = buffer.flagCircular;
this->flagExpandable = buffer.flagExpandable;
this->scaleSize = buffer.scaleSize;
buffer.base = {};
buffer.length = {};
buffer.allocSize = {};
buffer.writePtr = {};
buffer.readPtr = {};
buffer.flagCircular = {};
buffer.flagExpandable = {};
buffer.scaleSize = {};
}
/**
* @brief Copy with possible preserve pointers
* @param buffer
* @param preservePointers
*/
inline ByteBuffer(const ByteBuffer &buffer, bool preservePointers = true)
{
this->base = FAlloc<AuUInt8 *>(buffer.length);
if (!this->base)
{
Reset();
return;
}
this->length = buffer.length;
this->allocSize = buffer.length;
if (preservePointers)
{
this->writePtr = this->base + (buffer.writePtr - buffer.base);
this->readPtr = this->base + (buffer.readPtr - buffer.base);
}
else
{
this->writePtr = this->base;
this->readPtr = this->base;
}
AuMemcpy(this->base, buffer.base, this->length);
this->flagCircular = buffer.flagCircular;
this->flagExpandable = buffer.flagExpandable;
this->scaleSize = buffer.scaleSize;
}
/**
* @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) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
{
this->base = FAlloc<AuUInt8 *>(length);
if (!this->base)
{
Reset();
return;
}
this->length = length;
this->allocSize = length;
this->readPtr = this->base;
this->writePtr = this->readPtr + this->length;
AuMemcpy(this->base, in, this->length);
this->scaleSize = kBufferInitialPower;
}
inline ByteBuffer(const AuList<AuUInt8> &vector, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
{
this->base = FAlloc<AuUInt8 *>(vector.size());
if (!this->base)
{
Reset();
return;
}
this->length = vector.size();
this->allocSize = vector.size();
this->readPtr = this->base;
this->writePtr = this->readPtr + this->length;
AuMemcpy(this->base, vector.data(), this->length);
this->scaleSize = kBufferInitialPower;
}
inline ByteBuffer(AuUInt length, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
{
if (!length)
{
Reset();
return;
}
this->base = ZAlloc<AuUInt8 *>(length);
if (!this->base)
{
Reset();
return;
}
this->length = length;
this->allocSize = length;
this->readPtr = this->base;
this->writePtr = this->base;
this->scaleSize = kBufferInitialPower;
}
inline ByteBuffer(AuUInt length, AuUInt alignment, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
{
if (!length)
{
Reset();
return;
}
this->base = ZAlloc<AuUInt8 *>(length, alignment);
if (!this->base)
{
Reset();
return;
}
this->length = length;
this->allocSize = length;
this->readPtr = this->base;
this->writePtr = this->base;
this->scaleSize = kBufferInitialPower;
}
template<typename T>
ByteBuffer(T *base, T *end, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
{
auto length = static_cast<AuUInt>(end - base) * sizeof(T);
this->base = ZAlloc<AuUInt8 *>(length);
if (!this->base)
{
Reset();
return;
}
this->length = length;
this->allocSize = length;
this->readPtr = this->base;
this->writePtr = this->base + length;
this->scaleSize = kBufferInitialPower;
AuMemcpy(this->base, base, length);
}
/**
* @brief Default constructor, allocates an auto-expanding linear bytebuffer
*/
inline ByteBuffer() : flagCircular(0), flagExpandable(true), flagReadError(0), flagWriteError(0)
{
this->base = {};
this->length = {};
this->allocSize = {};
this->readPtr = {};
this->writePtr = {};
this->scaleSize = kBufferInitialPower;
}
inline ~ByteBuffer()
{
if (this->base)
{
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 AuUInt8 * begin() const;
/**
* @brief linear read end
* @warning writers should use ::GetLinearWriteable(uDesiredLength) or ::GetOrAllocateLinearWriteable(...)
* @return
*/
inline auline AuUInt8 * end() 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 relative to the read head
* @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);
inline auline MemoryViewRead GetLinearReadable(AuUInt length);
inline auline MemoryViewWrite GetLinearWriteable(AuUInt length);
/**
* @brief Used to write into existing linear space (writing into a stream or preallocated packet)
* or allocates the linear space if permitted (eg: a default constructor call would allow this).
*
* This is used to create APIs that accept an input bytebuffer write parameter over write view based apis.
* @param length
* @return
*/
inline auline MemoryViewWrite GetOrAllocateLinearWriteable(AuUInt length);
// Memory operations
inline auline bool Allocate(AuUInt length, bool fast = true);
inline auline bool Allocate(AuUInt length, AuUInt alignment, bool fast = true);
inline auline bool SetBuffer(const void *in, AuUInt length);
inline auline bool SetBuffer(const AuList<AuUInt8> &buffer);
/**
* @brief Releases excess memory (like, shrink to fit in c++)
* @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(const AuString &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 bool WriteFrom(ByteBuffer &buffer, AuUInt length);
// Utilities
inline bool Trim(AuUInt tail);
inline bool Pad(AuUInt16 aPowOf2, AuUInt8 magicCharacter = '\x00');
inline bool Fill(AuUInt length, AuUInt8 magicCharacter = '\x00');
// Typed read/write
template<typename T>
T Read();
template<typename T>
bool Write(const T &in);
template<typename T>
bool Read(T &out);
};
static ByteBuffer NewResizableBuffer(AuUInt32 length = 0)
{
return ByteBuffer(length, false, true);
}
static ByteBuffer NewRingBuffer(AuUInt32 length = 1024 * 5)
{
return ByteBuffer(length, true, false);
}
}