1 /**
2 Copyright: Copyright (c) 2016-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 module voxelman.container.chunkedbuffer;
7 
8 import std.experimental.allocator.gc_allocator;
9 alias allocator = GCAllocator.instance;
10 import std.stdio;
11 import std.algorithm : min, equal, swap;
12 import std.range;
13 
14 import voxelman.container.buffer;
15 import voxelman.container.chunkedrange;
16 import voxelman.math : nextPOT, divCeil;
17 
18 struct ChunkedBuffer(T, size_t pageSize = 4096)
19 {
20 	enum pageBytes = pageSize*T.sizeof;
21 
22 	Buffer!(T*) chunkBuffer; // array of chunks
23 
24 	// Must be kept private since it can be used to check for avaliable space
25 	// when used as output range
26 	size_t length;
27 	// can be non-zero after removeFront
28 	private size_t firstChunkDataPos;
29 	private size_t usedLength() const @property { return firstChunkDataPos + length; }
30 
31 	alias opDollar = length;
32 
33 	void put(T[] items ...)
34 	{
35 		reserveBack(items.length);
36 
37 		size_t firstChunkIndex = usedLength / pageSize;
38 		size_t firstChunkPos = usedLength % pageSize;
39 		size_t firstChunkSpace = pageSize - firstChunkPos;
40 		T*[] chunks = chunkBuffer.data;
41 
42 		length += items.length;
43 
44 		size_t firstChunkItems = min(firstChunkSpace, items.length);
45 		auto firstChunk = chunks[firstChunkIndex];
46 		firstChunk[firstChunkPos..firstChunkPos+firstChunkItems] = items[0..firstChunkItems];
47 		items = items[firstChunkItems..$];
48 
49 		size_t chunkIndex = firstChunkIndex + 1;
50 		while(items.length > 0)
51 		{
52 			size_t numItemsToWrite = min(pageSize, items.length);
53 			chunks[chunkIndex++][0..numItemsToWrite] = items[0..numItemsToWrite];
54 			items = items[numItemsToWrite..$];
55 		}
56 	}
57 
58 	void put(R)(R itemRange)
59 	{
60 		foreach(item; itemRange)
61 			put(item);
62 	}
63 
64 	alias putBack = put;
65 	alias push = put;
66 
67 	void removeFront(size_t howMany = 1)
68 	{
69 		assert(howMany <= length);
70 		length = length - howMany;
71 
72 		if (length == 0)
73 		{
74 			firstChunkDataPos = 0;
75 			return;
76 		}
77 
78 		size_t firstUsedItem = firstChunkDataPos + howMany;
79 		size_t lastUsedItem = firstUsedItem + length - 1;
80 
81 		size_t firstUsedChunk = firstUsedItem / pageSize;
82 		size_t lastUsedChunk = lastUsedItem / pageSize;
83 
84 		firstChunkDataPos = firstUsedItem % pageSize;
85 
86 		if (firstUsedChunk == 0) return;
87 
88 		T*[] chunks = chunkBuffer.data;
89 
90 		// move chunks to front
91 		size_t i;
92 		foreach (chunkIndex; firstUsedChunk..lastUsedChunk+1)
93 		{
94 			swap(chunks[i++], chunks[chunkIndex]);
95 		}
96 	}
97 	alias popFront = removeFront;
98 
99 	void removeBack(size_t howMany = 1)
100 	{
101 		assert(howMany <= length);
102 		length -= howMany;
103 	}
104 	alias pop = removeBack;
105 
106 	void clear() nothrow
107 	{
108 		length = 0;
109 		firstChunkDataPos = 0;
110 	}
111 
112 	size_t capacity() const @property
113 	{
114 		return chunkBuffer.data.length * pageSize;
115 	}
116 
117 	size_t reserved() const @property
118 	{
119 		return capacity - length;
120 	}
121 
122 	size_t reservedBack() const @property
123 	{
124 		return capacity - (firstChunkDataPos + length);
125 	}
126 
127 	void reserveBack(size_t items)
128 	{
129 		if (reservedBack < items)
130 		{
131 			// alloc chunks
132 			size_t numExtraChunks = divCeil(items, pageSize);
133 			chunkBuffer.reserve(numExtraChunks);
134 
135 			foreach(_; 0..numExtraChunks)
136 			{
137 				void* ptr = allocator.allocate(pageBytes).ptr;
138 				chunkBuffer.put(cast(T*)ptr);
139 				import core.memory;
140 				GC.addRange(ptr, pageBytes, typeid(T));
141 			}
142 		}
143 	}
144 	alias reserve = reserveBack;
145 
146 	bool empty() { return length == 0; }
147 
148 	ref T front() { return this[0]; }
149 	ref T back() { return this[$-1]; }
150 	alias top = back;
151 
152 	ref T opIndex(size_t at)
153 	{
154 		size_t chunkIndex = (firstChunkDataPos + at) / pageSize;
155 		size_t chunkPos = (firstChunkDataPos + at) % pageSize;
156 		return chunkBuffer.data[chunkIndex][chunkPos];
157 	}
158 
159 	alias ChunkRange = ChunkedBufferChunks!(T, pageSize);
160 	alias ItemRange = ChunkedBufferItemRange!(T, pageSize);
161 
162 	auto byChunk()
163 	{
164 		size_t numChunksWithData = divCeil(length, pageSize);
165 		return ChunkRange(chunkBuffer.data[0..numChunksWithData], length, firstChunkDataPos);
166 	}
167 
168 	auto opSlice()
169 	{
170 		return ItemRange(&this, 0, length);
171 	}
172 
173 	auto opSlice(size_t from, size_t to)
174 	{
175 		return this[][from..to];
176 	}
177 }
178 
179 struct ChunkedBufferChunks(T, size_t pageSize)
180 {
181 	private T** chunks;
182 	size_t chunksLeft; // includes front chunk
183 	size_t itemsLeft; // does not include items inside front
184 	T[] front;
185 
186 	size_t length() { return chunksLeft; }
187 	alias opDollar = length;
188 
189 	bool empty() { return front.length == 0; }
190 	auto save() { return this; }
191 
192 	this(T*[] chunks, size_t length, size_t firstChunkOffset)
193 	{
194 		itemsLeft = length;
195 		chunksLeft = chunks.length;
196 		this.chunks = chunks.ptr;
197 		if (length)
198 		{
199 			size_t pageItems = min(itemsLeft, pageSize-firstChunkOffset);
200 			size_t end = firstChunkOffset + pageItems;
201 			front = (*this.chunks++)[firstChunkOffset..end];
202 			itemsLeft -= front.length; // may cause itemsLeft to become 0
203 		}
204 	}
205 
206 	void popFront()
207 	{
208 		size_t pageItems = min(itemsLeft, pageSize);
209 		if (pageItems == 0)
210 		{
211 			front = null;
212 			return;
213 		}
214 
215 		front = (*chunks++)[0..pageItems];
216 		--chunksLeft;
217 		itemsLeft -= front.length; // may cause itemsLeft to become 0
218 	}
219 
220 	ChunkedRange!T toChunkedRange()
221 	{
222 		return ChunkedRange!T(front, itemsLeft, chunks, null, &ChunkedRange_popFront!(T, pageSize));
223 	}
224 }
225 
226 struct ChunkedBufferItemRange(T, size_t pageSize)
227 {
228 	private ChunkedBuffer!(T, pageSize)* buf;
229 	size_t start;
230 	size_t length;
231 
232 	alias opDollar = length;
233 	bool empty() { return length == 0; }
234 	ref T front() { return (*buf)[start]; }
235 	ref T back() { return (*buf)[start+length-1]; }
236 	void popFront() { ++start; --length; }
237 	void popBack() { --length; }
238 	auto save() { return this; }
239 	ref T opIndex(size_t at) { return (*buf)[start + at]; }
240 
241 	auto opSlice(size_t from, size_t to)
242 	{
243 		if (from != to)
244 		{
245 			assert(from < length);
246 			assert(to <= length);
247 		}
248 		size_t len = to - from;
249 		return ChunkedBufferItemRange(buf, start+from, len);
250 	}
251 
252 	ChunkedRange!T toChunkedRange()
253 	{
254 		size_t firstItem = start + buf.firstChunkDataPos;
255 		size_t firstChunk = firstItem / pageSize;
256 		size_t chunkPos = firstItem % pageSize;
257 		size_t itemsLeft = length;
258 
259 		size_t chunkLength = min(itemsLeft, pageSize-chunkPos);
260 		if (chunkLength == 0) return ChunkedRange!T(null, 0, null, null, &ChunkedRange_popFront!(T, pageSize));
261 		T** firstChunkPtr = buf.chunkBuffer.data.ptr + firstChunk;
262 		auto front = firstChunkPtr[0][chunkPos..chunkPos+chunkLength];
263 		itemsLeft -= front.length;
264 
265 		return ChunkedRange!T(front, itemsLeft, firstChunkPtr+1, null, &ChunkedRange_popFront!(T, pageSize));
266 	}
267 }
268 
269 private static void ChunkedRange_popFront(T, size_t pageSize)(
270 	ref T[] front,
271 	ref size_t itemsLeft,
272 	ref void* chunks,
273 	ref void* unused)
274 {
275 	if (itemsLeft == 0)
276 	{
277 		front = null;
278 		return;
279 	}
280 
281 	size_t chunkLength = min(itemsLeft, pageSize);
282 	T** nextChunkStart = cast(T**)chunks;
283 	front = nextChunkStart[0][0..chunkLength];
284 	chunks = nextChunkStart+1;
285 
286 	itemsLeft -= chunkLength;
287 }
288 
289 private alias ItemRangeT = ChunkedBufferItemRange!(int, 32);
290 static assert(isInputRange!ItemRangeT);
291 static assert(hasLength!ItemRangeT);
292 static assert(hasSlicing!ItemRangeT);
293 static assert(hasAssignableElements!ItemRangeT);
294 static assert(isRandomAccessRange!ItemRangeT);
295 static assert(isBidirectionalRange!ItemRangeT);
296 static assert(isForwardRange!ItemRangeT);
297 static assert(isOutputRange!(ChunkedBuffer!int, int));
298 
299 unittest
300 {
301 	ChunkedBuffer!(int, 16) queue;
302 	foreach(i; 0..16)
303 		queue.push(0);
304 	foreach(i; 0..100)
305 	{
306 		queue.popFront;
307 		queue.push(i);
308 	}
309 }
310 
311 unittest
312 {
313 	ChunkedBuffer!(int, 16) queue;
314 		queue.push(0);
315 		queue.popFront;
316 	foreach(i; 0..100)
317 	{
318 		queue.push(i);
319 	}
320 }
321 
322 unittest
323 {
324 	ChunkedBuffer!int queue;
325 	queue.push(39);
326 	assert(queue.front == 39);
327 	assert(queue.length == 1);
328 	assert(queue[].equal([39]));
329 	queue.popFront;
330 	assert(queue.length == 0);
331 	queue.push(44);
332 	assert(queue.front == 44);
333 	assert(queue.length == 1);
334 	assert(queue[].equal([44]));
335 	queue.push(45);
336 	assert(queue.front == 44);
337 	assert(queue.length == 2);
338 	assert(queue[].equal([44, 45]));
339 }
340 
341 unittest
342 {
343 	ChunkedBuffer!(int, 1) buf;
344 	buf.put(1, 2);
345 	assert(buf[].equal([1, 2]));
346 	assert(buf.byChunk.equal([[1], [2]]));
347 	assert(buf.length == 2);
348 	assert(buf.capacity == 2);
349 	assert(buf.reserved == 0);
350 }
351 
352 unittest
353 {
354 	ChunkedBuffer!(int, 2) buf;
355 	buf.put(1, 2, 3, 4);
356 	assert(buf[].equal([1, 2, 3, 4]));
357 	assert(buf.byChunk.equal([[1, 2], [3, 4]]));
358 	assert(buf.byChunk.length == 2);
359 	assert(buf.length == 4);
360 	assert(buf.capacity == 4);
361 	assert(buf.reserved == 0);
362 
363 	assert(buf[1..3].equal([2, 3]));
364 
365 	buf.put(5);
366 	assert(buf[].equal([1, 2, 3, 4, 5]));
367 	assert(buf.byChunk.equal([[1, 2], [3, 4], [5]]));
368 	assert(buf.byChunk.length == 3);
369 	assert(buf.length == 5);
370 	assert(buf.capacity == 6);
371 	assert(buf.reserved == 1);
372 }
373 
374 // test removeBack
375 unittest
376 {
377 	ChunkedBuffer!(int, 2) buf;
378 	buf.put(1, 2, 3, 4);
379 
380 	buf.removeBack(3);
381 
382 	assert(buf[].equal([1]));
383 	assert(buf.byChunk.equal([[1]]));
384 	assert(buf.byChunk.length == 1);
385 	assert(buf.length == 1);
386 	assert(buf.capacity == 4);
387 	assert(buf.reserved == 3);
388 }
389 
390 // test removeFront
391 unittest
392 {
393 	ChunkedBuffer!(int, 2) buf;
394 	buf.put(1, 2, 3, 4);
395 
396 	buf.removeFront(3);
397 
398 	assert(buf[].equal([4]));
399 	assert(buf.byChunk.equal([[4]]));
400 	assert(buf.byChunk.length == 1);
401 	assert(buf.length == 1);
402 	assert(buf.capacity == 4);
403 	assert(buf.reserved == 3);
404 }
405 
406 // test assignable front, back and [i]
407 unittest
408 {
409 	ChunkedBuffer!(int, 2) buf;
410 	buf.put(1, 2, 3, 4);
411 
412 	assert(buf.front == 1);
413 	assert(buf.back == 4);
414 
415 	buf.front = 0;
416 	assert(buf.front == 0);
417 
418 	buf.back = 0;
419 	assert(buf.back == 0);
420 
421 	buf[1] = 0;
422 	assert(buf[1] == 0);
423 
424 	assert(buf[].equal([0, 0, 3, 0]));
425 }
426 
427 // test removeFront, removeBack
428 unittest
429 {
430 	import std.range;
431 	ChunkedBuffer!(int, 1) buf;
432 	buf.put(100.iota);
433 
434 	buf.removeFront(10);
435 	buf.removeBack(10);
436 
437 	assert(buf[].equal(iota(10, 90)));
438 	assert(buf.byChunk.length == 80);
439 	assert(buf.length == 80);
440 	assert(buf.capacity == 100);
441 	assert(buf.reserved == 20);
442 }
443 
444 // test bidirectionality
445 unittest
446 {
447 	ChunkedBuffer!(int, 32) buf;
448 	buf.put(100.iota);
449 	assert(buf[].retro.equal(100.iota.retro));
450 }
451 
452 // test chunked range
453 unittest
454 {
455 	ChunkedBuffer!(int, 4) buf;
456 	buf.put(1, 2, 3, 4, 5, 6, 7, 8);
457 	assert(buf[].equal([1, 2, 3, 4, 5, 6, 7, 8]));
458 	assert(buf.byChunk.equal([[1, 2, 3, 4], [5, 6, 7, 8]]));
459 	assert(buf.byChunk.toChunkedRange.equal([[1, 2, 3, 4], [5, 6, 7, 8]]));
460 	assert(buf.byChunk.toChunkedRange.byItem.equal([1, 2, 3, 4, 5, 6, 7, 8]));
461 	int[8] sink;
462 	buf.byChunk.toChunkedRange.copyInto(sink[]);
463 	assert(sink[].equal([1, 2, 3, 4, 5, 6, 7, 8]));
464 	assert(buf[2..6].equal([3, 4, 5, 6]));
465 	assert(buf[2..6].toChunkedRange.equal([[3, 4], [5, 6]]));
466 	assert(buf[2..6].toChunkedRange.byItem.equal([3, 4, 5, 6]));
467 	assert(buf[4..8].equal([5, 6, 7, 8]));
468 	buf.put(9, 10, 11, 12);
469 	assert(buf[6..10].equal([7, 8, 9, 10]));
470 }
471 
472 unittest
473 {
474 	import std.algorithm : equal;
475 	import std.stdio;
476 	ChunkedBuffer!(char, 2) buf;
477 	buf.put("test1\n");
478 	buf.put("test2\n");
479 	buf.put("test3\n");
480 
481 	auto text = buf[6..17].toChunkedRange.byItem;
482 
483 	assert(text.equal("test2\ntest3"));
484 }