FlatCityBuf C++ reader 0.8.0
Native C++17 reader for FlatCityBuf, the cloud-optimized CityJSON format
Loading...
Searching...
No Matches
range_reader.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include "detail/checked.hpp"
6
7namespace fcb {
8
9void RangeReader::read_batch(std::vector<RangeRequest>& requests) {
10 for (auto& r : requests) {
11 r.data = read(r.offset, r.length);
12 }
13}
14
15// ---------------------------------------------------------------- File ---
16
17FileRangeReader::FileRangeReader(const std::string& path)
18 : path_(path), stream_(path, std::ios::binary | std::ios::ate) {
19 if (!stream_) {
20 throw Error(ErrorCode::IoError, "cannot open file: " + path);
21 }
22 size_ = static_cast<std::uint64_t>(stream_.tellg());
23}
24
25std::uint64_t FileRangeReader::total_size() { return size_; }
26
27std::vector<std::uint8_t> FileRangeReader::read(std::uint64_t offset, std::uint64_t length) {
28 if (length == 0)
29 return {};
30 if (offset >= size_)
31 return {};
32
33 // Clamp to EOF: a range crossing the end returns exactly what exists.
34 const std::uint64_t n = std::min<std::uint64_t>(length, size_ - offset);
35 std::vector<std::uint8_t> out(static_cast<std::size_t>(n));
36
37 stream_.clear();
38 stream_.seekg(static_cast<std::streamoff>(offset), std::ios::beg);
39 stream_.read(reinterpret_cast<char*>(out.data()), static_cast<std::streamsize>(n));
40 if (stream_.gcount() != static_cast<std::streamsize>(n)) {
41 throw Error(ErrorCode::IoError, "short read from " + path_);
42 }
43 return out;
44}
45
46// ------------------------------------------------------------ Buffered ---
47
48BufferedRangeReader::BufferedRangeReader(std::shared_ptr<RangeReader> inner,
49 std::uint64_t min_req_size)
50 : inner_(std::move(inner)), min_req_size_(min_req_size) {}
51
52std::uint64_t BufferedRangeReader::total_size() { return inner_->total_size(); }
53
54std::uint64_t BufferedRangeReader::clamped_fetch(std::uint64_t offset, std::uint64_t length) {
55 // Over-fetch to min_req_size, but never past the end of the resource --
56 // otherwise a small valid request near UINT64_MAX becomes an
57 // overflowing one.
58 const std::uint64_t want = std::max<std::uint64_t>(length, min_req_size_);
59 const std::uint64_t total = inner_->total_size();
60 if (offset >= total)
61 return length;
62 return std::min<std::uint64_t>(want, total - offset);
63}
64
65bool BufferedRangeReader::covers(std::uint64_t offset, std::uint64_t length) const {
66 // Validate the request unconditionally: returning early on an empty
67 // cache would let an overflowing range through to the transport.
68 (void)detail::range_end(offset, length);
69 if (buf_.empty() || offset < buf_offset_)
70 return false;
71 // Throws rather than wrapping; both ends derive from file-supplied values.
72 return detail::range_end(offset, length) <= detail::range_end(buf_offset_, buf_.size());
73}
74
75std::vector<std::uint8_t> BufferedRangeReader::slice_from_buffer(std::uint64_t offset,
76 std::uint64_t length) const {
77 const std::uint64_t rel = offset - buf_offset_;
78 return std::vector<std::uint8_t>(buf_.begin() + static_cast<std::ptrdiff_t>(rel),
79 buf_.begin() + static_cast<std::ptrdiff_t>(rel + length));
80}
81
82std::vector<std::uint8_t> BufferedRangeReader::read(std::uint64_t offset, std::uint64_t length) {
83 if (length == 0)
84 return {}; // contract: never contact the transport
85
86 if (!covers(offset, length)) {
87 buf_ = inner_->read(offset, clamped_fetch(offset, length));
88 buf_offset_ = offset;
89 }
90
91 const std::uint64_t rel = offset - buf_offset_;
92 if (rel >= buf_.size())
93 return {};
94 const std::uint64_t n = std::min<std::uint64_t>(length, buf_.size() - rel);
95 return slice_from_buffer(offset, n);
96}
97
98void BufferedRangeReader::read_batch(std::vector<RangeRequest>& requests) {
99 // Serve what the cache already covers and forward only the misses.
100 // Blindly forwarding everything would defeat the decorator exactly when
101 // tree traversal batches -- which is its whole reason to exist.
102 struct Miss {
103 std::size_t index;
104 std::uint64_t offset;
105 std::uint64_t want;
106 std::uint64_t fetch;
107 };
108 std::vector<Miss> misses;
109 misses.reserve(requests.size());
110
111 for (std::size_t i = 0; i < requests.size(); ++i) {
112 auto& r = requests[i];
113 if (r.length == 0) {
114 r.data.clear();
115 } else if (covers(r.offset, r.length)) {
116 r.data = slice_from_buffer(r.offset, r.length);
117 } else {
118 misses.push_back(Miss{i, r.offset, r.length, clamped_fetch(r.offset, r.length)});
119 }
120 }
121 if (misses.empty())
122 return;
123
124 // Over-fetch each miss to min_req_size, exactly as read() does; otherwise
125 // the cache seeded below would be one request wide and buy nothing.
126 std::vector<RangeRequest> fetches;
127 fetches.reserve(misses.size());
128 for (const auto& m : misses) {
129 fetches.push_back(RangeRequest{m.offset, m.fetch, {}});
130 }
131
132 inner_->read_batch(fetches);
133
134 // Hand back only the bytes each caller asked for -- never the over-fetch.
135 for (std::size_t k = 0; k < misses.size(); ++k) {
136 const auto& m = misses[k];
137 auto& got = fetches[k].data;
138 const std::uint64_t n = std::min<std::uint64_t>(m.want, got.size());
139 requests[m.index].data.assign(got.begin(), got.begin() + static_cast<std::ptrdiff_t>(n));
140 }
141
142 // Seed the single-window cache from the last over-fetched block:
143 // traversal walks forward, so the most recent range is the likeliest hit.
144 buf_offset_ = misses.back().offset;
145 buf_ = std::move(fetches.back().data);
146}
147
148} // namespace fcb
void read_batch(std::vector< RangeRequest > &requests) override
Fill every request, preserving order.
std::vector< std::uint8_t > read(std::uint64_t offset, std::uint64_t length) override
Read length bytes at offset, subject to the contract above.
std::uint64_t total_size() override
Total byte length of the resource.
BufferedRangeReader(std::shared_ptr< RangeReader > inner, std::uint64_t min_req_size)
Every failure the library reports is one of these.
Definition error.hpp:30
FileRangeReader(const std::string &path)
std::uint64_t total_size() override
Total byte length of the resource.
std::vector< std::uint8_t > read(std::uint64_t offset, std::uint64_t length) override
Read length bytes at offset, subject to the contract above.
virtual void read_batch(std::vector< RangeRequest > &requests)
Fill every request, preserving order.
virtual std::vector< std::uint8_t > read(std::uint64_t offset, std::uint64_t length)=0
Read length bytes at offset, subject to the contract above.
std::size_t index
Definition geometry.cpp:70
std::uint64_t range_end(std::uint64_t offset, std::uint64_t length)
End of a range, checked.
Definition checked.hpp:44
std::uint64_t offset
Definition stree.cpp:57
One range in a batched read. data is filled in place by read_batch().
std::uint64_t offset