Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Stream.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Streams_Stream_h_
5#define _Stroika_Foundation_Streams_Stream_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <memory>
10
11#include "Stroika/Foundation/Common/Common.h"
14#include "Stroika/Foundation/Memory/Common.h"
15
16/**
17 * \file
18 *
19 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
20 */
21
22namespace Stroika::Foundation::Streams {
23
24 /**
25 * When seeking, you can seek (offset) from the start (typical/start) or from the end of the
26 * stream, or from the current position)
27 */
28 enum class Whence : uint8_t {
29 eFromStart,
30 eFromCurrent,
31 eFromEnd,
32
33 Stroika_Define_Enum_Bounds (eFromStart, eFromEnd)
34 };
35
36 using Whence::eFromCurrent;
37 using Whence::eFromEnd;
38 using Whence::eFromStart;
39
40 /**
41 * SeekOffsetType is unsigned, normally, because for most purposes its zero based.
42 * @see SignedSeekOffsetType.
43 *
44 * \note We use a 64-bit integer here, because streaming >4GB files is common, even in devices so tiny as a cellphone.
45 * and no larger because its inconceivable to store that much data anytime soon.
46 */
47 using SeekOffsetType = uint64_t;
48
49 /**
50 * SignedSeekOffsetType is signed variant of SeekOffsetType - used to specify a seek
51 * offset which can sometimes negative (backwards).
52 *
53 * @see SignedSeekOffsetType.
54 */
55 using SignedSeekOffsetType = int64_t;
56
57 /**
58 * Used in some subclasses as a flag to indicate stream seekability.
59 *
60 * \note Common::DefaultNames<> supported
61 */
62 enum class SeekableFlag {
63 eNotSeekable,
64 eSeekable,
65
66 Stroika_Define_Enum_Bounds (eNotSeekable, eSeekable)
67 };
68 using SeekableFlag::eNotSeekable;
69 using SeekableFlag::eSeekable;
70
71 template <typename ELEMENT_TYPE>
72 class IRep;
73
74 /**
75 * \brief If eDontBlock passed to most Stream APIs, then when the code would do a blocking read, instead it will return nullopt or throw EWouldBlock
76 *
77 * \note the idea of 'blocking' is somewhat vague. In the context of streams, it simply refers to behavior of reading at least one element (or writing all requested elements).
78 *
79 * For reading, the low level Read () API (that everything is built off of) - will take a span<Element> and not return until it has at least one read. Failure to read
80 * more is nearly meaningless, and is fine. If it is KNOWN you are at EOF - end of stream - then and only then can the Read return 0 elements read.
81 *
82 * But if you cannot read at least one (say reading from a socket and no data is avaiable), then Read () can return the special value nullopt.
83 *
84 * For Writing - the Stream Write API (for simplicity sake) doesn't allow for incomplete writes. It blocks until all elements are written.
85 *
86 * \note - currently OutputStream classes don't use NoDataAvailableHandling (as of Stroika v3.0d15). Future versions may allow for non-blocking writes, but
87 * pretty unclear how to handle that (ie do we allow partial writes and if so, how to track - PITA). For now - simpler to just always block on writes.
88 * -- LGP 2025-01-29
89 */
91 /**
92 * Don't block EITHER translates into a THROW of EWouldBlock or returning of nullopt, depending
93 * on the API where this flag is used.
94 */
96
97 /**
98 * Note - even when blocking, Stroika APIs respect thread cancelation
99 */
101
102 /**
103 * By far safest, simplest approach, but sometimes unacceptable for various reasons, typically involving multiprocessing, and I/O.
104 */
105 eDEFAULT [[deprecated ("Since Stroika v3.0d15 - this value deprecated - use eBlockIfNoDataAvailable or another API")]] = eBlockIfNoDataAvailable
106 };
107 using NoDataAvailableHandling::eBlockIfNoDataAvailable;
108 using NoDataAvailableHandling::eDontBlock;
109
110 /**
111 * \em Design Overview
112 * o A Streams is a sequence of data elements made available over time. These elements
113 * are typically 'Bytes' - or 'Characters' - but can be any copyable type.
114 *
115 * o Streams are created, and then handled ONLY through smart pointers (Ptr). Assigning Streams
116 * merely copies the 'smart pointer' to that same underlying stream. This means that offsets
117 * and underlying data, are all shared.
118 *
119 * o Streams have two parallel hierarchies, which mirror one another, of smart pointers and related
120 * 'virtual rep' objects which provide the API which implementers override.
121 *
122 * o Seek Offsets are in elements of the kind of stream (e.g in Bytes for a Stream<byte>, and
123 * in Characters for a Stream<Character>).
124 *
125 * o Two important subclasses of Stream<> are InputStream::Ptr<> (for reading) and OutputStream::Ptr<> for
126 * writing. So that each can maintain its own intrinsic current offset (separate seek offset
127 * for reading and writing) in mixed (input/output) streams, the actual offset APIs and
128 * logic are in those subclasses.
129 *
130 * o Stream APIs are intrinsically blocking (except see InputStream::Ptr<>::ReadNonBlocking() and APIs that take optional non-blocking parameter).
131 * This makes working with them much simpler, since code using Streams doesn't need to be written to handle
132 * both blocking and non-blocking behavior, and be surprised when a 'mode' is set on a stream making it non-blocking.
133 *
134 * o Relationship with std iostream:
135 * Stroika streams are in some ways similar to iostreams. They are interoperable with iostreams
136 * through the Streams::iostream::(InputStreamFromStdIStream, InputStreamToStdIStream,
137 * OutputStreamFromStdIStream, OutputStreamToStdIStream) classes.
138 *
139 * o Stroika Streams are much easier to create (just a few intuitive virtual methods
140 * to override in the rep, whereas creating your own kind of basic_istream/ostream is much harder).
141 *
142 * o Stroika streams are unrelated to formatting of text.
143 *
144 * o Stroika Streams are much easier to use and understand, with better internal error checking,
145 * and simpler, more consistent naming for offsets/seeking, and seekability.
146 *
147 * o Stroika supports non-seekable streams (needed for things like sockets, and certain special files, like
148 * Linux procfs files).
149 *
150 * o Due to more orthogonal API, easier to provide intuitive simple adapters mapping one kind of stream
151 * to another (such as binary streams to streams of text, like the .net TextReader).
152 *
153 * o Stroika Streams are divided into 'Smart Pointer' objects (all you interact with) and the underlying Stream data (Rep).
154 * Copying an iostream is generally not possible with STL, but with Stroika, it copies a reference (smart pointer) to the underlying
155 * stream.
156 *
157 * \brief A Streams::Ptr<ELEMENT_TYPE> is a smart-pointer to a stream of elements of type T.
158 *
159 * Stream<T>::Ptr is seldom used directly. Much more commonly, you will want InputStream::Ptr<T>, or OutputStream::Ptr<T>.
160 *
161 * \note Since Streams::Ptr<ELEMENT_TYPE> is a smart pointer, the constness of the methods depends on whether they modify the smart pointer itself, not
162 * the underlying thread object.
163 *
164 * \note \em Thread-Safety <a href="Thread-Safety.md#C++-Standard-Thread-Safety-For-Envelope-But-Ambiguous-Thread-Safety-For-Letter">C++-Standard-Thread-Safety-For-Envelope-But-Ambiguous-Thread-Safety-For-Letter/a>
165 *
166 * \note <a href="Design-Overview.md#Comparisons">Comparisons</a>:
167 * o Support operator==(nullptr_t) only
168 */
169 template <typename ELEMENT_TYPE>
170 class Ptr {
171 public:
172 /**
173 * defaults to nullptr
174 */
175 Ptr () noexcept = default;
176 Ptr (const Ptr&) noexcept = default;
177 Ptr (Ptr&&) noexcept = default;
178 Ptr (nullptr_t) noexcept;
179 Ptr (const shared_ptr<IRep<ELEMENT_TYPE>>& rep) noexcept;
180
181 public:
182 /**
183 */
184 nonvirtual Ptr& operator= (const Ptr&) = default;
185 nonvirtual Ptr& operator= (Ptr&&) noexcept = default;
186
187 public:
188 /**
189 * reset () doesn't clear the data in the stream, or close the stream, but unreferences the Stream
190 * smart pointer. Only if this Stream smart pointer is the last reference to the underlying stream
191 * data does this reset () close the underlying stream.
192 */
193 nonvirtual void reset () noexcept;
194
195 public:
196 /**
197 * \brief return true iff stream ptr is nullptr
198 *
199 * @see reset()
200 */
201 nonvirtual bool operator== (nullptr_t) const;
202
203 public:
204 /**
205 * \brief return true iff *this != nullptr
206 */
207 nonvirtual explicit operator bool () const;
208
209 public:
210 /**
211 * \brief access to underlying stream smart pointer
212 */
213 nonvirtual shared_ptr<IRep<ELEMENT_TYPE>> GetSharedRep () const;
214
215 public:
216 /**
217 * \pre *this != nullptr
218 */
219 nonvirtual const IRep<ELEMENT_TYPE>& GetRepConstRef () const;
220
221 public:
222 /**
223 * \pre *this != nullptr
224 */
225 nonvirtual IRep<ELEMENT_TYPE>& GetRepRWRef () const;
226
227 public:
228 /**
229 * \brief Returns true iff this object was constructed with a seekable input stream rep.
230 *
231 * Returns true iff this object was constructed with a seekable input stream rep. Note -
232 * seekability cannot change over the lifetime of an object.
233 *
234 * @see GetSeekability
235 */
236 nonvirtual bool IsSeekable () const;
237
238 public:
239 [[deprecated ("Since Stroika v3.0d5 use IsSeekable")]] SeekableFlag GetSeekability () const
240 {
241 return IsSeekable () ? SeekableFlag::eSeekable : SeekableFlag::eNotSeekable;
242 }
243
244 protected:
245 [[no_unique_address]] Debug::AssertExternallySynchronizedMutex _fThisAssertExternallySynchronized; // refers to PTR not REP
246
247 private:
248 shared_ptr<IRep<ELEMENT_TYPE>> fRep_;
249
250 private:
251 bool fSeekable_{false};
252 };
253
254 /**
255 * \note \em Thread-Safety <a href="Thread-Safety.md#Thread-Safety-Rules-Depends-On-Subtype">Thread-Safety-Rules-Depends-On-Subtype/a>
256 */
257 template <typename ELEMENT_TYPE>
258 class IRep {
259 public:
260 using ElementType = ELEMENT_TYPE;
261
262 public:
263 IRep () = default;
264 IRep (const IRep&) = delete;
265
266 public:
267 virtual ~IRep () = default;
268
269 public:
270 nonvirtual IRep& operator= (const IRep&) = delete;
271
272 public:
273 /**
274 * cannot change value - called once and value cached
275 */
276 virtual bool IsSeekable () const = 0;
277 };
278
279}
280
281/*
282 ********************************************************************************
283 ***************************** Implementation Details ***************************
284 ********************************************************************************
285 */
286#include "Stream.inl"
287
288#endif /*_Stroika_Foundation_Streams_Stream_h_*/
#define Stroika_Define_Enum_Bounds(FIRST_ITEM, LAST_ITEM)
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...
virtual bool IsSeekable() const =0
A Streams::Ptr<ELEMENT_TYPE> is a smart-pointer to a stream of elements of type T.
Definition Stream.h:170
nonvirtual void reset() noexcept
Definition Stream.inl:50
nonvirtual IRep< ELEMENT_TYPE > & GetRepRWRef() const
Definition Stream.inl:37
nonvirtual shared_ptr< IRep< ELEMENT_TYPE > > GetSharedRep() const
access to underlying stream smart pointer
Definition Stream.inl:24
nonvirtual const IRep< ELEMENT_TYPE > & GetRepConstRef() const
Definition Stream.inl:30
nonvirtual bool IsSeekable() const
Returns true iff this object was constructed with a seekable input stream rep.
Definition Stream.inl:44