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 }