Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
ToSeekableInputStream.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
7
8namespace Stroika::Foundation::Streams::ToSeekableInputStream {
9
10 /*
11 ********************************************************************************
12 ****************** Streams::ToSeekableInputStream::New *************************
13 ********************************************************************************
14 */
15 template <typename ELEMENT_TYPE>
16 auto New (const Ptr<ELEMENT_TYPE>& in) -> Ptr<ELEMENT_TYPE>
17 {
19 struct seekableWrapper final : InputStreamDelegationHelper<ELEMENT_TYPE> {
20
22 seekableWrapper (const Ptr<ELEMENT_TYPE>& in)
23 : inherited{in}
24 , fOffset_{in.GetOffset ()}
25 , fCacheBaseOffset_{fOffset_}
26 {
27 Assert (not this->fRealIn.IsSeekable ()); // just to document that's why we're here!
28 }
29 virtual bool IsSeekable () const override
30 {
31 return true;
32 }
33 virtual optional<size_t> AvailableToRead () override
34 {
35 SeekOffsetType cacheEnd = fCacheBaseOffset_ + fCachedData_.size ();
36 if (fCacheBaseOffset_ <= fOffset_ and fOffset_ < cacheEnd) [[unlikely]] {
37 Ensure (cacheEnd > fOffset_);
38 return static_cast<size_t> (cacheEnd - fOffset_);
39 }
40 return this->fRealIn.AvailableToRead ();
41 }
42 virtual optional<SeekOffsetType> RemainingLength () override
43 {
44 auto baseRemaining = this->fRealIn.RemainingLength ();
45 if (baseRemaining) {
46 SeekOffsetType cacheEnd = fCacheBaseOffset_ + fCachedData_.size ();
47 Assert (fOffset_ <= cacheEnd);
48 baseRemaining = *baseRemaining + (cacheEnd - fOffset_); // if we have some cached data past current seek offset, add it too
49 }
50 return baseRemaining;
51 }
52 virtual optional<span<ELEMENT_TYPE>> Read (span<ELEMENT_TYPE> intoBuffer, [[maybe_unused]] NoDataAvailableHandling blockFlag) override
53 {
54 Require (not intoBuffer.empty ());
55 AssertExternallySynchronizedChecker::WriteContext declareContext{fThisAssertExternallySynchronized_};
56 /*
57 * See if the request can be serviced from the cached data. If so, do so.
58 */
59 SeekOffsetType cacheEnd = fCacheBaseOffset_ + fCachedData_.size ();
60 if (fCacheBaseOffset_ <= fOffset_ and fOffset_ < cacheEnd) [[unlikely]] {
61 // copyCnt: both of these are upper bounds (elements cached from fOffset_ forward, and room in
62 // intoBuffer); so take the lesser
63 size_t copyCnt = min<size_t> (static_cast<size_t> (cacheEnd - fOffset_), intoBuffer.size ());
64 auto r = Memory::CopyBytes (span{fCachedData_}.subspan (static_cast<size_t> (fOffset_ - fCacheBaseOffset_), copyCnt), intoBuffer);
65 fOffset_ += copyCnt;
66 return r;
67 }
68 /*
69 * If it cannot, accumulate any read data into the cache so it can be re-read.
70 */
71 Assert (fOffset_ == inherited::fRealIn.GetOffset ()); // could be bug with this code or somebody else playing fast and loose, but use assert
72 auto r = this->fRealIn.ReadOrThrow (intoBuffer, blockFlag);
73 // cache it, and update our data structures; note easy, cuz fRealIn must be at matching seek offset
74 fCachedData_.push_back (r);
75 fOffset_ += r.size ();
76 return r;
77 }
78 virtual SeekOffsetType GetReadOffset () const override
79 {
80 AssertExternallySynchronizedChecker::ReadContext declareContext{fThisAssertExternallySynchronized_};
81 return fOffset_;
82 }
83 virtual SeekOffsetType SeekRead (Whence whence, SignedSeekOffsetType offset) override
84 {
85 static const auto kException_ = range_error{"seek"};
86 AssertExternallySynchronizedChecker::WriteContext declareContext{fThisAssertExternallySynchronized_};
87 switch (whence) {
88 case Whence::eFromStart: {
89 if (offset < 0) [[unlikely]] {
90 Execution::Throw (kException_);
91 }
92 SeekOffsetType newOffset = static_cast<SeekOffsetType> (offset);
93 Require (newOffset >= fCacheBaseOffset_); // as documented in New() code that creates this, we cannot seek back past where we started from
94 SeekOffsetType cacheEnd = fCacheBaseOffset_ + fCachedData_.size ();
95 while (newOffset > cacheEnd) {
96 // we must read and buffer/accumulate in the cache; note - because of how this code works, the
97 // fRealIn is always seeked to the end cached data.
98 byte someBuf[1024];
99 auto r = this->fRealIn.ReadBlocking (span{someBuf});
100 if (r.empty ()) [[unlikely]] {
101 // upstream is exhausted, so newOffset is past the end of the stream: no
102 // further data can ever arrive, and looping again would spin forever
103 Execution::Throw (kException_);
104 }
105 fCachedData_.push_back (r); // nb: an exception in this copy would cause fRealIn offset to be out of sync, but not sure what todo about it
106 cacheEnd = fCacheBaseOffset_ + fCachedData_.size ();
107 }
108 Assert (newOffset <= cacheEnd);
109 fOffset_ = newOffset;
110 return newOffset;
111 } break;
112 case eFromCurrent: {
113 return this->SeekRead (eFromStart, fOffset_ + offset);
114 } break;
115 case eFromEnd: {
116 if (auto remainingLength = this->RemainingLength ()) {
117 return this->SeekRead (eFromStart, fOffset_ + *remainingLength + offset);
118 }
119 else {
120 // implies seeking (fRealIn) to the end, and so reading everything, and then performing the desired seek
121 while (true) {
122 byte someBuf[8 * 1024];
123 auto r = this->fRealIn.ReadBlocking (span{someBuf});
124 fCachedData_.push_back (r); // nb: an exception in this copy would cause fRealIn offset to be out of sync, but not sure what todo about it
125 if (r.empty ()) {
126 break;
127 }
128 }
129 SeekOffsetType realEnd = fCacheBaseOffset_ + fCachedData_.size ();
130 Assert (realEnd == this->fRealIn.GetOffset ());
131 return this->SeekRead (eFromStart, realEnd + offset);
132 }
133 } break;
134 default:
136 return 0;
137 }
138 }
139
140 private:
142 SeekOffsetType fOffset_{0}; // this rep's seek offset (as oppsed to that in fRealIn)
143 SeekOffsetType fCacheBaseOffset_{0};
145 };
146 if (in.IsSeekable ()) {
147 return in;
148 }
149 else {
150 return Ptr<ELEMENT_TYPE>{Memory::MakeSharedPtr<seekableWrapper> (in)};
151 }
152 return in;
153 }
154
155}
#define RequireNotReached()
Definition Assertions.h:386
#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
[[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this on things where its not b...
Definition StdCompat.h:443
NoDataAvailableHandling
If eDontBlock passed to most Stream APIs, then when the code would do a blocking read,...
Definition Stream.h:90
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
InputStream<>::Ptr is Smart pointer (with abstract Rep) class defining the interface to reading from ...
nonvirtual SeekOffsetType GetOffset() const