#include "ByteBuffer.h"
#include "System/Macroes/Asserts.h"
#include "FileSystem/PathFormatting.h"
#include "Logger/Log.h"
#include <algorithm> //For std::swap
#include <cstring> //For std::memcpy
#include <fstream>
ByteBuffer::ByteBuffer(size_t bytesToAllocate)
{
this->AllocateData(bytesToAllocate);
}
ByteBuffer::ByteBuffer(ByteBuffer &&other)
{
std::swap(this->data, other.data);
std::swap(this->bytes, other.bytes);
std::swap(this->isOwner, other.isOwner);
}
ByteBuffer &ByteBuffer::operator=(ByteBuffer &&other)
{
std::swap(this->data, other.data);
std::swap(this->bytes, other.bytes);
std::swap(this->isOwner, other.isOwner);
return *this;
}
ByteBuffer::~ByteBuffer()
{
this->Clear();
}
//========================================================
//Named constructors, for convience:
//========================================================
//Creates a ByteBuffer that owns 'data', and will delete it when destroyed.
ByteBuffer ByteBuffer::MakeOwnerOf(Byte *data, size_t bytes)
{
ByteBuffer buffer;
buffer.TakeData(data, bytes);
return buffer;
}
ByteBuffer ByteBuffer::MakeOwnerOf(ByteBuffer &other)
{
ByteBuffer buffer;
buffer.TakeData(other);
return buffer;
}
//Creates a ByteBuffer that copies 'data'. When destroyed, this ByteBuffer will delete the copied memory but not the original.
ByteBuffer ByteBuffer::MakeCopiedFrom(const Byte *data, size_t bytes)
{
ByteBuffer buffer;
buffer.CopyData(data, bytes);
return buffer;
}
ByteBuffer ByteBuffer::MakeCopiedFrom(const ByteBuffer &other, size_t pos, size_t bytes)
{
ByteBuffer buffer;
buffer.CopyData(other, pos, bytes);
return buffer;
}
//Creates a ByteBuffer that points to 'data', without copying it, and will not delete the memory when destroyed.
ByteBuffer ByteBuffer::MakePointedAt(Byte *data, size_t bytes)
{
ByteBuffer buffer;
buffer.PointToData(data, bytes);
return buffer;
}
ByteBuffer ByteBuffer::MakePointedAt(ByteBuffer &other, size_t pos, size_t bytes)
{
ByteBuffer buffer;
buffer.PointToData(other, pos, bytes);
return buffer;
}
//Creates a ByteBuffer that allocates a new block of memory of size 'bytes', and that will delete the memory when destroyed.
ByteBuffer ByteBuffer::MakeAllocated(size_t bytes)
{
ByteBuffer buffer;
buffer.AllocateData(bytes);
return buffer;
}
//========================================================
//Access functions:
//========================================================
//Returns a reference to the bytes starting at 'pos'.
//Doesn't check if 'pos' is inbounds or not.
Byte &ByteBuffer::operator[](size_t pos)
{
return this->data[pos];
}
const Byte &ByteBuffer::operator[](size_t pos) const
{
return this->data[pos];
}
//Returns a reference to the bytes starting at 'pos'.
//This is the same as buffer[pos], except it _does_ check if pos is in-bounds, by asserting.
Byte &ByteBuffer::At(size_t pos)
{
Assert(pos < this->bytes, "'pos' is out of range");
return this->data[pos];
}
const Byte &ByteBuffer::At(size_t pos) const
{
Assert(pos < this->bytes, "'pos' is out of range");
return this->data[pos];
}
//Returns a pointer to the first byte of the entire buffer. (The same as buffer[0])
Byte *ByteBuffer::GetData()
{
return this->data;
}
const Byte *ByteBuffer::GetData() const
{
return this->data;
}
//Creates a non-owning ByteBuffer that points to the memory starting at 'pos', and containing the specified length of bytes.
//This is similar to getting a substring of a string.
//This ByteBuffer still owns and data and is responsible for freeing it. The returned buffer merely points to the data, but doesn't own it.
ByteBuffer ByteBuffer::MakeDataPortion(size_t pos, size_t bytes)
{
return ByteBuffer::MakePointedAt(*this, pos, bytes);
}
//The same as 'MakeDataPortion()', but copies the data.
ByteBuffer ByteBuffer::DuplicateDataPortion(size_t pos, size_t bytes)
{
return ByteBuffer::MakeCopiedFrom(*this, pos, bytes);
}
//========================================================
//Querying functions:
//========================================================
//Returns the size of the buffer, in bytes.
size_t ByteBuffer::GetSize() const
{
return this->bytes;
}
//True if this ByteBuffer owns the memory it points to.
bool ByteBuffer::IsOwner() const
{
return this->isOwner;
}
//True if this ByteBuffer points to nullptr, or is of size 0.
bool ByteBuffer::IsEmpty() const
{
return (this->bytes == 0 || !this->data);
}
//========================================================
//Freeing functions: (deletes or releases the data)
//========================================================
//Releases ownership of the data, returning it.
//This ByteBuffer still points to the data, but will never delete it.
//If 'bytes' is not Null, it will be set to the size of this buffer.
Byte *ByteBuffer::ReleaseData(size_t *bytes)
{
//Release ownership.
this->isOwner = false;
//Set the number of bytes, if asked for.
if(bytes)
{
*bytes = this->bytes;
}
//Return the pointer.
return this->data;
}
//Releases ownership of the data, returning it.
//This ByteBuffer no longer points to the data, and will never delete it.
//If 'bytes' is not Null, it will be set to the size of this buffer.
//This is the same as calling ReleaseData() followed by Clear().
Byte *ByteBuffer::ReleaseAndReset(size_t *bytes)
{
//Release owernship of the data.
Byte *dataPtr = this->ReleaseData(bytes);
//Clear this buffer.
this->Clear();
//Return the pointer.
return dataPtr;
}
//Clears this ByteBuffer, setting the data pointer to nullptr and the size to 0.
//If this ByteBuffer owns the memory it points to, that memory will now get deleted.
void ByteBuffer::Clear()
{
this->resetTo(nullptr, 0, false);
}
//========================================================
//Re-assignment functions
//========================================================
//Takes ownership of 'data' without copying it.
//When this ByteBuffer is destroyed, it will delete 'data' also.
//Any old data this ByteBuffer already points to will be freed (if this buffer is the owner of that data).
void ByteBuffer::TakeData(Byte *data, size_t bytes)
{
Assert(bytes > 0, "Can't take zero bytes.");
this->resetTo(data, bytes, true);
}
//'other' is made to Release() the data.
void ByteBuffer::TakeData(ByteBuffer &other)
{
size_t bytes;
Byte *data = other.ReleaseData(&bytes);
this->resetTo(data, bytes, true);
}
//Copies 'data'. When this ByteBuffer is destroyed, it will delete the copied memory but not the original.
//Any old data this ByteBuffer already points to will be freed (if this buffer is the owner of that data).
void ByteBuffer::CopyData(const Byte *data, size_t bytes)
{
Assert(bytes > 0, "Can't copy zero bytes.");
//Allocate the new memory.
Byte *copiedData = new Byte[bytes];
//Copy the data over.
std::memcpy(copiedData, data, bytes);
//Assign the data.
this->resetTo(copiedData, bytes, true);
}
void ByteBuffer::CopyData(const ByteBuffer &other, size_t pos, size_t bytes)
{
if(bytes == ByteBuffer::NoPos)
{
bytes = (other.GetSize() - pos);
}
Assert((pos + bytes) <= other.GetSize(), "Trying to copy more data than exists in the buffer.");
this->CopyData(&other[pos], bytes);
}
//Points to 'data', without copying it, and when this ByteBuffer is destroyed, will not delete the memory.
//Any old data this ByteBuffer already points to will be freed (if this buffer is the owner of that data).
void ByteBuffer::PointToData(Byte *data, size_t bytes)
{
Assert(bytes > 0, "Can't point to zero bytes.");
this->resetTo(data, bytes, false);
}
void ByteBuffer::PointToData(ByteBuffer &other, size_t pos, size_t bytes)
{
if(bytes == ByteBuffer::NoPos)
{
bytes = (other.GetSize() - pos);
}
Assert((pos + bytes) <= other.GetSize(), "Trying to point to more data than exists in the buffer.");
this->PointToData(&other[pos], bytes);
}
//Allocates a new block of memory of size 'bytes'.
//Any old data this ByteBuffer already points to will be freed (if this buffer is the owner of that data).
void ByteBuffer::AllocateData(size_t bytes)
{
Assert(bytes > 0, "Can't allocate zero bytes.");
this->resetTo(new Byte[bytes], bytes, true);
}
//================================================================================
//Clears the existing data (freeing it if this ByteBuffer is the owner),
//and assigning 'data' as the new memory pointer and 'bytes' as the new size.
void ByteBuffer::resetTo(Byte *data, size_t bytes, bool isOwner)
{
//If we're the owner, delete the old memory (if any - it might be null).
if(this->isOwner)
delete[] this->data;
//Assign the new data, if any (it might be null).
this->data = data;
this->bytes = bytes;
this->isOwner = isOwner;
}
//================================================================================
//Resizes 'byteBuffer' by allocating a new buffer of size 'newSize', and copying the old bytes to it, before deleting the original buffer.
//If 'offset' is specified, offsets the position to place the the old memory in the new buffer. This allows you to grow the buffer's front as well as the back.
//e.g. to grow a buffer's front and back by 4 bytes each, you can do: ResizeByteBuffer(buffer, (buffer.GetSize() + 8), 4);
//This can shrink or grow the buffer.
void ResizeByteBuffer(ByteBuffer &byteBuffer, size_t newSize, size_t offset)
{
if(newSize == ByteBuffer_SuggestedResize)
{
size_t currentSize = byteBuffer.GetSize();
//Grow the ByteBuffer to the new size plus 35% or at least 128 bytes.
newSize = ((currentSize < 384) ? (currentSize + 128): size_t(float(currentSize) * 1.35f));
}
if(newSize == byteBuffer.GetSize())
return;
Assert(newSize > 0, "Can't allocate zero bytes.");
Assert(newSize > offset, "Offset can't be outside the buffer size.");
//Allocate the new memory.
Byte *newData = new Byte[newSize];
//Figure out how many bytes to copy.
size_t bytesToCopy = std::min(byteBuffer.GetSize(), (newSize-offset));
//Copy the data over.
std::memcpy(&newData[offset], byteBuffer.GetData(), bytesToCopy);
//Assign the data.
byteBuffer.TakeData(newData, newSize);
}
//Starting at 'pos', fills a number of bytes (bytes) in byteBuffer with 'fillValue'.
void FillByteBuffer(ByteBuffer &byteBuffer, Byte fillValue, size_t pos, size_t bytes)
{
Assert(pos < byteBuffer.GetSize(), "Can't fill outside the buffer.");
//Make sure we stay within the buffer.
if((bytes + pos) > byteBuffer.GetSize())
{
bytes = (byteBuffer.GetSize() - pos);
}
//Set the bytes.
std::memset(&byteBuffer[pos], fillValue, bytes);
}
//================================================================================
//Loads a file entirely into a bytebuffer. Returns an empty ByteBuffer if the load failed or if the file was empty.
ByteBuffer LoadFileAsByteBuffer(const std::string &filename)
{
//Open the file.
std::ifstream file(filename, std::fstream::binary);
//Make sure the file was opened.
if(!file)
{
Log::Message(MSG_SOURCE("FileFunctions", Log::Severity::Error))
<< "Failed to load '" << Log_HighlightCyan(GetFilenameFromPath(filename)) << "' at " << Log_DisplayPath(filename)
<< "\nDo I have sufficient privileges? Does the file even exist?" << Log::FlushStream;
return ByteBuffer();
}
//Seek to the end of the file, to figure out its length.
file.seekg(0, std::ios::end);
size_t bytes = file.tellg();
//Return if the file was empty.
if(bytes == 0)
{
return ByteBuffer();
}
//Allocate the byte buffer of the apropriate size.
ByteBuffer byteBuffer = ByteBuffer::MakeAllocated(bytes);
//Jump back to the beginning, and read the file into the buffer.
file.seekg(0, std::ios::beg);
file.read(reinterpret_cast<char*>(byteBuffer.GetData()), bytes);
//Return the buffer and (automaticly) close the file.
return byteBuffer;
}
bool SaveByteBufferToFile(const ByteBuffer &byteBuffer, const std::string &filename, size_t bytes)
{
//Open the file.
std::ofstream file(filename, std::fstream::binary);
//Make sure the file was opened.
if(!file)
{
Log::Message(MSG_SOURCE("FileFunctions", Log::Severity::Error))
<< "Failed to open '" << Log_HighlightCyan(GetFilenameFromPath(filename)) << "' for writing, at " << Log_DisplayPath(filename)
<< "\nDo I have sufficient privileges to write to this directory?" << Log::FlushStream;
return false;
}
size_t amountToWrite = std::min(bytes, byteBuffer.GetSize());
//Write the data.
file.write(reinterpret_cast<const char*>(byteBuffer.GetData()), amountToWrite);
//Return and (automaticly) close the file.
return true;
}
ByteBuffer StringToByteBuffer(const std::string &str)
{
return ByteBuffer::MakeCopiedFrom(reinterpret_cast<const Byte*>(str.data()), str.size());
}
std::string ByteBufferToString(const ByteBuffer &byteBuffer)
{
return std::string(reinterpret_cast<const char*>(byteBuffer.GetData()),
byteBuffer.GetSize());
}
//================================================================================