307 lines
11 KiB
C++
307 lines
11 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
|
|
|
|
namespace Aurora::Memory
|
|
{
|
|
static const auto kBufferPageSize = 512;
|
|
//static const auto kBufferBasePower = 8;
|
|
static const auto kBufferInitialPower = 9;// -kBufferBasePower; // 4-bit integer
|
|
|
|
/***
|
|
* A bytebuffer object represents a linear, partially-linear resizable, buffer **or** a ring buffer.
|
|
*
|
|
* Use cases for a ring buffer include wrapping streams for a use case in which the consumer may
|
|
* expect arbitrary stream seeks of an otherwise limited consume-once stream
|
|
*
|
|
* IE;
|
|
* -> 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-Sdemand or ad hoc,
|
|
* writing to its write head pointer, while never running out of space so long as the decompressed ring
|
|
* read head continues moving
|
|
*
|
|
* Small, linear, write/read-once [de]serialization use cases may elect to allocate a buffer and
|
|
* follow the linear fast paths; perhaps even enabling flagExpandable for hopefully-smarter-than-stdvec-scaling
|
|
*
|
|
* Ring buffers scale from the write head, to the read head, potentially going-around in the process
|
|
*
|
|
* Linear flagExpandable buffers scale from [0, length]; reallocating at end of buffer if flagExpandable is enabled
|
|
* if expanding is enabled,
|
|
* realloc(max(size + offset, (offset / kBufferPageSize + 1) * kBufferPageSize))
|
|
*
|
|
* Deprecates INetworkStream, fixes allocation issues around compression backends
|
|
* Superseeds abuse of AuList<AuUInt8> for binary blobs, alongside Memory::Array
|
|
*/
|
|
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;
|
|
// - implicit padding
|
|
AuUInt8 scaleSize;// : 4; screw it.... we should just take 6 * (4/8) up to 32/64, we wont go up a slab allocation bucket, whatever you want to call it
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
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 = {};
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
this->scaleSize = kBufferInitialPower;
|
|
AuMemcpy(this->base, base, length);
|
|
}
|
|
|
|
ByteBuffer() : flagCircular(0), flagExpandable(true), flagReadError(0), flagWriteError(0)
|
|
{
|
|
this->base = {};
|
|
this->length = {};
|
|
this->allocSize = {};
|
|
this->readPtr = {};
|
|
this->writePtr = {};
|
|
this->scaleSize = kBufferInitialPower;
|
|
}
|
|
|
|
~ByteBuffer()
|
|
{
|
|
if (this->base)
|
|
{
|
|
Free(this->base);
|
|
}
|
|
}
|
|
|
|
inline void ResetPositions()
|
|
{
|
|
this->flagReadError = 0;
|
|
this->flagWriteError = 0;
|
|
this->readPtr = base;
|
|
this->writePtr = base;
|
|
}
|
|
|
|
// Iterator
|
|
inline auline AuUInt8 * data() const;
|
|
inline auline AuUInt size() const;
|
|
inline auline AuUInt8 * begin() const;
|
|
inline auline AuUInt8 * end() const;
|
|
inline auline bool empty() const;
|
|
inline void clear();
|
|
inline void resize(AuUInt size);
|
|
inline void reserve(AuUInt size);
|
|
|
|
// Utils To alternative types
|
|
inline auline AuList<AuUInt8> ToVector() const;
|
|
|
|
inline AuUInt32 GetAllocationPower() const;
|
|
inline operator AuList<AuUInt8>() const;
|
|
inline operator MemoryViewRead() const;
|
|
|
|
// Internal buffer comparison
|
|
inline bool operator ==(const AuList<AuUInt8> &) const;
|
|
inline bool operator ==(const MemoryViewRead &) const;
|
|
inline bool operator ==(const ByteBuffer &) const;
|
|
|
|
// Move assignment
|
|
inline ByteBuffer &operator =(ByteBuffer &&);
|
|
|
|
// &byteArray[n]
|
|
inline AuUInt8 &operator [](AuUInt idx);
|
|
|
|
// if (byteArray) -> if (byteArray->IsValid())
|
|
inline operator bool() const;
|
|
|
|
inline AuList<AuUInt8> RemainingBytesToVector(bool endAtWrite = true) const;
|
|
|
|
|
|
// 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);
|
|
inline auline AuUInt RemainingBytes(bool endAtWrite = true);
|
|
|
|
inline auline bool Skip(AuUInt count);
|
|
inline auline AuUInt GetReadOffset() const;
|
|
inline auline AuUInt GetWriteOffset() const;
|
|
|
|
|
|
inline AuOptional<AuUInt8 *> WriterTryGetWriteHeadFor(AuUInt32 nBytes);
|
|
|
|
// Memory operations
|
|
|
|
inline auline bool Allocate(AuUInt length, bool fast = true);
|
|
inline auline bool SetBuffer(const void *in, AuUInt length);
|
|
inline auline bool SetBuffer(const AuList<AuUInt8> &buffer);
|
|
|
|
inline auline void GC();
|
|
inline void Reset();
|
|
inline void Reserve(AuUInt length);
|
|
inline auline bool IsEmpty() const;
|
|
inline auline bool HasStreamError() const;
|
|
inline auline bool IsValid() const;
|
|
|
|
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);
|
|
|
|
// 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);
|
|
}
|
|
} |