1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * This module contains buffers designed for C-style input/output APIs. 7 * 8 * Copyright: Eugene Wissner 2016-2020. 9 * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, 10 * Mozilla Public License, v. 2.0). 11 * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) 12 * Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/container/buffer.d, 13 * tanya/container/buffer.d) 14 */ 15 module tanya.container.buffer; 16 17 import std.traits : isScalarType; 18 import tanya.memory.allocator; 19 import tanya.meta.trait; 20 21 version (unittest) 22 { 23 private int fillBuffer(ubyte[] buffer, 24 int start = 0, 25 int end = 10) @nogc pure nothrow 26 in 27 { 28 assert(start < end); 29 } 30 do 31 { 32 auto numberRead = end - start; 33 for (ubyte i; i < numberRead; ++i) 34 { 35 buffer[i] = cast(ubyte) (start + i); 36 } 37 return numberRead; 38 } 39 } 40 41 /** 42 * Self-expanding buffer, that can be used with functions returning the number 43 * of the read bytes. 44 * 45 * This buffer supports asynchronous reading. It means you can pass a new chunk 46 * to an asynchronous read function during you are working with already 47 * available data. But only one asynchronous call at a time is supported. Be 48 * sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result 49 * of the pended asynchronous call. 50 * 51 * Params: 52 * T = Buffer type. 53 */ 54 struct ReadBuffer(T = ubyte) 55 if (isScalarType!T) 56 { 57 /// Internal buffer. 58 private T[] buffer_; 59 60 /// Filled buffer length. 61 private size_t length_; 62 63 /// Start of available data. 64 private size_t start; 65 66 /// Last position returned with $(D_KEYWORD []). 67 private size_t ring; 68 69 /// Available space. 70 private size_t minAvailable = 1024; 71 72 /// Size by which the buffer will grow. 73 private size_t blockSize = 8192; 74 75 invariant 76 { 77 assert(this.length_ <= this.buffer_.length); 78 assert(this.blockSize > 0); 79 assert(this.minAvailable > 0); 80 } 81 82 /** 83 * Creates a new read buffer. 84 * 85 * Params: 86 * size = Initial buffer size and the size by which the buffer 87 * will grow. 88 * minAvailable = minimal size should be always available to fill. 89 * So it will reallocate if $(D_INLINECODE 90 * $(D_PSYMBOL free) < $(D_PARAM minAvailable)). 91 * allocator = Allocator. 92 */ 93 this(size_t size, 94 size_t minAvailable = 1024, 95 shared Allocator allocator = defaultAllocator) @trusted 96 { 97 this(allocator); 98 this.minAvailable = minAvailable; 99 this.blockSize = size; 100 this.buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof); 101 } 102 103 /// ditto 104 this(shared Allocator allocator) 105 in 106 { 107 assert(allocator_ is null); 108 } 109 do 110 { 111 allocator_ = allocator; 112 } 113 114 /** 115 * Deallocates the internal buffer. 116 */ 117 ~this() @trusted 118 { 119 allocator.deallocate(this.buffer_); 120 } 121 122 /// 123 @nogc nothrow pure @safe unittest 124 { 125 ReadBuffer!ubyte b; 126 assert(b.capacity == 0); 127 assert(b.length == 0); 128 } 129 130 /** 131 * Returns: The size of the internal buffer. 132 */ 133 @property size_t capacity() const 134 { 135 return this.buffer_.length; 136 } 137 138 /** 139 * Returns: Data size. 140 */ 141 @property size_t length() const 142 { 143 return this.length_ - start; 144 } 145 146 /// ditto 147 alias opDollar = length; 148 149 /** 150 * Clears the buffer. 151 * 152 * Returns: $(D_KEYWORD this). 153 */ 154 void clear() 155 { 156 start = this.length_ = ring; 157 } 158 159 /** 160 * Returns: Available space. 161 */ 162 @property size_t free() const 163 { 164 return length > ring ? capacity - length : capacity - ring; 165 } 166 167 /// 168 @nogc nothrow pure @system unittest 169 { 170 ReadBuffer!ubyte b; 171 size_t numberRead; 172 173 assert(b.free == 0); 174 175 // Fills the buffer with values 0..10 176 numberRead = fillBuffer(b[], 0, 10); 177 b += numberRead; 178 assert(b.free == b.blockSize - numberRead); 179 b.clear(); 180 assert(b.free == b.blockSize); 181 } 182 183 /** 184 * Appends some data to the buffer. 185 * 186 * Params: 187 * length = Number of the bytes read. 188 * 189 * Returns: $(D_KEYWORD this). 190 */ 191 ref ReadBuffer opOpAssign(string op)(size_t length) 192 if (op == "+") 193 { 194 this.length_ += length; 195 ring = start; 196 return this; 197 } 198 199 /// 200 @nogc nothrow pure @system unittest 201 { 202 ReadBuffer!ubyte b; 203 size_t numberRead; 204 ubyte[] result; 205 206 // Fills the buffer with values 0..10 207 numberRead = fillBuffer(b[], 0, 10); 208 b += numberRead; 209 210 result = b[0 .. $]; 211 assert(result[0] == 0); 212 assert(result[1] == 1); 213 assert(result[9] == 9); 214 b.clear(); 215 216 // It shouldn't overwrite, but append another 5 bytes to the buffer 217 numberRead = fillBuffer(b[], 0, 10); 218 b += numberRead; 219 220 numberRead = fillBuffer(b[], 20, 25); 221 b += numberRead; 222 223 result = b[0..$]; 224 assert(result[0] == 0); 225 assert(result[1] == 1); 226 assert(result[9] == 9); 227 assert(result[10] == 20); 228 assert(result[14] == 24); 229 } 230 231 /** 232 * Params: 233 * start = Start position. 234 * end = End position. 235 * 236 * Returns: Array between $(D_PARAM start) and $(D_PARAM end). 237 */ 238 T[] opSlice(size_t start, size_t end) 239 { 240 return this.buffer_[this.start + start .. this.start + end]; 241 } 242 243 /** 244 * Returns a free chunk of the buffer. 245 * 246 * Add ($(D_KEYWORD +=)) the number of the read bytes after using it. 247 * 248 * Returns: A free chunk of the buffer. 249 */ 250 T[] opIndex() 251 { 252 if (start > 0) 253 { 254 auto ret = this.buffer_[0 .. start]; 255 ring = 0; 256 return ret; 257 } 258 else 259 { 260 if (capacity - length < this.minAvailable) 261 { 262 void[] buf = this.buffer_; 263 const cap = capacity; 264 () @trusted { 265 allocator.reallocate(buf, 266 (cap + this.blockSize) * T.sizeof); 267 this.buffer_ = cast(T[]) buf; 268 }(); 269 } 270 ring = this.length_; 271 return this.buffer_[this.length_ .. $]; 272 } 273 } 274 275 /// 276 @nogc nothrow pure @system unittest 277 { 278 ReadBuffer!ubyte b; 279 size_t numberRead; 280 ubyte[] result; 281 282 // Fills the buffer with values 0..10 283 numberRead = fillBuffer(b[], 0, 10); 284 b += numberRead; 285 286 assert(b.length == 10); 287 result = b[0 .. $]; 288 assert(result[0] == 0); 289 assert(result[9] == 9); 290 b.clear(); 291 assert(b.length == 0); 292 } 293 294 mixin DefaultAllocator; 295 } 296 297 /** 298 * Circular, self-expanding buffer with overflow support. Can be used with 299 * functions returning the number of the transferred bytes. 300 * 301 * The buffer is optimized for situations where you read all the data from it 302 * at once (without writing to it occasionally). It can become ineffective if 303 * you permanently keep some data in the buffer and alternate writing and 304 * reading, because it may allocate and move elements. 305 * 306 * Params: 307 * T = Buffer type. 308 */ 309 struct WriteBuffer(T = ubyte) 310 if (isScalarType!T) 311 { 312 /// Internal buffer. 313 private T[] buffer_; 314 315 /// Buffer start position. 316 private size_t start; 317 318 /// Buffer ring area size. After this position begins buffer overflow area. 319 private size_t ring; 320 321 /// Size by which the buffer will grow. 322 private const size_t blockSize; 323 324 /// The position of the free area in the buffer. 325 private size_t position; 326 327 invariant 328 { 329 assert(this.blockSize > 0); 330 // Position can refer to an element outside the buffer if the buffer is 331 // full. 332 assert(this.position <= this.buffer_.length); 333 } 334 335 /** 336 * Params: 337 * size = Initial buffer size and the size by which the buffer will 338 * grow. 339 * allocator = Allocator. 340 * 341 * Precondition: $(D_INLINECODE size > 0 && allocator !is null) 342 */ 343 this(size_t size, shared Allocator allocator = defaultAllocator) @trusted 344 in 345 { 346 assert(size > 0); 347 assert(allocator !is null); 348 } 349 do 350 { 351 this.blockSize = size; 352 ring = size - 1; 353 allocator_ = allocator; 354 this.buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof); 355 } 356 357 @disable this(); 358 359 /** 360 * Deallocates the internal buffer. 361 */ 362 ~this() 363 { 364 allocator.deallocate(this.buffer_); 365 } 366 367 /** 368 * Returns: The size of the internal buffer. 369 */ 370 @property size_t capacity() const 371 { 372 return this.buffer_.length; 373 } 374 375 /** 376 * Note that $(D_PSYMBOL length) doesn't return the real length of the data, 377 * but only the array length that will be returned with $(D_PSYMBOL opIndex) 378 * next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=) 379 * until $(D_PSYMBOL length) returns 0. 380 * 381 * Returns: Data size. 382 */ 383 @property size_t length() const 384 { 385 if (this.position > ring || this.position < start) // Buffer overflowed 386 { 387 return ring - start + 1; 388 } 389 else 390 { 391 return this.position - start; 392 } 393 } 394 395 /// ditto 396 alias opDollar = length; 397 398 /// 399 @nogc nothrow pure @system unittest 400 { 401 auto b = WriteBuffer!ubyte(4); 402 ubyte[3] buf = [48, 23, 255]; 403 404 b ~= buf; 405 assert(b.length == 3); 406 b += 2; 407 assert(b.length == 1); 408 409 b ~= buf; 410 assert(b.length == 2); 411 b += 2; 412 assert(b.length == 2); 413 414 b ~= buf; 415 assert(b.length == 5); 416 b += b.length; 417 assert(b.length == 0); 418 } 419 420 /** 421 * Returns: Available space. 422 */ 423 @property size_t free() const 424 { 425 return capacity - length; 426 } 427 428 /** 429 * Appends data to the buffer. 430 * 431 * Params: 432 * buffer = Buffer chunk got with $(D_PSYMBOL opIndex). 433 */ 434 ref WriteBuffer opOpAssign(string op)(const T[] buffer) 435 if (op == "~") 436 { 437 size_t end, start; 438 439 if (this.position >= this.start && this.position <= ring) 440 { 441 auto afterRing = ring + 1; 442 443 end = this.position + buffer.length; 444 if (end > afterRing) 445 { 446 end = afterRing; 447 } 448 start = end - this.position; 449 this.buffer_[this.position .. end] = buffer[0 .. start]; 450 if (end == afterRing) 451 { 452 this.position = this.start == 0 ? afterRing : 0; 453 } 454 else 455 { 456 this.position = end; 457 } 458 } 459 460 // Check if we have some free space at the beginning 461 if (start < buffer.length && this.position < this.start) 462 { 463 end = this.position + buffer.length - start; 464 if (end > this.start) 465 { 466 end = this.start; 467 } 468 auto areaEnd = end - this.position + start; 469 this.buffer_[this.position .. end] = buffer[start .. areaEnd]; 470 this.position = end == this.start ? ring + 1 : end - this.position; 471 start = areaEnd; 472 } 473 474 // And if we still haven't found any place, save the rest in the overflow area 475 if (start < buffer.length) 476 { 477 end = this.position + buffer.length - start; 478 if (end > capacity) 479 { 480 const newSize = end / this.blockSize * this.blockSize 481 + this.blockSize; 482 () @trusted { 483 void[] buf = this.buffer_; 484 allocator.reallocate(buf, newSize * T.sizeof); 485 this.buffer_ = cast(T[]) buf; 486 }(); 487 } 488 this.buffer_[this.position .. end] = buffer[start .. $]; 489 this.position = end; 490 if (this.start == 0) 491 { 492 ring = capacity - 1; 493 } 494 } 495 496 return this; 497 } 498 499 /** 500 * Sets how many bytes were written. It will shrink the buffer 501 * appropriately. Always call it after $(D_PSYMBOL opIndex). 502 * 503 * Params: 504 * length = Length of the written data. 505 * 506 * Returns: $(D_KEYWORD this). 507 */ 508 ref WriteBuffer opOpAssign(string op)(size_t length) 509 if (op == "+") 510 in 511 { 512 assert(length <= this.length); 513 } 514 do 515 { 516 auto afterRing = ring + 1; 517 auto oldStart = start; 518 519 if (length <= 0) 520 { 521 return this; 522 } 523 else if (this.position <= afterRing) 524 { 525 start += length; 526 if (start > 0 && this.position == afterRing) 527 { 528 this.position = oldStart; 529 } 530 } 531 else 532 { 533 auto overflow = this.position - afterRing; 534 535 if (overflow > length) 536 { 537 const afterLength = afterRing + length; 538 this.buffer_[start .. start + length] = this.buffer_[afterRing .. afterLength]; 539 this.buffer_[afterRing .. afterLength] = this.buffer_[afterLength .. this.position]; 540 this.position -= length; 541 } 542 else if (overflow == length) 543 { 544 this.buffer_[start .. start + overflow] = this.buffer_[afterRing .. this.position]; 545 this.position -= overflow; 546 } 547 else 548 { 549 this.buffer_[start .. start + overflow] = this.buffer_[afterRing .. this.position]; 550 this.position = overflow; 551 } 552 start += length; 553 554 if (start == this.position) 555 { 556 if (this.position != afterRing) 557 { 558 this.position = 0; 559 } 560 start = 0; 561 ring = capacity - 1; 562 } 563 } 564 if (start > ring) 565 { 566 start = 0; 567 } 568 return this; 569 } 570 571 /// 572 @nogc nothrow pure @system unittest 573 { 574 auto b = WriteBuffer!ubyte(6); 575 ubyte[6] buf = [23, 23, 255, 128, 127, 9]; 576 577 b ~= buf; 578 assert(b.length == 6); 579 b += 2; 580 assert(b.length == 4); 581 b += 4; 582 assert(b.length == 0); 583 } 584 585 /** 586 * Returns a chunk with data. 587 * 588 * After calling it, set $(D_KEYWORD +=) to the length could be 589 * written. 590 * 591 * $(D_PSYMBOL opIndex) may return only part of the data. You may need 592 * to call it and set $(D_KEYWORD +=) several times until 593 * $(D_PSYMBOL length) is 0. If all the data can be written, 594 * maximally 3 calls are required. 595 * 596 * Returns: A chunk of data buffer. 597 */ 598 T[] opSlice(size_t start, size_t end) 599 { 600 if (this.position > ring || this.position < start) // Buffer overflowed 601 { 602 return this.buffer_[this.start .. ring + 1 - length + end]; 603 } 604 else 605 { 606 return this.buffer_[this.start .. this.start + end]; 607 } 608 } 609 610 /// 611 @nogc nothrow pure @system unittest 612 { 613 auto b = WriteBuffer!ubyte(6); 614 ubyte[6] buf = [23, 23, 255, 128, 127, 9]; 615 616 b ~= buf; 617 assert(b[0 .. $] == buf[0 .. 6]); 618 b += 2; 619 620 assert(b[0 .. $] == buf[2 .. 6]); 621 622 b ~= buf; 623 assert(b[0 .. $] == buf[2 .. 6]); 624 b += b.length; 625 626 assert(b[0 .. $] == buf[0 .. 6]); 627 b += b.length; 628 } 629 630 /** 631 * After calling it, set $(D_KEYWORD +=) to the length could be 632 * written. 633 * 634 * $(D_PSYMBOL opIndex) may return only part of the data. You may need 635 * to call it and set $(D_KEYWORD +=) several times until 636 * $(D_PSYMBOL length) is 0. If all the data can be written, 637 * maximally 3 calls are required. 638 * 639 * Returns: A chunk of data buffer. 640 */ 641 T[] opIndex() 642 { 643 return opSlice(0, length); 644 } 645 646 mixin DefaultAllocator; 647 } 648 649 @nogc nothrow pure @system unittest 650 { 651 auto b = WriteBuffer!ubyte(4); 652 ubyte[3] buf = [48, 23, 255]; 653 654 b ~= buf; 655 assert(b.capacity == 4); 656 assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255); 657 658 b += 2; 659 b ~= buf; 660 assert(b.capacity == 4); 661 assert(b.buffer_[0] == 23 && b.buffer_[1] == 255 662 && b.buffer_[2] == 255 && b.buffer_[3] == 48); 663 664 b += 2; 665 b ~= buf; 666 assert(b.capacity == 8); 667 assert(b.buffer_[0] == 23 && b.buffer_[1] == 255 668 && b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255); 669 } 670 671 @nogc nothrow pure @system unittest 672 { 673 auto b = WriteBuffer!ubyte(2); 674 ubyte[3] buf = [48, 23, 255]; 675 676 b ~= buf; 677 assert(b.start == 0); 678 assert(b.capacity == 4); 679 assert(b.ring == 3); 680 assert(b.position == 3); 681 }