BmnRoot 26.10.0-18-g4e3414f9
Software framework of the BM@N experiment
Loading...
Searching...
No Matches
BmnReaderCacheManager.h
Go to the documentation of this file.
1#pragma once
2#ifndef BmnReaderCacheManager_h
3#define BmnReaderCacheManager_h
4
5// headers from this target
7// std:: headers
8#include <array>
9#include <cassert>
10#include <cstring>
11#include <filesystem>
12#include <fstream>
13// FairLogger headers
14#include <fairlogger/Logger.h>
15
16// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
17// ================== BmnReaderCacheManager class ==================
18// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
19
20/*
21 * This class should be used for easier management of various compression formats. That is, it
22 * handles input-file reads, keeps the needed data in cache for optimal (fast) reads in subsequent
23 * read calls while still relying on an owner class that inherits from `BmnIArchiveLibRead`.
24 */
25template<size_t SourceBufferSize = 32 * 1024ull, size_t DestinationBufferSize = 64 * 1024ull>
26class BmnReaderCacheManager final
27{
28 static_assert(SourceBufferSize >= 32 * 1024ull && DestinationBufferSize >= 32 * 1024ull,
29 "Don't reduce buffer sizes too much as you will lose efficiency!");
30 static_assert(SourceBufferSize <= DestinationBufferSize,
31 "For uncompressing, uncompressed buffers are expected to be bigger than compressed buffers!");
32
33 public:
37 // We also don't want to copy the data
40 // removing move constructor/assignment as I'm using arrays, i.e., I don't want to jump all over the heap for no
41 // reason. One should use the class without any pointers (no need for them), while the owner class should be
42 // allocated on the heap
45
46 public:
47 // @return The number of bytes that are read from an input stream and fed into an underlying decompression system
48 [[nodiscard]] constexpr size_t GetSourceBufferSize() const noexcept;
49
50 // @return The number of bytes that are used for caching decompressed data - the data is used for fast reading
51 [[nodiscard]] constexpr size_t GetDestinationBufferSize() const noexcept;
52
53 // inline methods - based around a reading stream
54
55 [[nodiscard]] inline bool IsOpen() const noexcept;
56 [[nodiscard]] inline bool IsGood() const noexcept;
57 [[nodiscard]] inline bool IsEOF() const noexcept;
58 [[nodiscard]] inline int64_t Tell() noexcept;
59 inline void Clear();
60 // inline methods - some getters
61
62 [[nodiscard]] inline size_t GetCurrentOutputBufferPosition() const noexcept;
63 [[nodiscard]] inline size_t GetTotalOutputBufferFilledSize() const noexcept;
64 [[nodiscard]] inline size_t GetCurrentInputBufferPosition() const noexcept;
65 [[nodiscard]] inline size_t GetTotalInputBufferFilledSize() const noexcept;
66
67 public:
68 /*
69 * A method for opening an input stream from a compressed file.
70 * @param inputFile A path to the file that should be opened
71 * @param inputOpenMode Appropriate std::ios::openmode flags related to the reading (mostly, those should be
72 * `std::ios::in | std::ios::binary`)
73 */
74 void Open(const std::filesystem::path& inputFile, const std::ios::openmode inputOpenMode);
75
76 /*
77 * Safely closes a stream and removes unnecessary data so a new stream can be opened
78 */
79 void Close();
80
81 /*
82 * Handles reading of a next data chunk. Before calling using decompression calls, it will forward
83 * all unused data from the caching buffer.
84 * @param dataToFill A pointer to the data that should be filled in this method
85 * @param sizeToFill A number of bytes that should be filled in `dataToFill`
86 * @return A number of successfully filled bytes. If `sizeOfFill != returnSize`, the stream either reached the end,
87 * or a fatal error has occurred
88 */
89 [[nodiscard]] int64_t Read(void* dataToFill, int64_t sizeToFill);
90
91 /*
92 * Seeks a new reading points in an input file
93 * @param value A new reading point from an input file
94 * @warning While using archives, it can be dangerous trying to access file bytes randomly
95 */
96 void Seek(int64_t value);
97
98 /*
99 * A method that helps to move to a specific place in the reading file. This method must move
100 * the output stream forward. If backward move is needed, the owner should handle it, mostly by
101 * reopening the stream.
102 * @param value A value in an output (decompressed) stream to which one wants to jump
103 * @warning This method may not be efficient and it would be best if jumping is not used while
104 * decompressing. Still, it can be used efficiently if one has compressed data with proper tactics
105 */
106 void MoveToDecompressingLocation(int64_t value);
107
108 private:
109 /*
110 * Handles extraction and decompression of the next data chunk. The decompression should be performed by
111 * the owner
112 */
113 [[nodiscard]] bool UncompressNextDataChunk();
114
115 private:
116 std::array<uint8_t, SourceBufferSize> fArchiveSourceBuffer;
117 std::array<uint8_t, DestinationBufferSize> fArchiveOutputBuffer;
119 std::ifstream fFileBeingProcessed;
124 /*
125 * keeping it as signed so `0 - 1` wouldn't go to
126 * `std::numeric_limits<size_t>::max()` and cause some unexpected error
127 */
129};
130
131// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
132// ================== BmnReaderCacheManager inline methods ==================
133// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
134
135template<size_t src, size_t dst>
136constexpr size_t BmnReaderCacheManager<src, dst>::GetSourceBufferSize() const noexcept
137{
138 return src;
139}
140
141template<size_t src, size_t dst>
142constexpr size_t BmnReaderCacheManager<src, dst>::GetDestinationBufferSize() const noexcept
143{
144 return dst;
145}
146
147template<size_t src, size_t dst>
148bool BmnReaderCacheManager<src, dst>::IsOpen() const noexcept
149{
150 return fFileBeingProcessed.is_open();
151}
152
153template<size_t src, size_t dst>
154bool BmnReaderCacheManager<src, dst>::IsGood() const noexcept
155{
156 return fFileBeingProcessed.good();
157}
158
159template<size_t src, size_t dst>
160bool BmnReaderCacheManager<src, dst>::IsEOF() const noexcept
161{
164 && fOwnerArchiveReference.HasReachedTheEndOfStream();
165}
166
167template<size_t src, size_t dst>
169{
170 if (!fFileBeingProcessed.is_open() || !fFileBeingProcessed.good())
171 return -1ll;
172 // tellg is not const o.O
174}
175
176template<size_t src, size_t dst>
178{
179 fFileBeingProcessed.clear();
180}
181
182template<size_t src, size_t dst>
184{
186}
187
188template<size_t src, size_t dst>
190{
192}
193
194template<size_t src, size_t dst>
196{
198}
199
200template<size_t src, size_t dst>
202{
204}
205
206// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
207// ================== BmnReaderCacheManager public methods ==================
208// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
209
210template<size_t src, size_t dst>
211BmnReaderCacheManager<src, dst>::BmnReaderCacheManager(BmnIArchiveLibRead& anOwnerArchive)
214 , fOwnerArchiveReference(anOwnerArchive)
221{}
222
223template<size_t src, size_t dst>
224void BmnReaderCacheManager<src, dst>::Open(const std::filesystem::path& inputFile,
225 const std::ios::openmode inputOpenMode)
226{
227 try {
228 if (!std::filesystem::exists(inputFile) || !std::filesystem::is_regular_file(inputFile)) {
229 LOG(error) << "`BmnReaderCacheManager<src, dst>::Open` expects an actual existing file! Passed path: "
230 << inputFile << '\n';
231 return;
232 }
233 if (fFileBeingProcessed.is_open()) {
234 LOG(warning) << "You are about to open a new file in `BmnReaderCacheManager<src, dst>::Open` while the "
235 "previous one wasn't closed - implicitly calling `Close()`!\n";
236 }
237 Close();
238 fRemainingFileSizeToRead = (int64_t)std::filesystem::file_size(inputFile);
239 } catch (const std::bad_alloc& errorDescription) {
240 LOG(error) << "Filesystem failure! Reason: " << errorDescription.what() << '\n';
241 return;
242 } catch (const std::filesystem::filesystem_error& errorDescription) {
243 LOG(error) << "Filesystem failure! Reason: " << errorDescription.what() << '\n';
244 return;
245 }
246 fFileBeingProcessed.open(inputFile, inputOpenMode);
247}
248
249template<size_t src, size_t dst>
251{
252 if (!fFileBeingProcessed.is_open())
253 return;
254 fCurrentOutputBufferPosition = 0ull;
255 fTotalOutputBufferFilledSize = 0ull;
256 fCurrentInputBufferPosition = 0ull;
257 fTotalInputBufferFilledSize = 0ull;
258 fRemainingFileSizeToRead = -1ll;
259 fFileBeingProcessed.close();
260}
261
262template<size_t tSrcSize, size_t tDstSize>
263int64_t BmnReaderCacheManager<tSrcSize, tDstSize>::Read(void* dataToFill, int64_t sizeToFill)
264{
265 if (!fFileBeingProcessed.is_open()) {
266 LOG(error) << "Calling `BmnReaderCacheManager::Read` while the input file is not ready for it!\n";
267 return 0ll;
268 }
269 // As we work with decompression here, we won't be able to know how many raw bytes will be obtained
270 // from N compressed bytes (even if we knew the compression ratio, it'd still be just an average).
271 // Thus, no matter what, we will be always reading the data into the output buffer, and only then
272 // copy them into `dataToFill`
273 if (sizeToFill <= 0ll) {
274 LOG(error) << "Calling `BmnReaderCacheManager::Read` with `sizeToFill < 0`!\n";
275 return 0ll;
276 }
277 size_t dataToFillOffset = 0ull;
278 auto fillTheBuffer = [this, &dataToFillOffset, dataToFill, &sizeToFill]() {
279 // we might encounter a few extra operations here for large `sizeToFill`, but it doesn't matter
280 size_t unreadBuffer = fTotalOutputBufferFilledSize - fCurrentOutputBufferPosition;
281 size_t bytesToCopy = unreadBuffer > (size_t)sizeToFill ? (size_t)sizeToFill : unreadBuffer;
282 std::memcpy((uint8_t*)dataToFill + dataToFillOffset, fArchiveOutputBuffer.data() + fCurrentOutputBufferPosition,
283 bytesToCopy);
284 fCurrentOutputBufferPosition += bytesToCopy;
285 if (fTotalOutputBufferFilledSize == fCurrentOutputBufferPosition) {
286 fCurrentOutputBufferPosition = 0ull;
287 fTotalOutputBufferFilledSize = 0ull;
288 }
289 sizeToFill -= (int64_t)bytesToCopy;
290 dataToFillOffset += bytesToCopy;
291 };
292 if (fTotalOutputBufferFilledSize - fCurrentOutputBufferPosition > 0)
293 fillTheBuffer();
294 while (sizeToFill > 0ll && !IsEOF()) {
295 // at this point we will either skip the loop as `sizeToFill == 0` or `fTotalOutputBufferFilledSize ==
296 // fCurrentOutputBufferPosition`
297 if (!UncompressNextDataChunk()) {
298 LOG(error) << "Encountered an error while uncompressing in `BmnReaderCacheManager::Read`... breaking "
299 "unhandled read!\nError reason: "
300 << fOwnerArchiveReference.GetLastUncompressErrorDescription() << '\n';
301 return 0ll;
302 }
303 // and we keep filling till we are done
304 fillTheBuffer();
305 }
306 return (int64_t)dataToFillOffset;
307}
308
309template<size_t src, size_t dst>
311{
312 fFileBeingProcessed.seekg(value);
313}
314
315template<size_t src, size_t dst>
317{
318 if (!fFileBeingProcessed.is_open()) {
319 LOG(warning) << "Calling `BmnReaderCacheManager::MoveToDecompressingLocation` while the input file is not "
320 "ready for it!\n";
321 return;
322 }
323 assert(value > 0ll);
324 assert(fTotalInputBufferFilledSize + value >= fOwnerArchiveReference.GetNumberOfDecompressedBytes());
325 while (true) {
326 if ((size_t)value <= fOwnerArchiveReference.GetNumberOfDecompressedBytes()) {
327 fCurrentOutputBufferPosition =
328 fTotalOutputBufferFilledSize + value - fOwnerArchiveReference.GetNumberOfDecompressedBytes();
329 return;
330 }
331 if (IsEOF())
332 return;
333 if (!UncompressNextDataChunk())
334 return;
335 }
336}
337
338// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
339// ================== BmnReaderCacheManager private methods ==================
340// o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
341
342template<size_t tSrcSize, size_t tDstSize>
344{
345 if (!fFileBeingProcessed.good()) {
346 // we can expect that different underlying libraries handle EOF differently, so we need to add
347 // `IsStreamInGoodState` as well
348 if (fFileBeingProcessed.eof() && fCurrentInputBufferPosition == fTotalInputBufferFilledSize
349 && !fOwnerArchiveReference.IsStreamInGoodState())
350 {
351 LOG(error) << "Calling `BmnReaderCacheManager::UncompressNextDataChunk` while the stream is not good!\n";
352 return false;
353 }
354 }
355 // Here we must be very carful as some libraries might not consume entire input buffer while uncompressing,
356 // i.e., the compressed data might be split into segments (packets). In that case, libraries might need entire
357 // segments or they won't process the data at all
358 bool shouldReadMoreStreamData = false;
359 if (fCurrentInputBufferPosition != fTotalInputBufferFilledSize) {
360 // if we used only a small portion of the input buffer, there's no need to move it around the memory and load
361 // more... it would have reduced the code efficiency
362 if (fCurrentInputBufferPosition != 0
363 && (double)fCurrentInputBufferPosition > 0.75 * (double)fTotalInputBufferFilledSize)
364 {
365 shouldReadMoreStreamData = true;
366 // using memmove here as we might encounter some overlapping while it internally handles such cases if
367 // needed. if no overlapping, it will practically memcpy
368 size_t nextMax = fTotalInputBufferFilledSize - fCurrentInputBufferPosition;
369 std::memmove(fArchiveSourceBuffer.data(),
370 (uint8_t*)fArchiveSourceBuffer.data() + fCurrentInputBufferPosition, nextMax);
371 fTotalInputBufferFilledSize = nextMax;
372 fCurrentInputBufferPosition = 0ull;
373 }
374 } else {
375 fCurrentInputBufferPosition = 0ull;
376 fTotalInputBufferFilledSize = 0ull;
377 shouldReadMoreStreamData = true;
378 }
379 if (!fFileBeingProcessed.eof() && shouldReadMoreStreamData) {
380 // Now we read the raw data from the stream
381 fFileBeingProcessed.read(reinterpret_cast<char*>(fArchiveSourceBuffer.data()) + fTotalInputBufferFilledSize,
382 (int64_t)(tSrcSize - fTotalInputBufferFilledSize));
383 // std::ofstream internally checks the file size and stops reading when EOF is reached, even when we ask
384 // additional bytes -> `gcount()`
385 fTotalInputBufferFilledSize += (size_t)fFileBeingProcessed.gcount();
386 fRemainingFileSizeToRead -= fFileBeingProcessed.gcount();
387 }
388 size_t noOfUsedBytes = 0ull;
389 BmnFileStreamResults decompressStatus =
390 fOwnerArchiveReference.Uncompress(fArchiveSourceBuffer.data() + fCurrentInputBufferPosition,
391 fTotalInputBufferFilledSize - fCurrentInputBufferPosition, noOfUsedBytes,
392 fArchiveOutputBuffer.data(), tDstSize, fTotalOutputBufferFilledSize);
393 fCurrentInputBufferPosition += noOfUsedBytes;
394 if (decompressStatus == BmnFileStreamResults::Success || decompressStatus == BmnFileStreamResults::ReachedEOF) {
395 return true;
396 }
397 LOG(error) << "Encountered an error while uncompressing in `BmnReaderCacheManager::Read`... breaking unhandled "
398 "read!\nError reason: "
399 << fOwnerArchiveReference.GetLastUncompressErrorDescription() << '\n';
400 return false;
401}
402
403#endif // BmnReaderCacheManager_h
constexpr size_t GetSourceBufferSize() const noexcept
bool IsOpen() const noexcept
BmnReaderCacheManager()=delete
BmnReaderCacheManager(const BmnReaderCacheManager &)=delete
BmnReaderCacheManager & operator==(BmnReaderCacheManager &&)=delete
bool IsEOF() const noexcept
constexpr size_t GetDestinationBufferSize() const noexcept
size_t GetTotalOutputBufferFilledSize() const noexcept
size_t GetCurrentInputBufferPosition() const noexcept
BmnIArchiveLibRead & fOwnerArchiveReference
size_t GetCurrentOutputBufferPosition() const noexcept
BmnReaderCacheManager(BmnIArchiveLibRead &)
bool IsGood() const noexcept
void Open(const std::filesystem::path &inputFile, const std::ios::openmode inputOpenMode)
constexpr size_t GetSourceBufferSize() const noexcept
BmnReaderCacheManager & operator=(const BmnReaderCacheManager &)=delete
BmnReaderCacheManager(BmnReaderCacheManager &&)=delete
int64_t Read(void *dataToFill, int64_t sizeToFill)
~BmnReaderCacheManager()=default
size_t GetTotalInputBufferFilledSize() const noexcept
std::array< uint8_t, SourceBufferSize > fArchiveSourceBuffer
void MoveToDecompressingLocation(int64_t value)
std::array< uint8_t, DestinationBufferSize > fArchiveOutputBuffer
Definition json.hpp:4446