Reece
99c5e1fa65
[*] Split up Aurora Async [*] Split Async app into seperate ThreadPool concept [*] Fix various OSThread bugs and tls transfer issues [*] Set default affinity to 0xFFFFFFFF [*] Update Build script [+] Add AuTuplePopFront [+] New Network Interface (unimplemented) [*] Stub out the interfaces required for a better logger [*] Fix Win32 ShellExecute bug; windows 11 struggles without explicit com init per the docs - now deferring to thread pool [*] Update gitignore [*] Follow XDG home standard [*] Refactor some namespaces to use the shorthand aliases [*] Various stability fixes
597 lines
19 KiB
C++
597 lines
19 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;
|
|
|
|
/***
|
|
* 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;
|
|
* -> peaking 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
|
|
* -> peaking, 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?
|
|
AuUInt32 flagCircular : 1;
|
|
|
|
/// Should resize linear buffer to accommodate additional writes
|
|
AuUInt32 flagExpandable : 1;
|
|
|
|
AuUInt32 flagReadError : 1;
|
|
AuUInt32 flagWriteError : 1;
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
ByteBuffer(const ByteBuffer &buffer, bool preservePointers = true)
|
|
{
|
|
this->base = ZAlloc<AuUInt8 *>(buffer.length);
|
|
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;
|
|
}
|
|
std::memcpy(this->base, buffer.base, this->length);
|
|
this->flagCircular = buffer.flagCircular;
|
|
this->flagExpandable = buffer.flagExpandable;
|
|
}
|
|
|
|
ByteBuffer(const void *in, AuUInt length, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
|
|
{
|
|
this->base = ZAlloc<AuUInt8 *>(length);
|
|
this->length = length;
|
|
this->allocSize = length;
|
|
this->readPtr = this->base;
|
|
this->writePtr = this->readPtr + this->length;
|
|
std::memcpy(this->base, in, this->length);
|
|
}
|
|
|
|
ByteBuffer(const AuList<AuUInt8> &vector, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
|
|
{
|
|
this->base = ZAlloc<AuUInt8 *>(length);
|
|
this->length = vector.size();
|
|
this->allocSize = vector.size();
|
|
this->readPtr = this->base;
|
|
this->writePtr = this->readPtr + this->length;
|
|
std::memcpy(this->base, vector.data(), this->length);
|
|
}
|
|
|
|
ByteBuffer(AuUInt length, bool circular = false, bool expandable = false) : flagCircular(circular), flagExpandable(expandable), flagReadError(0), flagWriteError(0)
|
|
{
|
|
this->base = ZAlloc<AuUInt8 *>(length);
|
|
this->length = length;
|
|
this->allocSize = length;
|
|
this->readPtr = this->base;
|
|
this->writePtr = this->base;
|
|
}
|
|
|
|
ByteBuffer() : flagCircular(0), flagExpandable(0), flagReadError(0), flagWriteError(0)
|
|
{
|
|
this->base = {};
|
|
this->length = {};
|
|
this->allocSize = {};
|
|
this->readPtr = {};
|
|
this->writePtr = {};
|
|
}
|
|
|
|
~ByteBuffer()
|
|
{
|
|
if (this->base)
|
|
{
|
|
Free(this->base);
|
|
}
|
|
}
|
|
|
|
inline void ResetPositions()
|
|
{
|
|
this->flagReadError = 0;
|
|
this->flagWriteError = 0;
|
|
this->readPtr = base;
|
|
this->writePtr = base;
|
|
}
|
|
|
|
// Iterator
|
|
AUKN_SYM AuUInt8 *begin();
|
|
AUKN_SYM AuUInt8 *end();
|
|
|
|
// To alternative types
|
|
AUKN_SYM AuList<AuUInt8> ToVector();
|
|
|
|
// Seek
|
|
AUKN_SYM bool ReaderTryGoForward(AuUInt32 offset);
|
|
AUKN_SYM bool ReaderTryGoBack(AuUInt32 offset);
|
|
|
|
AUKN_SYM bool WriterTryGoForward(AuUInt32 offset);
|
|
|
|
|
|
AUKN_SYM AuOptional<AuUInt8 *> WriterTryGetWriteHeadFor(AuUInt32 nBytes);
|
|
|
|
// Template implementation
|
|
// TODO: move to .inl
|
|
|
|
template<typename T>
|
|
inline bool Read(T &out)
|
|
{
|
|
if constexpr (std::is_class_v<T>)
|
|
{
|
|
if constexpr (AuIsBaseOfTemplate<AURORA_RUNTIME_AU_LIST, std::remove_reference_t<T>>::value)
|
|
{
|
|
if (Read<AuUInt32>() != sizeof(typename T::value_type))
|
|
{
|
|
this->flagReadError = true;
|
|
return false;
|
|
}
|
|
|
|
auto len = Read<AuUInt32>();
|
|
out.resize(len);
|
|
|
|
for (auto i = 0u; i < len; i++)
|
|
{
|
|
Read<T::value_type>(out[i]);
|
|
}
|
|
|
|
return !this->flagReadError;
|
|
}
|
|
else if constexpr (std::is_same_v<std::remove_reference_t<T>, AuString>)
|
|
{
|
|
out.resize(Read<AuUInt32>());
|
|
Read(out.data(), out.size());
|
|
return !this->flagReadError;
|
|
}
|
|
}
|
|
|
|
auto skipped = Read(&out, sizeof(T));
|
|
if (skipped != sizeof(T))
|
|
{
|
|
this->flagReadError = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
template<typename T>
|
|
inline T Read()
|
|
{
|
|
T a{};
|
|
Read(a);
|
|
return a;
|
|
}
|
|
|
|
template<typename T>
|
|
inline bool Write(const T &in)
|
|
{
|
|
if constexpr (std::is_class_v<T>)
|
|
{
|
|
if constexpr (AuIsBaseOfTemplate<AURORA_RUNTIME_AU_LIST, std::remove_reference_t<T>>::value)
|
|
{
|
|
Write<AuUInt32>(sizeof(typename T::value_type));
|
|
Write<AuUInt32>(AuUInt32(in.size()));
|
|
|
|
for (const auto &item : in)
|
|
{
|
|
Write<T::value_type>(item);
|
|
}
|
|
|
|
return !this->flagWriteError;
|
|
}
|
|
else if constexpr (std::is_same_v<std::remove_reference_t<T>, AuString>)
|
|
{
|
|
Write<AuUInt32>(AuUInt32(in.size()));
|
|
Write(in.data(), in.size());
|
|
return !this->flagWriteError;
|
|
}
|
|
}
|
|
|
|
auto skipped = Write(&in, sizeof(T));
|
|
if (skipped != sizeof(T))
|
|
{
|
|
this->flagWriteError = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline bool Resize(AuUInt length)
|
|
{
|
|
AuUInt oldWriteIdx, oldReadIdx, oldLength, newLength;
|
|
AuUInt8 *nextRead, *nextWrite, *nextPtr;
|
|
|
|
if (this->allocSize > length)
|
|
{
|
|
this->length = length;
|
|
|
|
oldLength = this->length;
|
|
newLength = length;
|
|
nextPtr = this->base;
|
|
oldWriteIdx = this->writePtr - this->base;
|
|
oldReadIdx = this->readPtr - this->base;
|
|
nextRead = nextPtr + oldReadIdx;
|
|
nextWrite = nextPtr + oldWriteIdx;
|
|
}
|
|
else
|
|
{
|
|
oldLength = this->length;
|
|
newLength = std::max(AuUInt(length), AuUInt(((this->allocSize / kBufferPageSize) + 1) * kBufferPageSize));
|
|
|
|
nextPtr = ZRealloc(this->base, newLength);
|
|
if (!nextPtr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
oldWriteIdx = this->writePtr - this->base;
|
|
oldReadIdx = this->readPtr - this->base;
|
|
nextRead = nextPtr + oldReadIdx;
|
|
nextWrite = nextPtr + oldWriteIdx;
|
|
|
|
this->allocSize = newLength;
|
|
this->length = length;
|
|
}
|
|
|
|
this->base = nextPtr;
|
|
|
|
if (!flagCircular)
|
|
{
|
|
this->readPtr = nextRead;
|
|
this->writePtr = nextWrite;
|
|
}
|
|
else
|
|
{
|
|
if (this->writePtr > this->readPtr)
|
|
{
|
|
this->readPtr = nextRead;
|
|
this->writePtr = nextWrite;
|
|
}
|
|
else
|
|
{
|
|
auto expansion = newLength - oldLength;
|
|
auto movableTail = std::min(oldWriteIdx, expansion);
|
|
|
|
std::memcpy(nextPtr + oldLength, nextPtr, movableTail);
|
|
|
|
this->readPtr = nextRead;
|
|
this->writePtr = nextPtr + oldLength + movableTail;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline AuUInt Write(const void *buffer, AuUInt requestLength)
|
|
{
|
|
AuUInt linearOverhead = 0, toReadOverhead = 0, linearWritable = 0, toReadWritable = 0, writable = 0;
|
|
auto cptr = reinterpret_cast<const AuUInt8 *>(buffer);
|
|
|
|
if (flagCircular)
|
|
{
|
|
// 0 1 2 3 4 5
|
|
// W R
|
|
// 6 - (1) = 5 -> read bound; we have zero overhead not 5
|
|
|
|
if (writePtr < readPtr)
|
|
{
|
|
// Handle read-bound writes
|
|
linearOverhead = readPtr - writePtr;
|
|
toReadOverhead = 0;
|
|
}
|
|
else
|
|
{
|
|
// Handle ordinary stream consume bound IO
|
|
linearOverhead = length - (writePtr - base);
|
|
toReadOverhead = readPtr - base;
|
|
}
|
|
|
|
writable = std::min(linearOverhead + toReadOverhead, requestLength);
|
|
|
|
linearWritable = std::min(linearOverhead, requestLength);
|
|
toReadWritable = writable - linearWritable;
|
|
|
|
if (cptr)
|
|
{
|
|
std::memcpy(writePtr, cptr, linearWritable);
|
|
}
|
|
writePtr += linearWritable;
|
|
|
|
if (toReadWritable)
|
|
{
|
|
writePtr = base;
|
|
if (cptr)
|
|
{
|
|
std::memcpy(writePtr, cptr + linearOverhead, toReadWritable);
|
|
}
|
|
writePtr += toReadWritable;
|
|
}
|
|
|
|
#if 0
|
|
if (writePtr == base + length)
|
|
{
|
|
writePtr = base;
|
|
}
|
|
#endif
|
|
|
|
return linearWritable + toReadWritable;
|
|
}
|
|
else
|
|
{
|
|
auto offset = writePtr - base;
|
|
auto overhead = length - offset;
|
|
|
|
AuUInt len = std::min(overhead, requestLength);
|
|
|
|
if ((len != requestLength) && (flagExpandable))
|
|
{
|
|
if (!Resize(offset + requestLength))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
overhead = length - offset;
|
|
len = std::min(overhead, requestLength);
|
|
}
|
|
|
|
if (buffer)
|
|
{
|
|
std::memcpy(writePtr, buffer, len);
|
|
}
|
|
|
|
writePtr += len;
|
|
return len;
|
|
}
|
|
}
|
|
|
|
inline AuUInt RemainingBytes(bool endAtWrite = true)
|
|
{
|
|
if (flagCircular)
|
|
{
|
|
if ((readPtr < writePtr) && (endAtWrite))
|
|
{
|
|
return length - (writePtr - readPtr);
|
|
}
|
|
else
|
|
{
|
|
auto linearOverhead = length - (readPtr - base);
|
|
auto toWriteOverhead = writePtr - base;
|
|
return linearOverhead + toWriteOverhead;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (endAtWrite)
|
|
{
|
|
if (writePtr < readPtr)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return writePtr - readPtr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return (length - (readPtr - base));
|
|
}
|
|
}
|
|
}
|
|
|
|
inline AuUInt RemainingWrite(bool endAtRead = true)
|
|
{
|
|
if (flagCircular)
|
|
{
|
|
if ((writePtr < readPtr) && (endAtRead))
|
|
{
|
|
return length - (readPtr - writePtr);
|
|
}
|
|
else
|
|
{
|
|
auto linearOverhead = length - (writePtr - base);
|
|
auto toWriteOverhead = readPtr - base;
|
|
return linearOverhead + toWriteOverhead;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return length - (writePtr - base);
|
|
}
|
|
}
|
|
|
|
inline AuList<AuUInt8> RemainingBytesToVector(bool endAtWrite = true)
|
|
{
|
|
AuList<AuUInt8> vec;
|
|
|
|
if (flagCircular)
|
|
{
|
|
if ((readPtr < writePtr) && (endAtWrite))
|
|
{
|
|
auto len = length - (writePtr - readPtr);
|
|
|
|
vec.resize(len);
|
|
std::memcpy(vec.data(), readPtr, len);
|
|
}
|
|
else
|
|
{
|
|
auto linearOverhead = length - (readPtr - base);
|
|
auto toWriteOverhead = endAtWrite ? (writePtr - base) : (readPtr - base);
|
|
|
|
vec.resize(linearOverhead + toWriteOverhead);
|
|
std::memcpy(vec.data(), readPtr, linearOverhead);
|
|
std::memcpy(vec.data() + linearOverhead, base, linearOverhead);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AuUInt len;
|
|
if (endAtWrite)
|
|
{
|
|
len = writePtr - readPtr;
|
|
}
|
|
else
|
|
{
|
|
len = length - (readPtr - base);
|
|
}
|
|
|
|
vec.resize(len);
|
|
std::memcpy(vec.data(), readPtr, len);
|
|
}
|
|
|
|
return vec;
|
|
}
|
|
|
|
inline bool Skip(AuUInt count)
|
|
{
|
|
auto oldptr = readPtr;
|
|
auto skipped = Read(nullptr, count);
|
|
if (skipped != count)
|
|
{
|
|
readPtr = oldptr;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
inline AuUInt GetReadOffset()
|
|
{
|
|
if (flagCircular)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return readPtr - base;
|
|
}
|
|
}
|
|
|
|
inline AuUInt GetWriteOffset()
|
|
{
|
|
if (flagCircular)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return writePtr - base;
|
|
}
|
|
}
|
|
|
|
inline AuUInt Read(void *out, AuUInt requestedLength, bool peak = false)
|
|
{
|
|
AuUInt linearOverhead = 0, toWriteOverhead = 0, linearReadable = 0, toWriteReadable = 0;
|
|
if (flagCircular)
|
|
{
|
|
if (readPtr < writePtr)
|
|
{
|
|
linearOverhead = writePtr - readPtr;
|
|
toWriteOverhead = 0;
|
|
}
|
|
else
|
|
{
|
|
linearOverhead = length - (readPtr - base);
|
|
toWriteOverhead = writePtr - base;
|
|
}
|
|
|
|
auto readable = std::min(linearOverhead + toWriteOverhead, requestedLength);
|
|
|
|
linearReadable = std::min(linearOverhead, requestedLength);
|
|
toWriteReadable = readable - linearReadable;
|
|
|
|
if (out)
|
|
{
|
|
std::memcpy(out, readPtr, linearOverhead);
|
|
}
|
|
|
|
if (!peak)
|
|
{
|
|
readPtr += linearOverhead;
|
|
}
|
|
|
|
if (toWriteOverhead)
|
|
{
|
|
std::memcpy(reinterpret_cast<AuUInt8 *>(out) + linearOverhead, base, toWriteReadable);
|
|
|
|
if (!peak)
|
|
{
|
|
readPtr = base + toWriteReadable;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
if (readPtr == base + length)
|
|
{
|
|
readPtr = base;
|
|
}
|
|
#endif
|
|
|
|
return linearReadable + toWriteReadable;
|
|
}
|
|
else
|
|
{
|
|
AuUInt len = std::min(AuUInt(writePtr - readPtr), requestedLength);
|
|
|
|
if (out)
|
|
{
|
|
std::memcpy(out, readPtr, len);
|
|
}
|
|
|
|
if (!peak)
|
|
{
|
|
readPtr += len;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
}
|
|
};
|
|
} |