aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-05-23 10:13:47 +0200
committerGitHub <[email protected]>2023-05-23 10:13:47 +0200
commit633dee5f8688c104a04f0ec719b756dbbad7142f (patch)
treef0ce192964f6d6b9fd506f4963e261320803e2ac /src
parentMemoryView::RightChop -> const (diff)
downloadzen-633dee5f8688c104a04f0ec719b756dbbad7142f.tar.xz
zen-633dee5f8688c104a04f0ec719b756dbbad7142f.zip
streaming decompression support (#142)
Added CompressedBufferReader support from UE. This provides some streaming decompression support which can be employed to reduce memory and other resource usage.
Diffstat (limited to 'src')
-rw-r--r--src/zencore/compositebuffer.cpp13
-rw-r--r--src/zencore/compress.cpp806
-rw-r--r--src/zencore/include/zencore/compositebuffer.h14
-rw-r--r--src/zencore/include/zencore/compress.h118
-rw-r--r--src/zencore/include/zencore/intmath.h26
-rw-r--r--src/zencore/include/zencore/stream.h27
-rw-r--r--src/zencore/include/zencore/zencore.h4
-rw-r--r--src/zencore/stream.cpp21
8 files changed, 1000 insertions, 29 deletions
diff --git a/src/zencore/compositebuffer.cpp b/src/zencore/compositebuffer.cpp
index 735020451..c49bf775c 100644
--- a/src/zencore/compositebuffer.cpp
+++ b/src/zencore/compositebuffer.cpp
@@ -58,7 +58,7 @@ CompositeBuffer::MakeOwned() &&
}
SharedBuffer
-CompositeBuffer::Flatten() const&
+CompositeBuffer::ToShared() const&
{
switch (m_Segments.size())
{
@@ -81,7 +81,7 @@ CompositeBuffer::Flatten() const&
}
SharedBuffer
-CompositeBuffer::Flatten() &&
+CompositeBuffer::ToShared() &&
{
return m_Segments.size() == 1 ? std::move(m_Segments[0]) : std::as_const(*this).Flatten();
}
@@ -100,10 +100,13 @@ CompositeBuffer::Mid(uint64_t Offset, uint64_t Size) const
}
MemoryView
-CompositeBuffer::ViewOrCopyRange(uint64_t Offset, uint64_t Size, UniqueBuffer& CopyBuffer) const
+CompositeBuffer::ViewOrCopyRange(uint64_t Offset,
+ uint64_t Size,
+ UniqueBuffer& CopyBuffer,
+ std::function<UniqueBuffer(uint64_t Size)> Allocator) const
{
MemoryView View;
- IterateRange(Offset, Size, [Size, &View, &CopyBuffer, WriteView = MutableMemoryView()](MemoryView Segment) mutable {
+ IterateRange(Offset, Size, [Size, &View, &CopyBuffer, &Allocator, WriteView = MutableMemoryView()](MemoryView Segment) mutable {
if (Size == Segment.GetSize())
{
View = Segment;
@@ -114,7 +117,7 @@ CompositeBuffer::ViewOrCopyRange(uint64_t Offset, uint64_t Size, UniqueBuffer& C
{
if (CopyBuffer.GetSize() < Size)
{
- CopyBuffer = UniqueBuffer::Alloc(Size);
+ CopyBuffer = Allocator(Size);
}
View = WriteView = CopyBuffer.GetMutableView().Left(Size);
}
diff --git a/src/zencore/compress.cpp b/src/zencore/compress.cpp
index 44d6efc36..2362d8e78 100644
--- a/src/zencore/compress.cpp
+++ b/src/zencore/compress.cpp
@@ -6,7 +6,9 @@
#include <zencore/compositebuffer.h>
#include <zencore/crc32.h>
#include <zencore/endian.h>
+#include <zencore/intmath.h>
#include <zencore/iohash.h>
+#include <zencore/stream.h>
#include <zencore/testing.h>
#include <oodle2.h>
@@ -19,7 +21,8 @@ namespace zen::detail {
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-static constexpr uint64_t DefaultBlockSize = 256 * 1024;
+static constexpr uint64_t DefaultBlockSize = 256 * 1024;
+static constexpr uint64_t DefaultHeaderSize = 4 * 1024;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -115,27 +118,72 @@ struct BufferHeader
}
return Crc32;
}
+
+ bool TryGetCompressParameters(OodleCompressor& OutCompressor, OodleCompressionLevel& OutCompressionLevel, uint64_t& OutBlockSize) const
+ {
+ switch (Method)
+ {
+ case CompressionMethod::None:
+ OutCompressor = OodleCompressor::NotSet;
+ OutCompressionLevel = OodleCompressionLevel::None;
+ OutBlockSize = 0;
+ return true;
+ case CompressionMethod::Oodle:
+ OutCompressor = OodleCompressor(Compressor);
+ OutCompressionLevel = OodleCompressionLevel(CompressionLevel);
+ OutBlockSize = uint64_t(1) << BlockSizeExponent;
+ return true;
+ default:
+ return false;
+ }
+ }
};
+using FHeader = BufferHeader;
+
static_assert(sizeof(BufferHeader) == 64, "BufferHeader is the wrong size.");
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+class DecoderSource
+{
+public:
+ virtual bool Read(uint64_t Offset, MutableMemoryView Data) const = 0;
+ virtual MemoryView ReadOrView(uint64_t Offset, uint64_t Size, DecoderContext& Context) const = 0;
+ virtual CompositeBuffer ReadToComposite(uint64_t Offset, uint64_t Size) const = 0;
+};
+
class BaseEncoder
{
public:
- virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0;
+ [[nodiscard]] virtual CompositeBuffer Compress(const CompositeBuffer& RawData, uint64_t BlockSize = DefaultBlockSize) const = 0;
};
class BaseDecoder
{
public:
- virtual CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const = 0;
- virtual bool TryDecompressTo(const BufferHeader& Header,
- const CompositeBuffer& CompressedData,
- MutableMemoryView RawView,
- uint64_t RawOffset) const = 0;
- virtual uint64_t GetHeaderSize(const BufferHeader& Header) const = 0;
+ [[nodiscard]] virtual uint64_t GetHeaderSize(const BufferHeader& Header) const = 0;
+ [[nodiscard]] virtual bool TryDecompressTo(const BufferHeader& Header,
+ const CompositeBuffer& CompressedData,
+ MutableMemoryView RawView,
+ uint64_t RawOffset) const = 0;
+
+ [[nodiscard]] virtual bool TryDecompressTo(DecoderContext& Context,
+ const DecoderSource& Source,
+ const BufferHeader& Header,
+ MemoryView HeaderView,
+ uint64_t RawOffset,
+ MutableMemoryView RawView) const = 0;
+
+ [[nodiscard]] virtual CompositeBuffer DecompressToComposite(const BufferHeader& Header,
+ const CompositeBuffer& CompressedData) const = 0;
+
+ virtual CompositeBuffer DecompressToComposite(DecoderContext& Context,
+ const DecoderSource& Source,
+ const FHeader& Header,
+ const MemoryView HeaderView,
+ uint64_t RawOffset,
+ uint64_t RawSize) const = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -161,7 +209,7 @@ public:
class NoneDecoder final : public BaseDecoder
{
public:
- [[nodiscard]] CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final
+ [[nodiscard]] CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final
{
if (Header.Method == CompressionMethod::None && Header.TotalCompressedSize == CompressedData.GetSize() &&
Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader))
@@ -171,6 +219,23 @@ public:
return CompositeBuffer();
}
+ CompositeBuffer DecompressToComposite(DecoderContext& Context,
+ const DecoderSource& Source,
+ const FHeader& Header,
+ const MemoryView HeaderView,
+ const uint64_t RawOffset,
+ const uint64_t RawSize) const final
+ {
+ ZEN_UNUSED(HeaderView, Context);
+
+ if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize && RawSize <= Header.TotalRawSize - RawOffset &&
+ Header.TotalCompressedSize == Header.TotalRawSize + sizeof(FHeader))
+ {
+ return Source.ReadToComposite(sizeof(FHeader) + RawOffset, RawSize);
+ }
+ return CompositeBuffer();
+ }
+
[[nodiscard]] bool TryDecompressTo(const BufferHeader& Header,
const CompositeBuffer& CompressedData,
MutableMemoryView RawView,
@@ -186,6 +251,24 @@ public:
return false;
}
+ [[nodiscard]] bool TryDecompressTo(DecoderContext& Context,
+ const DecoderSource& Source,
+ const BufferHeader& Header,
+ MemoryView HeaderView,
+ uint64_t RawOffset,
+ MutableMemoryView RawView) const final
+ {
+ ZEN_UNUSED(HeaderView, Context);
+
+ if (Header.Method == CompressionMethod::None && RawOffset <= Header.TotalRawSize &&
+ RawView.GetSize() <= Header.TotalRawSize - RawOffset &&
+ Header.TotalCompressedSize == Header.TotalRawSize + sizeof(BufferHeader))
+ {
+ return Source.Read(sizeof(BufferHeader) + RawOffset, RawView);
+ }
+ return false;
+ }
+
[[nodiscard]] uint64_t GetHeaderSize(const BufferHeader&) const final { return sizeof(BufferHeader); }
};
@@ -305,22 +388,38 @@ BlockEncoder::Compress(const CompositeBuffer& RawData, const uint64_t BlockSize)
class BlockDecoder : public BaseDecoder
{
public:
- CompositeBuffer Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final;
- [[nodiscard]] bool TryDecompressTo(const BufferHeader& Header,
- const CompositeBuffer& CompressedData,
- MutableMemoryView RawView,
- uint64_t RawOffset) const final;
[[nodiscard]] uint64_t GetHeaderSize(const BufferHeader& Header) const final
{
return sizeof(BufferHeader) + sizeof(uint32_t) * uint64_t(Header.BlockCount);
}
+ [[nodiscard]] virtual bool TryDecompressTo(DecoderContext& Context,
+ const DecoderSource& Source,
+ const BufferHeader& Header,
+ MemoryView HeaderView,
+ uint64_t RawOffset,
+ MutableMemoryView RawView) const final;
+
+ CompositeBuffer DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const final;
+
+ CompositeBuffer DecompressToComposite(DecoderContext& Context,
+ const DecoderSource& Source,
+ const FHeader& Header,
+ const MemoryView HeaderView,
+ const uint64_t RawOffset,
+ const uint64_t RawSize) const final;
+
+ [[nodiscard]] bool TryDecompressTo(const BufferHeader& Header,
+ const CompositeBuffer& CompressedData,
+ MutableMemoryView RawView,
+ uint64_t RawOffset) const final;
+
protected:
virtual bool DecompressBlock(MutableMemoryView RawData, MemoryView CompressedData) const = 0;
};
CompositeBuffer
-BlockDecoder::Decompress(const BufferHeader& Header, const CompositeBuffer& CompressedData) const
+BlockDecoder::DecompressToComposite(const BufferHeader& Header, const CompositeBuffer& CompressedData) const
{
if (Header.BlockCount == 0 || Header.TotalCompressedSize != CompressedData.GetSize())
{
@@ -519,6 +618,109 @@ BlockDecoder::TryDecompressTo(const BufferHeader& Header,
return RemainingRawSize == 0;
}
+bool
+BlockDecoder::TryDecompressTo(DecoderContext& Context,
+ const DecoderSource& Source,
+ const FHeader& Header,
+ const MemoryView HeaderView,
+ const uint64_t RawOffset,
+ MutableMemoryView RawView) const
+{
+ if (Header.TotalRawSize < RawOffset + RawView.GetSize())
+ {
+ return false;
+ }
+
+ const uint64_t BlockSize = uint64_t(1) << Header.BlockSizeExponent;
+ const uint64_t LastBlockSize = BlockSize - (BlockSize * Header.BlockCount - Header.TotalRawSize);
+ const uint32_t FirstBlockIndex = uint32_t(RawOffset / BlockSize);
+ const uint32_t LastBlockIndex = uint32_t((RawOffset + RawView.GetSize() - 1) / BlockSize);
+
+ uint64_t RawBlockOffset = RawOffset % BlockSize;
+
+ const uint32_t* const CompressedBlockSizes = static_cast<const uint32_t*>(HeaderView.RightChop(sizeof(FHeader)).GetData());
+ uint64_t CompressedOffset = sizeof(FHeader) + sizeof(uint32_t) * uint32_t(Header.BlockCount);
+
+ // + Algo::TransformAccumulate(MakeArrayView(CompressedBlockSizes, FirstBlockIndex),
+ // [](uint32_t Size) -> uint64_t { return NETWORK_ORDER32(Size); },
+ // uint64_t(0)
+
+ for (uint32_t i = 0; i < FirstBlockIndex; ++i)
+ {
+ CompressedOffset += ByteSwap(CompressedBlockSizes[i]);
+ }
+
+ for (uint32_t BlockIndex = FirstBlockIndex; BlockIndex <= LastBlockIndex; BlockIndex++)
+ {
+ const uint64_t RawBlockSize = BlockIndex == Header.BlockCount - 1 ? LastBlockSize : BlockSize;
+ const uint64_t RawBlockReadSize = zen::Min(RawView.GetSize(), RawBlockSize - RawBlockOffset);
+ const uint32_t CompressedBlockSize = ByteSwap(CompressedBlockSizes[BlockIndex]);
+ const bool bIsCompressed = CompressedBlockSize < RawBlockSize;
+
+ if (bIsCompressed)
+ {
+ if (Context.RawBlockIndex == BlockIndex)
+ {
+ RawView.Left(RawBlockReadSize).CopyFrom(Context.RawBlock.GetView().Mid(RawBlockOffset, RawBlockReadSize));
+ }
+ else
+ {
+ MutableMemoryView RawBlock;
+ if (RawBlockReadSize == RawBlockSize)
+ {
+ RawBlock = RawView.Left(RawBlockSize);
+ }
+ else
+ {
+ if (Context.RawBlock.GetSize() < RawBlockSize)
+ {
+ Context.RawBlock = UniqueBuffer::Alloc(BlockSize);
+ }
+ RawBlock = Context.RawBlock.GetMutableView().Left(RawBlockSize);
+ Context.RawBlockIndex = BlockIndex;
+ }
+
+ const MemoryView CompressedBlock = Source.ReadOrView(CompressedOffset, CompressedBlockSize, Context);
+ if (CompressedBlock.IsEmpty() || !DecompressBlock(RawBlock, CompressedBlock))
+ {
+ return false;
+ }
+
+ if (RawBlockReadSize != RawBlockSize)
+ {
+ RawView.CopyFrom(RawBlock.Mid(RawBlockOffset, RawBlockReadSize));
+ }
+ }
+ }
+ else
+ {
+ Source.Read(CompressedOffset + RawBlockOffset, RawView.Left(RawBlockReadSize));
+ }
+
+ RawBlockOffset = 0;
+ CompressedOffset += CompressedBlockSize;
+ RawView += RawBlockReadSize;
+ }
+
+ return RawView.GetSize() == 0;
+}
+
+CompositeBuffer
+BlockDecoder::DecompressToComposite(DecoderContext& Context,
+ const DecoderSource& Source,
+ const FHeader& Header,
+ const MemoryView HeaderView,
+ const uint64_t RawOffset,
+ const uint64_t RawSize) const
+{
+ UniqueBuffer Buffer = UniqueBuffer::Alloc(RawSize);
+ if (TryDecompressTo(Context, Source, Header, HeaderView, RawOffset, Buffer))
+ {
+ return CompositeBuffer(Buffer.MoveToShared());
+ }
+ return CompositeBuffer();
+}
+
//////////////////////////////////////////////////////////////////////////
struct OodleInit
@@ -671,14 +873,15 @@ GetDecoder(CompressionMethod Method)
bool
BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash, uint64_t& OutRawSize)
{
- uint64_t Size = CompressedData.GetSize();
- if (Size < sizeof(BufferHeader))
+ const uint64_t CompressedDataSize = CompressedData.GetSize();
+ if (CompressedDataSize < sizeof(BufferHeader))
{
return false;
}
+
const size_t StackBufferSize = 256;
uint8_t StackBuffer[StackBufferSize];
- uint64_t ReadSize = Min(Size, StackBufferSize);
+ uint64_t ReadSize = Min(CompressedDataSize, StackBufferSize);
BufferHeader* Header = reinterpret_cast<BufferHeader*>(StackBuffer);
{
CompositeBuffer::Iterator It;
@@ -689,20 +892,27 @@ BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash,
{
return false;
}
+
const BaseDecoder* const Decoder = GetDecoder(Header->Method);
if (!Decoder)
{
return false;
}
+
uint32_t Crc32 = Header->Crc32;
OutRawHash = IoHash::FromBLAKE3(Header->RawHash);
OutRawSize = Header->TotalRawSize;
uint64_t HeaderSize = Decoder->GetHeaderSize(*Header);
+
+ if (Header->TotalCompressedSize > CompressedDataSize)
+ {
+ return false;
+ }
+
Header->ByteSwap();
if (HeaderSize > ReadSize)
{
- // 0.004% of cases on a Fortnite hot cache cook
UniqueBuffer HeaderCopy = UniqueBuffer::Alloc(HeaderSize);
CompositeBuffer::Iterator It;
CompressedData.CopyTo(HeaderCopy.GetMutableView(), It);
@@ -720,9 +930,170 @@ BufferHeader::IsValid(const CompositeBuffer& CompressedData, IoHash& OutRawHash,
return false;
}
}
+
return true;
}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool
+TryReadHeader(DecoderContext& Context, Archive& Ar, FHeader& OutHeader, MemoryView& OutHeaderView)
+{
+ if (Context.HeaderOffset != MAX_uint64)
+ {
+ OutHeaderView = Context.Header.GetView().Left(Context.HeaderSize);
+ MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader)));
+ OutHeader.ByteSwap();
+ return true;
+ }
+
+ const int64_t Offset = Ar.Tell();
+
+ FHeader& Header = OutHeader;
+ Ar.Serialize(&Header, sizeof(FHeader));
+ Header.ByteSwap();
+
+ if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic)
+ {
+ const uint64_t HeaderSize = Decoder->GetHeaderSize(Header);
+ if (Context.Header.GetSize() < HeaderSize)
+ {
+ Context.Header = UniqueBuffer::Alloc(zen::Max(NextPow2(HeaderSize), DefaultHeaderSize));
+ }
+
+ const MutableMemoryView HeaderView = Context.Header.GetMutableView().Left(HeaderSize);
+ const MutableMemoryView HeaderTail = HeaderView.CopyFrom(MakeMemoryView(&Header, &Header + 1));
+ Ar.Serialize(HeaderTail.GetData(), int64_t(HeaderTail.GetSize()));
+
+ FHeader* const HeaderCopy = static_cast<FHeader*>(HeaderView.GetData());
+ HeaderCopy->ByteSwap();
+ if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView))
+ {
+ Context.HeaderOffset = uint64_t(Offset);
+ Context.HeaderSize = HeaderSize;
+ Context.HeaderCrc32 = Header.Crc32;
+ Context.RawBlockIndex = MAX_uint32;
+ OutHeaderView = HeaderView;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+TryReadHeader(DecoderContext& Context, const CompositeBuffer& Buffer, FHeader& OutHeader, MemoryView& OutHeaderView)
+{
+ if (Context.HeaderOffset != MAX_uint64)
+ {
+ OutHeaderView = Buffer.ViewOrCopyRange(Context.HeaderOffset, Context.HeaderSize, Context.Header);
+ MakeMutableMemoryView(&OutHeader, &OutHeader + 1).CopyFrom(OutHeaderView.Left(sizeof(FHeader)));
+ OutHeader.ByteSwap();
+ return true;
+ }
+
+ if (Buffer.GetSize() < sizeof(FHeader))
+ {
+ return false;
+ }
+
+ FHeader& Header = OutHeader;
+ Buffer.CopyTo(MakeMutableMemoryView(&Header, &Header + 1));
+ Header.ByteSwap();
+
+ if (const BaseDecoder* const Decoder = GetDecoder(Header.Method); Decoder && Header.Magic == FHeader::ExpectedMagic)
+ {
+ const uint64_t HeaderSize = Decoder->GetHeaderSize(Header);
+ const MemoryView HeaderView = Buffer.ViewOrCopyRange(0, HeaderSize, Context.Header, [](uint64_t Size) {
+ return UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultHeaderSize));
+ });
+ if (Header.Crc32 == FHeader::CalculateCrc32(HeaderView))
+ {
+ Context.HeaderOffset = 0;
+ Context.HeaderSize = HeaderSize;
+ if (Context.HeaderCrc32 != Header.Crc32)
+ {
+ Context.HeaderCrc32 = Header.Crc32;
+ Context.RawBlockIndex = MAX_uint32;
+ }
+ OutHeaderView = HeaderView;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class ArchiveDecoderSource final : public DecoderSource
+{
+public:
+ explicit ArchiveDecoderSource(Archive& InArchive, const uint64_t InBaseOffset) : m_Archive(InArchive), m_BaseOffset(InBaseOffset) {}
+
+ bool Read(uint64_t Offset, MutableMemoryView Data) const final
+ {
+ m_Archive.Seek(int64_t(m_BaseOffset + Offset));
+ m_Archive.Serialize(Data.GetData(), int64_t(Data.GetSize()));
+ return !m_Archive.IsError();
+ }
+
+ MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final
+ {
+ if (Context.CompressedBlock.GetSize() < Size)
+ {
+ Context.CompressedBlock = UniqueBuffer::Alloc(zen::Max(NextPow2(Size), DefaultBlockSize));
+ }
+ const MutableMemoryView View = Context.CompressedBlock.GetMutableView().Left(Size);
+ return Read(Offset, View) ? View : MemoryView();
+ }
+
+ CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final
+ {
+ UniqueBuffer Buffer = UniqueBuffer::Alloc(Size);
+ if (Read(Offset, Buffer))
+ {
+ return CompositeBuffer(Buffer.MoveToShared());
+ }
+ return CompositeBuffer();
+ }
+
+private:
+ Archive& m_Archive;
+ const uint64_t m_BaseOffset;
+};
+
+class BufferDecoderSource final : public DecoderSource
+{
+public:
+ explicit BufferDecoderSource(const CompositeBuffer& InBuffer) : m_Buffer(InBuffer) {}
+
+ bool Read(const uint64_t Offset, const MutableMemoryView Data) const final
+ {
+ if (Offset + Data.GetSize() <= m_Buffer.GetSize())
+ {
+ m_Buffer.CopyTo(Data, Offset);
+ return true;
+ }
+ return false;
+ }
+
+ MemoryView ReadOrView(const uint64_t Offset, const uint64_t Size, DecoderContext& Context) const final
+ {
+ return m_Buffer.ViewOrCopyRange(Offset, Size, Context.CompressedBlock, [](uint64_t BufferSize) -> UniqueBuffer {
+ return UniqueBuffer::Alloc(zen::Max(zen::NextPow2(BufferSize), DefaultBlockSize));
+ });
+ }
+
+ CompositeBuffer ReadToComposite(const uint64_t Offset, const uint64_t Size) const final
+ {
+ return m_Buffer.Mid(Offset, Size).MakeOwned();
+ }
+
+private:
+ const CompositeBuffer& m_Buffer;
+};
+
//////////////////////////////////////////////////////////////////////////
template<typename BufferType>
@@ -1007,7 +1378,7 @@ CompressedBuffer::DecompressToComposite() const
{
if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
{
- return Decoder->Decompress(Header, CompressedData);
+ return Decoder->DecompressToComposite(Header, CompressedData);
}
}
}
@@ -1041,6 +1412,240 @@ CompressedBuffer::TryGetCompressParameters(OodleCompressor& OutCompressor,
return false;
}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CompressedBufferReader::CompressedBufferReader(Archive& Archive)
+{
+ SetSource(Archive);
+}
+
+CompressedBufferReader::CompressedBufferReader(const CompressedBuffer& Buffer)
+{
+ SetSource(Buffer);
+}
+
+void
+CompressedBufferReader::ResetBuffers()
+{
+ using namespace detail;
+ if (SourceArchive && Context.HeaderOffset != MAX_uint64)
+ {
+ SourceArchive->Seek(int64_t(Context.HeaderOffset));
+ }
+ Context = DecoderContext();
+}
+
+void
+CompressedBufferReader::ResetSource()
+{
+ Context.HeaderOffset = MAX_uint64;
+ SourceArchive = nullptr;
+ SourceBuffer = nullptr;
+}
+
+void
+CompressedBufferReader::SetSource(Archive& Archive)
+{
+ if (SourceArchive == &Archive)
+ {
+ return;
+ }
+ Context.HeaderOffset = MAX_uint64;
+ SourceArchive = &Archive;
+ SourceBuffer = nullptr;
+}
+
+void
+CompressedBufferReader::SetSource(const CompressedBuffer& Buffer)
+{
+ if (SourceBuffer == &Buffer)
+ {
+ return;
+ }
+ Context.HeaderOffset = MAX_uint64;
+ SourceArchive = nullptr;
+ SourceBuffer = &Buffer;
+}
+
+uint64_t
+CompressedBufferReader::GetCompressedSize()
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ return Header.TotalCompressedSize;
+ }
+ return 0;
+}
+
+uint64_t
+CompressedBufferReader::GetRawSize()
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ return Header.TotalRawSize;
+ }
+ return 0;
+}
+
+IoHash
+CompressedBufferReader::GetRawHash()
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ return IoHash::FromBLAKE3(Header.RawHash);
+ }
+ return {};
+}
+
+bool
+CompressedBufferReader::TryGetCompressParameters(OodleCompressor& OutCompressor,
+ OodleCompressionLevel& OutCompressionLevel,
+ uint64_t& OutBlockSize)
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ return Header.TryGetCompressParameters(OutCompressor, OutCompressionLevel, OutBlockSize);
+ }
+ return false;
+}
+
+bool
+CompressedBufferReader::TryDecompressTo(const MutableMemoryView RawView, const uint64_t RawOffset)
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ const uint64_t TotalRawSize = Header.TotalRawSize;
+ if (RawOffset <= TotalRawSize && RawView.GetSize() <= TotalRawSize - RawOffset)
+ {
+ if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
+ {
+ if (Decoder->TryDecompressTo(
+ Context,
+ SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
+ : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
+ Header,
+ HeaderView,
+ RawOffset,
+ RawView))
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+SharedBuffer
+CompressedBufferReader::Decompress(const uint64_t RawOffset, const uint64_t RawSize)
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ const uint64_t TotalRawSize = Header.TotalRawSize;
+ const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize;
+ if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset)
+ {
+ if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
+ {
+ UniqueBuffer RawData = UniqueBuffer::Alloc(RawSizeToCopy);
+ if (Decoder->TryDecompressTo(
+ Context,
+ SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
+ : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
+ Header,
+ HeaderView,
+ RawOffset,
+ RawData))
+ {
+ return RawData.MoveToShared();
+ }
+ }
+ }
+ }
+ return {};
+}
+
+CompositeBuffer
+CompressedBufferReader::DecompressToComposite(const uint64_t RawOffset, const uint64_t RawSize)
+{
+ using namespace detail;
+ BufferHeader Header;
+ MemoryView HeaderView;
+ if (TryReadHeader(Header, HeaderView))
+ {
+ const uint64_t TotalRawSize = Header.TotalRawSize;
+ const uint64_t RawSizeToCopy = RawSize == MAX_uint64 ? TotalRawSize - RawOffset : RawSize;
+ if (RawOffset <= TotalRawSize && RawSizeToCopy <= TotalRawSize - RawOffset)
+ {
+ if (const BaseDecoder* const Decoder = GetDecoder(Header.Method))
+ {
+ return Decoder->DecompressToComposite(
+ Context,
+ SourceArchive ? static_cast<const DecoderSource&>(ArchiveDecoderSource(*SourceArchive, Context.HeaderOffset))
+ : static_cast<const DecoderSource&>(BufferDecoderSource(SourceBuffer->GetCompressed())),
+ Header,
+ HeaderView,
+ RawOffset,
+ RawSizeToCopy);
+ }
+ }
+ }
+ return CompositeBuffer();
+}
+
+bool
+CompressedBufferReader::TryReadHeader(detail::BufferHeader& OutHeader, MemoryView& OutHeaderView)
+{
+ using namespace detail;
+ if (Archive* const Archive = SourceArchive)
+ {
+ return detail::TryReadHeader(Context, *Archive, OutHeader, OutHeaderView);
+ }
+ if (const CompressedBuffer* const Buffer = SourceBuffer)
+ {
+ return detail::TryReadHeader(Context, Buffer->GetCompressed(), OutHeader, OutHeaderView);
+ }
+ return false;
+}
+
+/** A type that sets the source of a reader upon construction and resets it upon destruction. */
+class CompressedBufferReaderSourceScope
+{
+public:
+ inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, Archive& InArchive) : Reader(InReader)
+ {
+ Reader.SetSource(InArchive);
+ }
+
+ inline CompressedBufferReaderSourceScope(CompressedBufferReader& InReader, const CompressedBuffer& InBuffer) : Reader(InReader)
+ {
+ Reader.SetSource(InBuffer);
+ }
+
+ inline ~CompressedBufferReaderSourceScope() { Reader.ResetSource(); }
+
+private:
+ CompressedBufferReader& Reader;
+};
+
/**
______________________ _____________________________
\__ ___/\_ _____// _____/\__ ___/ _____/
@@ -1341,6 +1946,167 @@ TEST_CASE("CompressedBuffer")
}
}
+TEST_CASE("CompressedBufferReader")
+{
+ const auto GenerateData = [](size_t N) -> std::vector<uint64_t> {
+ std::vector<uint64_t> Data;
+ Data.resize(N);
+ for (size_t Idx = 0; Idx < Data.size(); ++Idx)
+ {
+ Data[Idx] = uint64_t(Idx);
+ }
+ return Data;
+ };
+
+ CompressedBufferReader Reader;
+
+ SUBCASE("Decompress with offset and size")
+ {
+ const auto UncompressAndValidate = [&](const CompressedBuffer& Compressed,
+ const int32_t OffsetCount,
+ const int32_t Count,
+ const std::vector<uint64_t>& ExpectedValues) {
+ Reader.SetSource(Compressed);
+ {
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+ {
+ UniqueBuffer Uncompressed = UniqueBuffer::Alloc(Count * sizeof(uint64_t));
+ if (Reader.TryDecompressTo(Uncompressed, OffsetCount * sizeof(uint64_t)))
+ {
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+ }
+ };
+
+ constexpr uint64_t BlockSize = 64 * sizeof(uint64_t);
+ constexpr int32_t N = 5000;
+ const std::vector<uint64_t> ExpectedValues = GenerateData(N);
+
+ const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
+ OodleCompressor::Mermaid,
+ OodleCompressionLevel::Fast,
+ BlockSize);
+
+ UncompressAndValidate(Compressed, 0, N, ExpectedValues);
+ UncompressAndValidate(Compressed, 1, N - 1, ExpectedValues);
+ UncompressAndValidate(Compressed, N - 1, 1, ExpectedValues);
+ UncompressAndValidate(Compressed, 0, 1, ExpectedValues);
+ UncompressAndValidate(Compressed, 2, 4, ExpectedValues);
+ UncompressAndValidate(Compressed, 0, 512, ExpectedValues);
+ UncompressAndValidate(Compressed, 3, 514, ExpectedValues);
+ UncompressAndValidate(Compressed, 256, 512, ExpectedValues);
+ UncompressAndValidate(Compressed, 512, 512, ExpectedValues);
+ UncompressAndValidate(Compressed, 512, 512, ExpectedValues);
+ UncompressAndValidate(Compressed, 4993, 4, ExpectedValues);
+ }
+
+ SUBCASE("Decompress with offset only")
+ {
+ constexpr uint64_t BlockSize = 64 * sizeof(uint64_t);
+ constexpr int32_t N = 1000;
+ const std::vector<uint64_t> ExpectedValues = GenerateData(N);
+
+ const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
+ OodleCompressor::Mermaid,
+ OodleCompressionLevel::Fast,
+ BlockSize);
+
+ constexpr uint64_t OffsetCount = 150;
+ {
+ SharedBuffer Buffer = Compressed.GetCompressed().ToShared();
+ BufferReader Ar(Buffer.GetData(), int64_t(Buffer.GetSize()));
+ CompressedBufferReaderSourceScope Source(Reader, Ar);
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount))));
+ }
+ {
+ CompressedBufferReaderSourceScope Source(Reader, Compressed);
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount))));
+ }
+
+ // Short (malformed) Buffer
+ {
+ IoHash RawHash;
+ uint64_t RawSize = 0;
+ const CompressedBuffer CompressedShort =
+ CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128),
+ /* out */ RawHash,
+ /* out */ RawSize);
+ Reader.SetSource(CompressedShort);
+ const SharedBuffer Uncompressed = Reader.Decompress();
+ CHECK(Uncompressed.IsNull());
+ }
+ }
+
+ SUBCASE("Decompress only block")
+ {
+ constexpr uint64_t BlockSize = 256 * sizeof(uint64_t);
+ constexpr int32_t N = 100;
+ const std::vector<uint64_t> ExpectedValues = GenerateData(N);
+
+ const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
+ OodleCompressor::Mermaid,
+ OodleCompressionLevel::Fast,
+ BlockSize);
+
+ constexpr uint64_t OffsetCount = 2;
+ constexpr uint64_t Count = 50;
+ {
+ SharedBuffer Buffer = Compressed.GetCompressed().ToShared();
+ BufferReader Ar(Buffer.GetData(), int64_t(Buffer.GetSize()));
+ CompressedBufferReaderSourceScope Source(Reader, Ar);
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+ {
+ CompressedBufferReaderSourceScope Source(Reader, Compressed);
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+ }
+
+ SUBCASE("Decompress from an uncompressed buffer.")
+ {
+ constexpr int32_t N = 4242;
+ const std::vector<uint64_t> ExpectedValues = GenerateData(N);
+
+ const CompressedBuffer Compressed = CompressedBuffer::Compress(SharedBuffer::MakeView(MakeMemoryView(ExpectedValues)),
+ OodleCompressor::NotSet,
+ OodleCompressionLevel::None);
+ Reader.SetSource(Compressed);
+
+ {
+ constexpr uint64_t OffsetCount = 0;
+ constexpr uint64_t Count = N;
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+
+ {
+ constexpr uint64_t OffsetCount = 21;
+ constexpr uint64_t Count = 999;
+ const SharedBuffer Uncompressed = Reader.Decompress(OffsetCount * sizeof(uint64_t), Count * sizeof(uint64_t));
+ CHECK(Uncompressed.GetView().EqualBytes(MakeMemoryView(std::span{ExpectedValues}.subspan(OffsetCount, Count))));
+ }
+
+ // Short (malformed) Buffer
+ {
+ IoHash RawHash;
+ uint64_t RawSize = 0;
+ const CompressedBuffer CompressedShort =
+ CompressedBuffer::FromCompressed(Compressed.GetCompressed().Mid(0, Compressed.GetCompressedSize() - 128),
+ /* out */ RawHash,
+ /* out */ RawSize);
+ Reader.SetSource(CompressedShort);
+ const SharedBuffer Uncompressed = Reader.Decompress();
+ CHECK(Uncompressed.IsNull());
+ }
+ }
+}
+
void
compress_forcelink()
{
diff --git a/src/zencore/include/zencore/compositebuffer.h b/src/zencore/include/zencore/compositebuffer.h
index 4e4b4d002..cc03dd156 100644
--- a/src/zencore/include/zencore/compositebuffer.h
+++ b/src/zencore/include/zencore/compositebuffer.h
@@ -62,8 +62,12 @@ public:
[[nodiscard]] ZENCORE_API CompositeBuffer MakeOwned() &&;
/** Returns the concatenation of the segments into a contiguous buffer. */
- [[nodiscard]] ZENCORE_API SharedBuffer Flatten() const&;
- [[nodiscard]] ZENCORE_API SharedBuffer Flatten() &&;
+ [[nodiscard]] inline SharedBuffer Flatten() const& { return ToShared(); }
+ [[nodiscard]] inline SharedBuffer Flatten() && { return ToShared(); }
+
+ /** Returns the concatenation of the segments into a contiguous buffer. */
+ [[nodiscard]] ZENCORE_API SharedBuffer ToShared() const&;
+ [[nodiscard]] ZENCORE_API SharedBuffer ToShared() &&;
/** Returns the middle part of the buffer by taking the size starting at the offset. */
[[nodiscard]] ZENCORE_API CompositeBuffer Mid(uint64_t Offset, uint64_t Size = ~uint64_t(0)) const;
@@ -76,8 +80,12 @@ public:
* @param Offset The byte offset in this buffer that the range starts at.
* @param Size The number of bytes in the range to view or copy.
* @param CopyBuffer The buffer to write the copy into if a copy is required.
+ * @param Allocator The optional allocator to use when the copy buffer is required.
*/
- [[nodiscard]] ZENCORE_API MemoryView ViewOrCopyRange(uint64_t Offset, uint64_t Size, UniqueBuffer& CopyBuffer) const;
+ [[nodiscard]] ZENCORE_API MemoryView ViewOrCopyRange(uint64_t Offset,
+ uint64_t Size,
+ UniqueBuffer& CopyBuffer,
+ std::function<UniqueBuffer(uint64_t Size)> Allocator = UniqueBuffer::Alloc) const;
/**
* Copies a range of the buffer to a contiguous region of memory.
diff --git a/src/zencore/include/zencore/compress.h b/src/zencore/include/zencore/compress.h
index 99ce20d8a..44431f299 100644
--- a/src/zencore/include/zencore/compress.h
+++ b/src/zencore/include/zencore/compress.h
@@ -6,9 +6,18 @@
#include "zencore/blake3.h"
#include "zencore/compositebuffer.h"
+#include "zencore/intmath.h"
+
+#include <limits>
namespace zen {
+class Archive;
+
+namespace detail {
+ struct BufferHeader;
+}
+
enum class OodleCompressor : uint8_t
{
NotSet = 0,
@@ -160,6 +169,115 @@ private:
CompositeBuffer CompressedData;
};
+namespace detail {
+ /** A reusable context for the compressed buffer decoder. */
+ struct DecoderContext
+ {
+ /** The offset in the source at which the compressed buffer begins, otherwise MAX_uint64. */
+ uint64_t HeaderOffset = MAX_uint64;
+ /** The size of the header if known, otherwise 0. */
+ uint64_t HeaderSize = 0;
+ /** The CRC-32 from the header, otherwise 0. */
+ uint32_t HeaderCrc32 = 0;
+ /** Index of the block stored in RawBlock, otherwise MAX_uint32. */
+ uint32_t RawBlockIndex = MAX_uint32;
+
+ /** A buffer used to store the header when HeaderOffset is not MAX_uint64. */
+ UniqueBuffer Header;
+ /** A buffer used to store a raw block when a partial block read is requested. */
+ UniqueBuffer RawBlock;
+ /** A buffer used to store a compressed block when it was not in contiguous memory. */
+ UniqueBuffer CompressedBlock;
+ };
+} // namespace detail
+
+/**
+ * A type that stores the state needed to decompress a compressed buffer.
+ *
+ * The compressed buffer can be in memory or can be loaded from a seekable archive.
+ *
+ * The reader can be reused across multiple source buffers, which allows its temporary buffers to
+ * be reused if they are the right size.
+ *
+ * It is only safe to use the reader from one thread at a time.
+ *
+ * @see CompressedBuffer
+ */
+class CompressedBufferReader
+{
+public:
+ /** Construct a reader with no source. */
+ CompressedBufferReader() = default;
+
+ /** Construct a reader that will read from an archive as needed. */
+ explicit CompressedBufferReader(Archive& Archive);
+
+ /** Construct a reader from an in-memory compressed buffer. */
+ explicit CompressedBufferReader(const CompressedBuffer& Buffer);
+
+ /** Release any temporary buffers that have been allocated by the reader. */
+ void ResetBuffers();
+
+ /** Clears the reference to the source without releasing temporary buffers. */
+ void ResetSource();
+
+ void SetSource(Archive& Archive);
+ void SetSource(const CompressedBuffer& Buffer);
+
+ [[nodiscard]] inline bool HasSource() const { return SourceArchive || SourceBuffer; }
+
+ /** Returns the size of the compressed data. Zero on error. */
+ [[nodiscard]] uint64_t GetCompressedSize();
+
+ /** Returns the size of the raw data. Zero on error. */
+ [[nodiscard]] uint64_t GetRawSize();
+
+ /** Returns the hash of the raw data. Zero on error. */
+ [[nodiscard]] IoHash GetRawHash();
+
+ /**
+ * Returns the compressor and compression level used by this buffer.
+ *
+ * The compressor and compression level may differ from those specified when creating the buffer
+ * because an incompressible buffer is stored with no compression. Parameters cannot be accessed
+ * if this is null or uses a method other than Oodle, in which case this returns false.
+ *
+ * @return True if parameters were written, otherwise false.
+ */
+ [[nodiscard]] bool TryGetCompressParameters(OodleCompressor& OutCompressor,
+ OodleCompressionLevel& OutCompressionLevel,
+ uint64_t& OutBlockSize);
+
+ /**
+ * Decompress into a memory view that is less than or equal to the available raw size.
+ *
+ * @param RawView The view to write to. The size to read is equal to the view size.
+ * @param RawOffset The offset into the raw data from which to decompress.
+ * @return True if the requested range was decompressed, otherwise false.
+ */
+ [[nodiscard]] bool TryDecompressTo(MutableMemoryView RawView, uint64_t RawOffset = 0);
+
+ /**
+ * Decompress into an owned buffer.
+ *
+ * RawOffset must be at most the raw buffer size. RawSize may be MAX_uint64 to read the whole
+ * buffer from RawOffset, and must otherwise fit within the bounds of the buffer.
+ *
+ * @param RawOffset The offset into the raw data from which to decompress.
+ * @param RawSize The size of the raw data to read from the offset.
+ * @return An owned buffer containing the raw data, or null on error.
+ */
+ [[nodiscard]] SharedBuffer Decompress(uint64_t RawOffset = 0, uint64_t RawSize = MAX_uint64);
+ [[nodiscard]] CompositeBuffer DecompressToComposite(uint64_t RawOffset = 0, uint64_t RawSize = MAX_uint64);
+
+private:
+ bool TryReadHeader(detail::BufferHeader& OutHeader, MemoryView& OutHeaderView);
+
+ Archive* SourceArchive = nullptr;
+ const CompressedBuffer* SourceBuffer = nullptr;
+ detail::DecoderContext Context;
+};
+
void compress_forcelink(); // internal
} // namespace zen
diff --git a/src/zencore/include/zencore/intmath.h b/src/zencore/include/zencore/intmath.h
index f24caed6e..0d87e1b78 100644
--- a/src/zencore/include/zencore/intmath.h
+++ b/src/zencore/include/zencore/intmath.h
@@ -7,6 +7,32 @@
#include <stdint.h>
//////////////////////////////////////////////////////////////////////////
+// UE Numeric constants
+
+#define MIN_uint8 ((uint8_t)0x00)
+#define MIN_uint16 ((uint16_t)0x0000)
+#define MIN_uint32 ((uint32_t)0x00000000)
+#define MIN_uint64 ((uint64_t)0x0000000000000000)
+#define MIN_int8 ((int8_t)-128)
+#define MIN_int16 ((int16_t)-32768)
+#define MIN_int32 ((int32_t)0x80000000)
+#define MIN_int64 ((int64_t)0x8000000000000000)
+
+#define MAX_uint8 ((uint8_t)0xff)
+#define MAX_uint16 ((uint16_t)0xffff)
+#define MAX_uint32 ((uint32_t)0xffffffff)
+#define MAX_uint64 ((uint64_t)0xffffffffffffffff)
+#define MAX_int8 ((int8_t)0x7f)
+#define MAX_int16 ((int16_t)0x7fff)
+#define MAX_int32 ((int32_t)0x7fffffff)
+#define MAX_int64 ((int64_t)0x7fffffffffffffff)
+
+#define MIN_flt (1.175494351e-38F) /* min positive value */
+#define MAX_flt (3.402823466e+38F)
+#define MIN_dbl (2.2250738585072014e-308) /* min positive value */
+#define MAX_dbl (1.7976931348623158e+308)
+
+//////////////////////////////////////////////////////////////////////////
#if ZEN_COMPILER_MSC || ZEN_PLATFORM_WINDOWS
# pragma intrinsic(_BitScanReverse)
diff --git a/src/zencore/include/zencore/stream.h b/src/zencore/include/zencore/stream.h
index 9e4996249..a9d35ef1b 100644
--- a/src/zencore/include/zencore/stream.h
+++ b/src/zencore/include/zencore/stream.h
@@ -79,12 +79,37 @@ public:
inline uint64_t CurrentOffset() const { return m_Offset; }
inline void Skip(size_t ByteCount) { m_Offset += ByteCount; };
-private:
+protected:
const uint8_t* m_BufferBase;
uint64_t m_BufferSize;
uint64_t m_Offset = 0;
};
+/**
+ * Archive base class implementation
+ *
+ * Roughly mimics UE's FArchive class, but without all the non-essential functionality
+ */
+class Archive
+{
+public:
+ /** Attempts to set the current offset into backing data storage, this will do nothing if there is no storage. */
+ virtual void Seek(uint64_t InPos) = 0;
+ virtual int64_t Tell() = 0;
+ virtual void Serialize(void* V, int64_t Length) = 0;
+ inline bool IsError() { return false; }
+};
+
+class BufferReader : public Archive, private BinaryReader
+{
+public:
+ BufferReader(const void* Buffer, uint64_t Size) : BinaryReader(Buffer, Size) {}
+
+ virtual void Seek(uint64_t InPos) override;
+ virtual int64_t Tell() override;
+ virtual void Serialize(void* V, int64_t Length) override;
+};
+
void stream_forcelink(); // internal
} // namespace zen
diff --git a/src/zencore/include/zencore/zencore.h b/src/zencore/include/zencore/zencore.h
index 02e9dd5d6..47ff2ebc2 100644
--- a/src/zencore/include/zencore/zencore.h
+++ b/src/zencore/include/zencore/zencore.h
@@ -68,6 +68,10 @@
# pragma warning(disable : 4324) // warning C4324: '<type>': structure was padded due to alignment specifier
# pragma warning(default : 4668) // warning C4668: 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives'
# pragma warning(default : 4100) // warning C4100: 'identifier' : unreferenced formal parameter
+# pragma warning( \
+ disable : 4373) // '%$S': virtual function overrides '%$pS', previous versions of the compiler did not override when parameters
+ // only differed by const/volatile qualifiers
+ // https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-3-c4373
#endif
#ifndef ZEN_THIRD_PARTY_INCLUDES_START
diff --git a/src/zencore/stream.cpp b/src/zencore/stream.cpp
index 3402e51be..ee97a53c4 100644
--- a/src/zencore/stream.cpp
+++ b/src/zencore/stream.cpp
@@ -51,6 +51,27 @@ BinaryWriter::Reset()
}
//////////////////////////////////////////////////////////////////////////
+
+void
+BufferReader::Seek(uint64_t InPos)
+{
+ // Validate range
+ m_Offset = InPos;
+}
+
+int64_t
+BufferReader::Tell()
+{
+ return CurrentOffset();
+}
+
+void
+BufferReader::Serialize(void* V, int64_t Length)
+{
+ Read(V, Length);
+}
+
+//////////////////////////////////////////////////////////////////////////
//
// Testing related code follows...
//