1 module jaster.stream.range; 2 3 private 4 { 5 import std.range : ElementType, isInputRange; 6 import std.typecons : RefCounted, refCounted, RefCountedAutoInitialize; 7 import std.experimental.allocator : dispose, makeArray; 8 import jaster.stream.core, jaster.stream.util; 9 } 10 11 /// See `byChunkAlloc`. 12 struct ByChunkAllocRange(Alloc) 13 { 14 private static struct Payload 15 { 16 alias _buffer this; 17 private ubyte[] _buffer; 18 private AllocHelper!Alloc _alloc; 19 ~this() 20 { 21 if(this._buffer !is null) 22 this._alloc.alloc.dispose(this._buffer); 23 } 24 } 25 26 alias RC = RefCounted!(Payload, RefCountedAutoInitialize.no); 27 private RC _buffer; 28 private Stream _stream; 29 private ubyte[] _used; 30 private bool _empty; 31 32 static assert(isInputRange!(typeof(this))); 33 static assert(is(ElementType!(typeof(this)) == ubyte[])); 34 35 this(ubyte[] buffer, Stream stream, AllocHelper!Alloc alloc) 36 { 37 this._buffer = refCounted(Payload(buffer, alloc)); 38 this._stream = stream; 39 } 40 41 void popFront() 42 { 43 this._used = this._stream.readToBuffer(this._buffer); 44 if(this._used.length == 0) 45 this._empty = true; 46 } 47 48 @nogc 49 ubyte[] front() nothrow 50 { 51 return this._used; 52 } 53 54 @safe @nogc 55 bool empty() nothrow pure const 56 { 57 return this._empty; 58 } 59 } 60 61 /// See `byChunkGC` 62 struct ByChunkGCRange 63 { 64 private Stream _stream; 65 private ubyte[] _buffer; 66 private ubyte[] _used; 67 private bool _empty; 68 69 static assert(isInputRange!(typeof(this))); 70 static assert(is(ElementType!(typeof(this)) == ubyte[])); 71 72 this(ubyte[] buffer, Stream stream) 73 { 74 this._buffer = buffer; 75 this._stream = stream; 76 } 77 78 void popFront() 79 { 80 this._used = this._stream.readToBuffer(this._buffer); 81 if(this._used.length == 0) 82 this._empty = true; 83 } 84 85 @nogc 86 ubyte[] front() nothrow 87 { 88 return this._used; 89 } 90 91 @safe @nogc 92 bool empty() nothrow pure const 93 { 94 return this._empty; 95 } 96 } 97 98 /++ 99 + Allows access to a stream's data as an input range, chunk-by-chunk. 100 + 101 + The resulting range is only empty once the stream has no more data to read. 102 + 103 + Notes: 104 + The stream's $(B current) position will be used. 105 + 106 + No part of this function or the resulting range uses GC memory. GC memory is only 107 + used if `Alloc` uses the GC, and/or if the given `stream` uses the GC. 108 + 109 + The resulting range uses `std.typecons.RefCounted` for it's internal buffer, so 110 + copying the range has a small overhead. 111 + 112 + Params: 113 + stream = The `Stream` to use. 114 + bufferSize = The size of the buffer, this also determines the max size of each chunk. 115 + alloc = [Non-static allocators only] The allocator instance to use. 116 + ++/ 117 ByChunkAllocRange!Alloc byChunkAlloc(Alloc)(Stream stream, size_t bufferSize) 118 if(AllocHelper!Alloc.IsStatic) 119 { 120 auto r = ByChunkAllocRange!Alloc(Alloc.instance.makeArray!ubyte(bufferSize), stream, AllocHelper!Alloc()); 121 r.popFront(); 122 return r; 123 } 124 /// 125 unittest 126 { 127 import std.experimental.allocator.mallocator : Mallocator; 128 import std.algorithm : all; 129 import jaster.stream.memory : MemoryStreamGC; 130 131 auto stream = new MemoryStreamGC(); 132 foreach(i; 0..129) 133 stream.write([cast(ubyte)i]); 134 135 stream.position = 0; 136 assert(stream.byChunkAlloc!Mallocator(4).all!(arr => arr.length > 0)); 137 } 138 139 /// ditto. 140 ByChunkAllocRange!Alloc byChunkAlloc(Alloc)(Stream stream, size_t bufferSize, Alloc alloc) 141 { 142 auto r = ByChunkAllocRange!Alloc(alloc.makeArray!ubyte(bufferSize), stream, AllocHelper!Alloc(alloc)); 143 r.popFront(); 144 return r; 145 } 146 /// 147 unittest 148 { 149 import std.experimental.allocator.building_blocks : StatsCollector, Options; 150 import std.experimental.allocator.mallocator : Mallocator; 151 import std.algorithm : all; 152 import jaster.stream.memory : MemoryStreamGC; 153 154 alias Alloc = StatsCollector!(Mallocator, Options.all, Options.all); 155 auto alloc = new Alloc(); 156 157 auto stream = new MemoryStreamGC(); 158 foreach(i; 0..129) 159 stream.write([cast(ubyte)i]); 160 161 stream.position = 0; 162 assert(stream.byChunkAlloc!(Alloc*)(4, alloc).all!(arr => arr.length > 0)); 163 assert(alloc.bytesUsed == 0); 164 165 // TODO: Test with more complex range chains, including holding the resulting range in a variable and doing stuff with it manually. 166 // If that all passes with 0 bytes used in the end, and 0 crashes, then it should all be good :) 167 } 168 169 /// Variation of `byChunkAlloc` that uses a GC allocated array, and doesn't use ref counting. 170 ByChunkGCRange byChunkGC(Stream stream, size_t bufferSize) 171 { 172 auto r = ByChunkGCRange(new ubyte[bufferSize], stream); 173 r.popFront(); 174 return r; 175 } 176 /// 177 unittest 178 { 179 import jaster.stream.memory : MemoryStreamGC; 180 181 auto stream = new MemoryStreamGC(); 182 stream.write([0, 1, 2, 3, 4, 5]); 183 stream.position = 0; 184 185 auto range = stream.byChunkGC(4); 186 assert(!range.empty); 187 assert(range.front == [0, 1, 2, 3]); 188 189 range.popFront(); 190 assert(!range.empty); 191 assert(range.front == [4, 5]); 192 193 range.popFront(); 194 assert(range.empty); 195 }