#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_STORAGE_HELPERS_STREAMINGCACHECONTEXT_H #define NUCLEX_STORAGE_HELPERS_STREAMINGCACHECONTEXT_H #include "Nuclex/Storage/Config.h" #include "Nuclex/Storage/Blob.h" #include "Cache.h" #include #include // This ugly wart is needed because some creep at Microsoft thought it would be a good // idea to define min and max macros (in lowercase) inside Windows.h. And somehow // Windows.h is getting included here, possibly through the Visual C++ standard headers. // Shouldn't affect other platforms and if you have macros like this, you're doing it wrong. #undef min #undef max namespace Nuclex { namespace Storage { namespace Helpers { // ------------------------------------------------------------------------------------------- // /// Cache context that processes data incrementally template < typename TIdentifier, typename TComparer = std::equal_to > class StreamingCacheContext : public Cache::Context { /// Size of the buffer used to decompress the file public: static const std::size_t DecompressionBufferSize = 8192; /// When the buffer gets below this size, it is refilled /// /// The remaining buffer contents need to be copied to the front of the buffer, /// so making this too large will cause needless copying of memory blocks. /// public: static const std::size_t BufferRefillSize = 64; /// Initializes a new steaming context /// Identifier of the file the context is handling /// Blob the input data is coming from public: StreamingCacheContext( const TIdentifier &identifier, const std::shared_ptr &blob, std::uint64_t compressedDataStartOffset, std::uint64_t compressedDataLength ) : Context(identifier), blob(blob), compressedLocation(compressedDataStartOffset), compressedRemaining(compressedDataLength) { // Pre-allocate the memory of the buffers used to hold compressed and uncompressed data. // Vectors will never shrink, thus, this guarantees that no additional memory allocations // will happen during extraction. this->CompressedData.reserve(DecompressionBufferSize); this->UncompressedData.reserve(DecompressionBufferSize); } /// Reads data from the context /// Location from which data needs to be read /// Buffer into which the data will be read /// Number of bytes that need to be read public: void ReadAt(std::uint64_t location, void *buffer, std::size_t count) { using namespace std; assert((location >= this->Location) && "Cache can be advanced in forward direction only"); // Determine the offset the requested data has as seen from our current buffer. // This may be way beyond the buffer capacity, in which case we need to seek std::uint64_t bufferStartOffset = location - this->Location; // If the requested data is past the end of the data we have buffered currently, // skip ahead until we reach the desired area. if(bufferStartOffset > this->UncompressedData.size()) { SkipTo(location); bufferStartOffset = location - this->Location; } std::uint8_t *indexableBuffer = reinterpret_cast(buffer); // Copy any data the caller is interested in out of the current buffer. The buffer start // offset is guaranteed to fit within a normal size_t now. std::size_t bufferCount = std::min( this->UncompressedData.size() - static_cast(bufferStartOffset), count ); if(bufferCount > 0) { std::copy_n( &this->UncompressedData[static_cast(bufferStartOffset)], bufferCount, indexableBuffer ); // If this was all the data the caller wanted, we can exit early. if(bufferCount == count) { return; } // Advance the pointers and counters in accordance with the part of the buffer we have // already processed above indexableBuffer += bufferCount; count -= bufferCount; location += bufferCount; } // If the amount of data requested is more than the decompression buffer can hold, // there's no point in hitting the decompression buffer at all and we can directly // extract the decompressed data into the caller-provided buffer. if(count > DecompressionBufferSize) { count = ExtractDirectly(indexableBuffer, count); if(count > 0 ){ ExtractUsingBuffer(indexableBuffer, count); } } else { ExtractUsingBuffer(indexableBuffer, count); } //if(this->Location + this->UncompressedData.size() == uncompressedLength) { // ReleaseCompressedData(); //} } /// Skips forward until the specified location is reached /// Location up to which data will be skipped protected: virtual void SkipTo(std::uint64_t location) = 0; /// Extracts data directly into the caller-provided memory block /// Location from which on extraction should begin /// Buffer into which the data will be read /// Number of bytes the caller wants to read /// The number of bytes remaining to be extracted protected: virtual std::size_t ExtractDirectly( std::uint8_t *indexableBuffer, std::size_t count ) = 0; /// Extracts data using the decompression buffer and provides it /// Location from which on extraction should begin /// Buffer into which the data will be read /// Number of bytes the caller wants to read protected: virtual void ExtractUsingBuffer( std::uint8_t *indexableBuffer, std::size_t count ) = 0; /// /// Frees the memory used for decompression after stream has been decompressed /// /// /// Because decompressors can only advance in forward direction, after a decompressor /// has fini /// protected: virtual void ReleaseCompressedData() { this->CompressedData.resize(0); this->CompressedData.shrink_to_fit(); } /// Refills the input-buffer, taking over the remaining data, if any /// /// Amount of data still remaining in the input buffer (assumed to be at /// the very end of the bfufer /// protected: void RefillInputBuffer(std::size_t remainingInputLength = 0) { // First, move the remaining buffer contents to the start of the buffer so the new data // can be appended after it (a ring buffer would be cooler, but ZLib expects linear memory) if(remainingInputLength > 0) { std::size_t dataStartOffset = this->CompressedData.size() - remainingInputLength; // Make sure that the memory regions don't overlap (otherwise we'd have to use // std::copy_backward and pay with some performance, which we don't want to). { using namespace std; assert( (remainingInputLength < dataStartOffset) && "RefillInputBuffer cannot be used until the buffer is at least half empty" ); } std::copy_n( &this->CompressedData[dataStartOffset], remainingInputLength, &this->CompressedData[0] ); } // Find out how many bytes the buffer can be refilled with. Usually, the buffer is // refilled to capacity, but near the end of the compressed data blob, it may be less. std::size_t bytesToCopy = this->CompressedData.capacity() - remainingInputLength; if(bytesToCopy > this->compressedRemaining) { bytesToCopy = static_cast(this->compressedRemaining); } // Resize the buffer to the exact amount of data we're putting in it, then append // the data right after the leftover bits we moved to the beginning of the buffer. this->CompressedData.resize(bytesToCopy + remainingInputLength); this->blob->ReadAt( this->compressedLocation, &this->CompressedData[remainingInputLength], bytesToCopy ); this->compressedLocation += bytesToCopy; this->compressedRemaining -= bytesToCopy; } private: StreamingCacheContext(const StreamingCacheContext &); private: StreamingCacheContext &operator =(const StreamingCacheContext &); /// Blob the context is caching data for private: std::shared_ptr blob; /// Contains the compressed data protected: std::vector CompressedData; /// Contains the uncompressed data protected: std::vector UncompressedData; /// Current location within the compressed data private: std::uint64_t compressedLocation; /// Number of bytes of compressed data that are remaining private: std::uint64_t compressedRemaining; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Storage::Helpers #endif // NUCLEX_STORAGE_HELPERS_STREAMINGCACHECONTEXT_H