/*** 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 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(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(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 &vector, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0) { this->base = FAlloc(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(length); if (!this->base) { Reset(); return; } this->length = length; this->allocSize = length; this->readPtr = this->base; this->writePtr = this->base; this->scaleSize = kBufferInitialPower; } template ByteBuffer(T *base, T *end, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0) { auto length = static_cast(end - base) * sizeof(T); this->base = ZAlloc(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 ToVector() const; inline AuUInt32 GetAllocationPower() const; inline operator AuList() const; inline operator MemoryViewRead() const; // Internal buffer comparison inline bool operator ==(const AuList &) 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 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 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 &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 T Read(); template bool Write(const T &in); template 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); } }