blob: 70c4c33b94213028604ffcde6262e2da04930d2f [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef RIEGELI_BYTES_PULLABLE_READER_H_
#define RIEGELI_BYTES_PULLABLE_READER_H_
#include <stddef.h>
#include <memory>
#include <utility>
#include "absl/base/optimization.h"
#include "absl/strings/cord.h"
#include "riegeli/base/base.h"
#include "riegeli/base/chain.h"
#include "riegeli/base/object.h"
#include "riegeli/bytes/backward_writer.h"
#include "riegeli/bytes/reader.h"
#include "riegeli/bytes/writer.h"
namespace riegeli {
// Abstract class `PullableReader` helps to implement
// `Reader::PullSlow(min_length, recommended_length)` with `min_length > 1`.
//
// `PullableReader` accumulates pulled data in a scratch buffer if needed.
class PullableReader : public Reader {
private:
struct Scratch;
protected:
// Helps to implement move constructor or move assignment if scratch is used.
//
// Moving the source should be in scope of a `BehindScratch` local variable,
// unless source buffer pointers are known to remain unchanged during a move
// or their change does not need to be reflected elsewhere.
//
// This temporarily reveals the relationship between the source and the buffer
// pointers, in case it was hidden behind scratch usage. In a `BehindScratch`
// scope, scratch is not used, and buffer pointers may be changed. The current
// position reflects what has been read from the source and must not be
// changed.
class BehindScratch {
public:
explicit BehindScratch(PullableReader* context);
BehindScratch(const BehindScratch&) = delete;
BehindScratch& operator=(const BehindScratch&) = delete;
~BehindScratch();
private:
void Enter();
void Leave();
PullableReader* context_;
std::unique_ptr<Scratch> scratch_;
size_t read_from_scratch_;
};
// Creates a `PullableReader` with the given initial state.
explicit PullableReader(InitiallyClosed) noexcept
: Reader(kInitiallyClosed) {}
explicit PullableReader(InitiallyOpen) noexcept : Reader(kInitiallyOpen) {}
PullableReader(PullableReader&& that) noexcept;
PullableReader& operator=(PullableReader&& that) noexcept;
// Makes `*this` equivalent to a newly constructed `PullableReader`. This
// avoids constructing a temporary `PullableReader` and moving from it.
// Derived classes which redefine `Reset()` should include a call to
// `PullableReader::Reset()`.
void Reset(InitiallyClosed);
void Reset(InitiallyOpen);
// Returns `true` if scratch is used, which means that buffer pointers are
// temporarily unrelated to the source. This is exposed for assertions.
bool scratch_used() const;
// `PullableReader::{Done,SyncImpl}()` seek the source back to the current
// position if scratch is used but not all data from scratch were read.
// This is feasible only if `SupportsRandomAccess()`.
//
// Warning: if `!SupportsRandomAccess()`, the source will have an
// unpredictable amount of extra data consumed because of buffering.
//
// For propagating `{Close,Sync}()` to dependencies, `{Done,SyncImpl}()`
// should be overridden to call `PullableReader::{Done,SyncImpl}()` and then
// close/sync the dependencies.
// Implementation of `Done()`, called while scratch is not used. This is
// called before buffer pointers are reset.
//
// If scratch was used but not all data from scratch were read and
// `!SupportsRandomAccess()`, seeking back is not feasible and
// `DoneBehindScratch()` is not called.
//
// By default calls `SyncBehindScratch(SyncType::kFromObject)`, which by
// default does nothing.
//
// Precondition: `!scratch_used()`
virtual void DoneBehindScratch();
// Implementation of `PullSlow(1, 0)`, called while scratch is not used.
//
// Preconditions:
// `available() == 0`
// `!scratch_used()`
virtual bool PullBehindScratch() = 0;
// Implementation of `ReadSlow()`, `CopySlow()`, `ReadHintSlow()`,
// `SyncImpl()`, and `SeekSlow()`, called while scratch is not used.
//
// Regarding `SyncBehindScratch()`, if scratch was used but not all data from
// scratch were read and `!SupportsRandomAccess()`, seeking back is not
// feasible and `SyncBehindScratch()` is not called.
//
// By default they are implemented analogously to the corresponding `Reader`
// functions.
//
// Preconditions:
// like the corresponding `Reader` functions
// `!scratch_used()`
virtual bool ReadBehindScratch(size_t length, char* dest);
virtual bool ReadBehindScratch(size_t length, Chain& dest);
virtual bool ReadBehindScratch(size_t length, absl::Cord& dest);
virtual bool CopyBehindScratch(Position length, Writer& dest);
virtual bool CopyBehindScratch(size_t length, BackwardWriter& dest);
virtual void ReadHintBehindScratch(size_t length);
virtual bool SyncBehindScratch(SyncType sync_type);
virtual bool SeekBehindScratch(Position new_pos);
void Done() override;
bool PullSlow(size_t min_length, size_t recommended_length) override;
bool ReadSlow(size_t length, char* dest) override;
bool ReadSlow(size_t length, Chain& dest) override;
bool ReadSlow(size_t length, absl::Cord& dest) override;
bool CopySlow(Position length, Writer& dest) override;
bool CopySlow(size_t length, BackwardWriter& dest) override;
void ReadHintSlow(size_t length) override;
bool SyncImpl(SyncType sync_type) override;
bool SeekSlow(Position new_pos) override;
private:
struct Scratch {
ChainBlock buffer;
const char* original_start = nullptr;
size_t original_start_to_limit = 0;
size_t original_start_to_cursor = 0;
};
void SyncScratch();
// Stops using scratch and returns `true` if all remaining data in scratch
// come from a single fragment of the original source.
bool ScratchEnds();
std::unique_ptr<Scratch> scratch_;
// Invariants if `scratch_used()`:
// `start() == scratch_->buffer.data()`
// `start_to_limit() == scratch_->buffer.size()`
};
// Implementation details follow.
inline PullableReader::PullableReader(PullableReader&& that) noexcept
: Reader(std::move(that)),
// Using `that` after it was moved is correct because only the base class
// part was moved.
scratch_(std::move(that.scratch_)) {}
inline PullableReader& PullableReader::operator=(
PullableReader&& that) noexcept {
Reader::operator=(std::move(that));
// Using `that` after it was moved is correct because only the base class part
// was moved.
scratch_ = std::move(that.scratch_);
return *this;
}
inline void PullableReader::Reset(InitiallyClosed) {
Reader::Reset(kInitiallyClosed);
if (ABSL_PREDICT_FALSE(scratch_used())) scratch_->buffer.Clear();
}
inline void PullableReader::Reset(InitiallyOpen) {
Reader::Reset(kInitiallyOpen);
if (ABSL_PREDICT_FALSE(scratch_used())) scratch_->buffer.Clear();
}
inline bool PullableReader::scratch_used() const {
return scratch_ != nullptr && !scratch_->buffer.empty();
}
inline PullableReader::BehindScratch::BehindScratch(PullableReader* context)
: context_(context) {
if (ABSL_PREDICT_FALSE(context_->scratch_used())) Enter();
}
inline PullableReader::BehindScratch::~BehindScratch() {
if (ABSL_PREDICT_FALSE(scratch_ != nullptr)) Leave();
}
} // namespace riegeli
#endif // RIEGELI_BYTES_PULLABLE_READER_H_