1 /**
2     Memory Streams
3 
4     Copyright:
5         Copyright © 2023-2025, Kitsunebi Games
6         Copyright © 2023-2025, Inochi2D Project
7     
8     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Authors:   Luna Nielsen
10 */
11 module nulib.io.stream.memstream;
12 import nulib.io.stream;
13 import numem;
14 
15 /**
16     A stream over a memory buffer.
17 
18     MemoryStreams allow to write to a memory backed buffer,
19     said buffer is owned by the memory stream and will grow
20     to accomodate writes.
21 
22     Ownership can be taken from a memory stream using $(D take).
23 
24     Threadsafety:
25         Memory streams are not thread safe objects; to share them
26         between threads you must first finish your stream operations,
27         then take ownership of the memory; you can then copy the result
28         out of thread-local memory.
29     
30     Memory_safety:
31         Generally, memory streams are safe to use as long as you do not
32         mess with their ownership semantics; when passing an existing buffer
33         to the memory stream, make sure that no other variable is referencing
34         the backing buffer. The buffer's memory address may change during
35         resize operations, which can lead to stale pointers outside of the
36         memory stream.
37     
38     See_Also:
39         $(D nulib.io.stream.rw.StreamReader), 
40         $(D nulib.io.stream.rw.StreamWriter)
41 */
42 class MemoryStream : Stream {
43 private:
44 @nogc nothrow @safe:
45     ubyte[] buffer;
46     size_t rptr;
47 
48 public:
49 
50     /**
51         Destructor.
52     */
53     ~this() @trusted {
54         if (buffer.length != 0)
55             buffer = nu_resize(buffer, 0, 1);
56         
57         rptr = 0;
58     }
59 
60     /**
61         Creates a new memory stream with $(D reserved) bytes of
62         memory.
63     
64         Params:
65             reserved = How many bytes to reserve.
66     */
67     this(size_t reserved) @trusted {
68         buffer = nu_resize(buffer, reserved, 1);
69     }
70 
71     /**
72         Takes ownership of an existing buffer.
73         
74         Params:
75             buffer =    The buffer to take ownership of,
76                         the original buffer gets reset to its initial state. 
77     */
78     this(ref ubyte[] buffer) {
79         this.buffer = buffer;
80         buffer = null;
81     }
82 
83     /**
84         Whether the stream can be read from.
85 
86         Returns:
87             $(D true) if you can read data from the stream,
88             $(D false) otherwise.
89     */
90     override @property bool canRead() { return true; }
91 
92     /**
93         Whether the stream can be written to.
94 
95         Returns:
96             $(D true) if you can write data to the stream,
97             $(D false) otherwise.
98     */
99     override @property bool canWrite() { return true; }
100     
101     /**
102         Whether the stream can be seeked.
103 
104         Returns:
105             $(D true) if you can seek through the stream,
106             $(D false) otherwise.
107     */
108     override @property bool canSeek() { return true; }
109 
110     /**
111         Whether the stream can timeout during operations.
112 
113         Returns:
114             $(D true) if the stream may time out during operations,
115             $(D false) otherwise.
116     */
117     override @property bool canTimeout() { return false; }
118 
119     /**
120         Whether the stream can be flushed to disk.
121 
122         Returns:
123             $(D true) if the stream may be flushed,
124             $(D false) otherwise.
125     */
126     override @property bool canFlush() { return false; }
127 
128     /**
129         Length of the stream.
130 
131         Returns:
132             Length of the stream, or $(D -1) if the length is unknown.
133     */
134     override @property ptrdiff_t length() { return buffer.length; }
135 
136     /**
137         Position in stream
138     
139         Returns
140             Position in the stream, or $(D -1) if the position is unknown.
141     */
142     override @property ptrdiff_t tell() { return rptr; }
143 
144     /**
145         Timeout in milliseconds before a read operation will fail.
146 
147         Returns
148             A timeout in milliseconds, or $(D 0) if there's no timeout.
149     */
150     override @property int readTimeout() { return 0; }
151 
152     /**
153         Timeout in milliseconds before a write operation will fail.
154 
155         Returns
156             A timeout in milliseconds, or $(D 0) if there's no timeout.
157     */
158     override @property int writeTimeout() { return 0; }
159 
160     /**
161         Clears all buffers of the stream and causes data to be written to the underlying device.
162     
163         Returns:
164             $(D true) if the flush operation succeeded,
165             $(D false) otherwise.
166     */
167     override bool flush() { return false; }
168 
169     /**
170         Sets the reading position within the stream
171 
172         Returns
173             The new position in the stream, or a $(D StreamError) if the 
174             seek operation failed.
175         
176         See_Also:
177             $(D StreamError)
178     */
179     override
180     long seek(ptrdiff_t offset, SeekOrigin origin = SeekOrigin.start) {
181         final switch(origin) {
182             case SeekOrigin.start:
183                 ptrdiff_t newpos = cast(ptrdiff_t)offset;
184                 if (newpos > buffer.length || newpos < 0)
185                     return STREAM_ERROR_OUT_OF_RANGE;
186                 
187                 rptr = newpos;
188                 return cast(long)rptr;
189             case SeekOrigin.relative:
190                 ptrdiff_t newpos = cast(ptrdiff_t)rptr+offset;
191                 if (newpos > buffer.length || newpos < 0)
192                     return STREAM_ERROR_OUT_OF_RANGE;
193                     
194                 rptr = newpos;
195                 return cast(long)rptr;
196             case SeekOrigin.end:
197                 ptrdiff_t newpos = cast(ptrdiff_t)buffer.length-offset;
198                 if (newpos > buffer.length || newpos < 0)
199                     return STREAM_ERROR_OUT_OF_RANGE;
200 
201                 rptr = newpos;
202                 return cast(long)rptr;
203         }
204     }
205 
206     /**
207         Closes the stream.
208     */
209     override
210     void close() @trusted {
211         if (buffer)
212             buffer = nu_resize(buffer, 0, 1);
213     }
214 
215     /**
216         Reads bytes from the specified stream in to the specified buffer
217         
218         Notes:
219             The position and length to read is specified by the slice of `buffer`.  
220             Use slicing operation to specify a range to read to.
221 
222         Returns:
223             The amount of bytes read from the stream, 
224             or a $(D StreamError).
225         
226         See_Also:
227             $(D StreamError)
228     */
229     override
230     ptrdiff_t read(ubyte[] buffer) {
231         ptrdiff_t start = rptr;
232         ptrdiff_t end = rptr+buffer.length;
233 
234         // Snap to end
235         if (end >= this.buffer.length)
236             end = this.buffer.length;
237         
238         // EOF
239         if (start == end)
240             return STREAM_ERROR_EOF;
241         
242         // Read bytes into provided buffer
243         rptr = end;
244         buffer[0..end-start] = this.buffer[start..end];
245         return end-start;
246     }
247 
248     /**
249         Writes bytes from the specified buffer in to the stream
250 
251         Notes
252             The position and length to write is specified by the slice of `buffer`.  
253             Use slicing operation to specify a range to write from.
254 
255         Returns:
256             The amount of bytes read from the stream, 
257             or a $(D StreamError).
258         
259         See_Also:
260             $(D StreamError)
261     */
262     override
263     ptrdiff_t write(ubyte[] buffer) @trusted {
264         try {
265             ptrdiff_t start = rptr;
266             ptrdiff_t end = rptr+buffer.length;
267 
268             // Grow buffer
269             this.buffer = nu_resize(this.buffer, end+1, 1);
270             
271             // Read bytes into the buffer
272             rptr = end;
273             this.buffer[start..end] = buffer[0..end-start];
274             return end-start;
275         } catch(Exception ex) {
276             if (NuException nex = cast(NuException)ex)
277                 nex.free();
278             
279             return STREAM_ERROR_INVALID_STATE;
280         }
281     }
282 
283     /**
284         Takes ownership of the memory in the memory stream,
285         causing the memory stream to reset to its initial state.
286 
287         Returns:
288             The slice that used to belong to the memory stream,
289             the owner of the slice is responsible for freeing it.
290     */
291     final
292     ubyte[] take() {
293         ubyte[] result = buffer;
294 
295         buffer = null;
296         rptr = 0;
297         return result;
298     }
299 }