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 }