1 module xbuffer.buffer; 2 3 import std.bitmanip : swapEndian; 4 import std.string : toUpper; 5 import std.system : Endian, endian; 6 import std.traits : isArray, isBoolean, isIntegral, isFloatingPoint, isSomeChar, Unqual; 7 8 import xbuffer.memory : xmalloc, xrealloc, xfree; 9 import xbuffer.varint : isVar, Var; 10 11 //TODO remove 12 import std.stdio : writeln; 13 14 alias ForeachType(T) = typeof(T.init[0]); 15 16 enum canSwapEndianness(T) = isBoolean!T || isIntegral!T || isFloatingPoint!T || isSomeChar!T || (is(T == struct) && canSwapEndiannessImpl!T); 17 18 private bool canSwapEndiannessImpl(T)() { 19 static if(is(T == struct)) { 20 import std.traits : Fields; 21 bool ret = true; 22 foreach(field ; Fields!T) { 23 if(!canSwapEndianness!field) ret = false; 24 } 25 return ret; 26 } else { 27 return false; 28 } 29 } 30 31 unittest { 32 33 static assert(canSwapEndianness!byte); 34 static assert(canSwapEndianness!int); 35 static assert(canSwapEndianness!double); 36 static assert(canSwapEndianness!char); 37 static assert(canSwapEndianness!dchar); 38 39 } 40 41 unittest { 42 43 static struct A {} 44 45 static struct B { int a, b; } 46 47 static class C {} 48 49 static struct D { int a; B b; } 50 51 static struct E { B b; C c; } 52 53 static struct F { void a(){} float b; } 54 55 static struct G { @property Object a(){ return null; } } 56 57 static struct H { ubyte[] a; } 58 59 static struct I { int* a; } 60 61 static assert(canSwapEndianness!A); 62 static assert(canSwapEndianness!B); 63 static assert(!canSwapEndianness!C); 64 static assert(canSwapEndianness!D); 65 static assert(!canSwapEndianness!E); 66 static assert(canSwapEndianness!F); 67 static assert(canSwapEndianness!G); 68 static assert(!canSwapEndianness!H); 69 static assert(!canSwapEndianness!I); 70 71 } 72 73 private union EndianSwapper(T) if(canSwapEndianness!T) { 74 75 enum builtInSwap = T.sizeof == 2 || T.sizeof == 4 || T.sizeof == 8; 76 77 T value; 78 void[T.sizeof] data; 79 ubyte[T.sizeof] bytes; 80 81 this(T value) { 82 this.value = value; 83 } 84 85 this(void[] data) { 86 this.data = data; 87 } 88 89 static if(T.sizeof == 2) ushort _swap; 90 else static if(T.sizeof == 4) uint _swap; 91 else static if(T.sizeof == 8) ulong _swap; 92 93 void swap() { 94 static if(builtInSwap) _swap = swapEndian(_swap); 95 else static if(T.sizeof > 1) { 96 import std.algorithm.mutation : swap; 97 foreach(i ; 0..T.sizeof>>1) { 98 swap(bytes[i], bytes[T.sizeof-i-1]); 99 } 100 } 101 } 102 103 } 104 105 unittest { 106 107 static struct Test { 108 109 int a, b, c; 110 111 } 112 113 static assert(Test.sizeof == 12); 114 115 EndianSwapper!Test swapper; 116 swapper.value = Test(1, 2, 3); 117 118 version(BigEndian) assert(swapper.bytes == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 119 version(LittleEndian) assert(swapper.bytes == [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 120 121 swapper.swap(); 122 123 version(BigEndian) assert(swapper.bytes == [3, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0]); 124 version(LittleEndian) assert(swapper.bytes == [0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 1]); 125 126 assert(swapper.value == Test(3 << 24, 2 << 24, 1 << 24)); 127 128 } 129 130 /** 131 * Exception thrown when the buffer cannot read the requested 132 * data. 133 */ 134 class BufferOverflowException : Exception { 135 136 this(string file=__FILE__, size_t line=__LINE__) pure nothrow @safe @nogc { 137 super("The buffer's index has exceeded its length", file, line); 138 } 139 140 } 141 142 static if(__traits(compiles, () @nogc { throw new Exception(""); })) version = DIP1008; 143 144 /** 145 * Buffer for writing and reading binary data. 146 */ 147 class Buffer { 148 149 private immutable size_t chunk; 150 151 private void[] _data; 152 private size_t _rindex, _windex; 153 154 /** 155 * Creates a buffer specifying the chunk size. 156 * This should be the default constructor for re-used buffers and 157 * input buffers. 158 */ 159 this(size_t chunk) pure nothrow @trusted @nogc { 160 this.chunk = chunk == 0 ? 1 : chunk; 161 //_data = malloc(this.chunk); 162 _data = xrealloc(_data.ptr, this.chunk); 163 } 164 165 /// 166 pure nothrow @safe unittest { 167 168 Buffer buffer = new Buffer(8); 169 170 // 8 bytes allocated by the constructor 171 assert(buffer.capacity == 8); 172 173 // writing 4 bytes does not alter the capacity 174 buffer.write(0); 175 assert(buffer.capacity == 8); 176 177 // writing 8 bytes requires a new allocation (because 4 + 8 is 178 // higher than the current capacity of 8). 179 // The new capacity is rounded up to the nearest multiple of the chunk size. 180 buffer.write(0L); 181 assert(buffer.capacity == 16); 182 183 } 184 185 /** 186 * Creates a buffer from an array of data. 187 * The chunk size is set to the size of the array. 188 */ 189 this(T)(in T[] data...) pure nothrow @trusted @nogc if(canSwapEndianness!T || is(T == void)) { 190 this(data.length * T.sizeof); 191 _windex = _data.length; 192 _data[0..$] = cast(void[])data; 193 } 194 195 /// 196 pure nothrow @safe unittest { 197 198 Buffer buffer = new Buffer(cast(ubyte[])[1, 2, 3, 4]); 199 assert(buffer.rindex == 0); 200 assert(buffer.windex == 4); 201 202 buffer = new Buffer([1, 2]); 203 assert(buffer.rindex == 0); 204 assert(buffer.windex == 8); 205 206 } 207 208 private void resize(size_t requiredSize) pure nothrow @trusted @nogc { 209 immutable rem = requiredSize / chunk; 210 immutable size = (requiredSize + chunk - 1) / chunk * chunk; 211 _data = xrealloc(_data.ptr, size); 212 } 213 214 @property size_t rindex() pure nothrow @safe @nogc { 215 return _rindex; 216 } 217 218 @property size_t windex() pure nothrow @safe @nogc { 219 return _windex; 220 } 221 222 @property T[] data(T=void)() pure nothrow @trusted @nogc if((canSwapEndianness!T || is(T == void)) && T.sizeof == 1) { 223 return cast(T[])_data[_rindex.._windex]; 224 } 225 226 @property T[] data(T)() pure nothrow @nogc if(canSwapEndianness!T && T.sizeof != 1) { 227 return cast(T[])_data[_rindex.._windex]; 228 } 229 230 /** 231 * Sets new data and resets the index. 232 */ 233 @property auto data(T)(in T[] data) pure nothrow @trusted @nogc { 234 _rindex = 0; 235 _windex = data.length * T.sizeof; 236 if(_windex > _data.length) this.resize(_windex); 237 _data[0.._windex] = cast(void[])data; 238 return data; 239 } 240 241 /// 242 pure nothrow @trusted unittest { 243 244 Buffer buffer = new Buffer(2); 245 246 buffer.data = cast(ubyte[])[0, 0, 0, 1]; 247 assert(buffer.rindex == 0); // resetted when setting new data 248 assert(buffer.windex == 4); 249 version(BigEndian) assert(buffer.data!uint == [1]); 250 version(LittleEndian) assert(buffer.data!uint == [1 << 24]); 251 252 buffer.data = "hello"; 253 assert(buffer.rindex == 0); 254 assert(buffer.windex == 5); 255 assert(buffer.data == "hello"); 256 257 } 258 259 /** 260 * Resets the buffer setting the index and its length to 0. 261 */ 262 void reset() pure nothrow @safe @nogc { 263 _rindex = 0; 264 _windex = 0; 265 } 266 267 /** 268 * Gets the size of the data allocated by the buffer. 269 */ 270 @property size_t capacity() pure nothrow @safe @nogc { 271 return _data.length; 272 } 273 274 void back(size_t amount) pure nothrow @safe @nogc { 275 assert(amount <= _rindex); 276 _rindex -= amount; 277 } 278 279 // ---------- 280 // operations 281 // ---------- 282 283 /** 284 * Check whether the data of the buffer is equals to 285 * the given array. 286 */ 287 bool opEquals(T)(in T[] data) pure nothrow @safe @nogc if(T.sizeof == 1) { 288 return this.data!T == data; 289 } 290 291 /// ditto 292 bool opEquals(T)(in T[] data) pure nothrow @nogc if(T.sizeof != 1) { 293 return this.data!T == data; 294 } 295 296 /// 297 pure nothrow @trusted unittest { 298 299 Buffer buffer = new Buffer([1, 2, 3]); 300 301 assert(buffer == [1, 2, 3]); 302 version(BigEndian) assert(buffer == cast(ubyte[])[0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 303 version(LittleEndian) assert(buffer == cast(ubyte[])[1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 304 305 } 306 307 // ----- 308 // write 309 // ----- 310 311 private void need(size_t size) pure nothrow @safe @nogc { 312 size += _windex; 313 if(size > this.capacity) this.resize(size); 314 } 315 316 private void writeDataImpl(in void[] data) pure nothrow @trusted @nogc { 317 immutable start = _windex; 318 _windex += data.length; 319 _data[start.._windex] = data; 320 } 321 322 /** 323 * Writes data to the buffer and expands if it is not big enough. 324 */ 325 void writeData(in void[] data) pure nothrow @safe @nogc { 326 this.need(data.length); 327 this.writeDataImpl(data); 328 } 329 330 /** 331 * Writes data to buffer using the given endianness. 332 */ 333 void write(Endian endianness, T)(T value) pure nothrow @trusted @nogc if(canSwapEndianness!T) { 334 EndianSwapper!T swapper = EndianSwapper!T(value); 335 static if(endianness != endian && T.sizeof > 1) swapper.swap(); 336 this.writeData(swapper.data); 337 } 338 339 /// 340 unittest { 341 342 Buffer buffer = new Buffer(4); 343 buffer.write!(Endian.bigEndian)(4); 344 buffer.write!(Endian.littleEndian)(4); 345 assert(buffer.data!ubyte == [0, 0, 0, 4, 4, 0, 0, 0]); 346 347 } 348 349 /** 350 * Writes data to the buffer using the system's endianness. 351 */ 352 void write(T)(T value) pure nothrow @safe @nogc if(canSwapEndianness!T) { 353 this.write!(endian, T)(value); 354 } 355 356 /// 357 pure nothrow @safe unittest { 358 359 Buffer buffer = new Buffer(5); 360 buffer.write(ubyte(5)); 361 buffer.write(10); 362 version(BigEndian) assert(buffer.data!ubyte == [5, 0, 0, 0, 10]); 363 version(LittleEndian) assert(buffer.data!ubyte == [5, 10, 0, 0, 0]); 364 365 } 366 367 /** 368 * Writes an array using the given endianness. 369 */ 370 void write(Endian endianness, T)(in T value) pure nothrow @trusted @nogc if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 371 static if(endianness == endian || T.sizeof <= 1) { 372 this.writeData(value); 373 } else { 374 this.need(value.length * ForeachType!T.sizeof); 375 foreach(element ; value) { 376 auto swapper = EndianSwapper!(ForeachType!T)(element); 377 swapper.swap(); 378 this.writeDataImpl(swapper.data); 379 } 380 } 381 } 382 383 /// 384 pure nothrow @safe unittest { 385 386 Buffer buffer = new Buffer(8); 387 buffer.write!(Endian.bigEndian)([1, 2, 3]); 388 assert(buffer.capacity == 16); 389 assert(buffer.data!ubyte == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 390 391 buffer.reset(); 392 buffer.write!(Endian.littleEndian)(cast(short[])[-2, 2]); 393 assert(buffer.data!ubyte == [254, 255, 2, 0]); 394 395 buffer.reset(); 396 buffer.write!(Endian.bigEndian, wstring)("test"w); 397 assert(buffer.data!ubyte == [0, 't', 0, 'e', 0, 's', 0, 't']); 398 399 } 400 401 /** 402 * Writes an array using the system's endianness. 403 */ 404 void write(T)(in T value) pure nothrow @safe @nogc if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 405 this.write!(endian, T)(value); 406 } 407 408 /// 409 pure nothrow @safe unittest { 410 411 Buffer buffer = new Buffer(8); 412 buffer.write(cast(ubyte[])[1, 2, 3, 4]); 413 buffer.write("test"); 414 assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't']); 415 buffer.write([1, 2]); 416 version(BigEndian) assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't', 0, 0, 0, 1, 0, 0, 0, 2]); 417 version(LittleEndian) assert(buffer.data!ubyte == [1, 2, 3, 4, 't', 'e', 's', 't', 1, 0, 0, 0, 2, 0, 0, 0]); 418 419 } 420 421 /** 422 * Writes a varint. 423 */ 424 void writeVar(T)(T value) pure nothrow @safe @nogc if(isIntegral!T && T.sizeof > 1) { 425 Var!T.encode(this, value); 426 } 427 428 /// ditto 429 void write(T:Var!B, B)(B value) pure nothrow @safe @nogc { 430 this.writeVar!(T.Base)(value); 431 } 432 433 /// 434 pure nothrow @safe unittest { 435 436 import xbuffer.varint; 437 438 Buffer buffer = new Buffer(8); 439 buffer.writeVar(1); 440 buffer.write!varuint(1); 441 assert(buffer.data!ubyte == [2, 1]); 442 443 } 444 445 /** 446 * Writes data at the given index. 447 */ 448 void write(alias E=endian, T)(in T value, size_t index) pure nothrow @trusted @nogc if(is(typeof(E) : Endian) && (canSwapEndianness!T || is(T == void) || isArray!T && (canSwapEndianness!(ForeachType!T) || is(ForeachType!T == void))) || is(E == struct) && isVar!E) { 449 void[] shift = xmalloc(this.data.length - index); 450 shift[0..$] = this.data[index..$]; 451 _windex = _rindex + index; 452 this.write!E(value); 453 this.writeData(shift); 454 xfree(shift.ptr); 455 } 456 457 /// ditto 458 pure @trusted unittest { 459 460 import xbuffer.varint; 461 462 Buffer buffer = new Buffer([1, 2, 3]); 463 buffer.write(0, 0); 464 assert(buffer.data!int == [0, 1, 2, 3]); 465 466 buffer.data = cast(ubyte[])[0, 1, 2, 5]; 467 buffer.write(cast(ubyte[])[3, 4], 3); 468 assert(buffer.data!ubyte == [0, 1, 2, 3, 4, 5]); 469 470 buffer.data = cast(ubyte[])[1, 1, 2]; 471 buffer.read!ubyte(); 472 buffer.write!varuint(118485, 1); 473 assert(buffer.data!ubyte == [1, 213, 157, 7, 2]); 474 475 buffer.data = "hellld"; 476 buffer.write('o', 4); 477 buffer.write(" wor", 5); 478 assert(buffer.data == "hello world"); 479 480 } 481 482 // ---- 483 // read 484 // ---- 485 486 /** 487 * Indicates whether an array of length `size` or the given type 488 * can be read without any exceptions thrown. 489 */ 490 bool canRead(size_t size) pure nothrow @safe @nogc { 491 return _rindex + size <= _windex; 492 } 493 494 /// ditto 495 bool canRead(T)() pure nothrow @safe @nogc if(canSwapEndianness!T) { 496 return this.canRead(T.sizeof); 497 } 498 499 /// 500 unittest { 501 502 import xbuffer.varint; 503 504 Buffer buffer = new Buffer(cast(ubyte[])[128, 200, 3]); 505 assert(buffer.canRead(2)); 506 assert(buffer.canRead(3)); 507 assert(!buffer.canRead(4)); 508 assert(buffer.canRead!byte()); 509 assert(buffer.canRead!short()); 510 assert(!buffer.canRead!int()); 511 512 } 513 514 /** 515 * Reads the amount of data requested. 516 * Throws: BufferOverflowException if there isn't enough data to read. 517 */ 518 void[] readData(size_t size) pure @safe { 519 if(!this.canRead(size)) throw new BufferOverflowException(); 520 _rindex += size; 521 return _data[_rindex-size.._rindex]; 522 } 523 524 /// 525 pure @safe unittest { 526 527 Buffer buffer = new Buffer([1]); 528 assert(buffer.read!int() == 1); 529 try { 530 buffer.read!int(); assert(0); 531 } catch(BufferOverflowException ex) { 532 assert(ex.file == __FILE__); 533 } 534 535 } 536 537 /** 538 * Reads a value with the given endianness. 539 * Throws: BufferOverflowException if there isn't enough data to read. 540 */ 541 T read(Endian endianness, T)() pure @trusted if(canSwapEndianness!T) { 542 EndianSwapper!T swapper = EndianSwapper!T(this.readData(T.sizeof)); 543 static if(endianness != endian) swapper.swap(); 544 return swapper.value; 545 } 546 547 /// 548 pure @safe unittest { 549 550 Buffer buffer = new Buffer(cast(ubyte[])[0, 0, 0, 1, 1, 0]); 551 assert(buffer.read!(Endian.bigEndian, int)() == 1); 552 assert(buffer.read!(Endian.littleEndian, short)() == 1); 553 554 } 555 556 /** 557 * Reads a value using the system's endianness. 558 * Throws: BufferOverflowException if there isn't enough data to read. 559 */ 560 T read(T)() pure @safe if(canSwapEndianness!T) { 561 return this.read!(endian, T)(); 562 } 563 564 /// 565 pure @safe unittest { 566 567 version(BigEndian) Buffer buffer = new Buffer([0, 0, 0, 1]); 568 version(LittleEndian) Buffer buffer = new Buffer([1, 0, 0, 0]); 569 assert(buffer.read!int() == 1); 570 571 } 572 573 /** 574 * Reads an array using the given endianness. 575 * Throws: BufferOverflowException if there isn't enough data to read. 576 */ 577 T read(Endian endianness, T)(size_t length) pure @trusted if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 578 T ret = cast(T)this.readData(length * ForeachType!T.sizeof); 579 static if(endianness != endian && T.sizeof != 1) { 580 foreach(ref element ; ret) { 581 EndianSwapper!(ForeachType!T) swapper = EndianSwapper!(ForeachType!T)(element); 582 swapper.swap(); 583 element = swapper.value; 584 } 585 } 586 return ret; 587 } 588 589 /// 590 pure @safe unittest { 591 592 Buffer buffer = new Buffer(16); 593 594 buffer.write!(Endian.bigEndian)(16); 595 buffer.write!(Endian.bigEndian)(32); 596 buffer.write!(Endian.littleEndian)(32); 597 buffer.write!(Endian.littleEndian)(16); 598 assert(buffer.data!ubyte == [0, 0, 0, 16, 0, 0, 0, 32, 32, 0, 0, 0, 16, 0, 0, 0]); 599 600 assert(buffer.read!(Endian.bigEndian, int[])(2) == [16, 32]); 601 assert(buffer.read!(Endian.littleEndian, int[])(2) == [32, 16]); 602 603 } 604 605 /** 606 * Reads an array using the system's endianness. 607 * Throws: BufferOverflowException if there isn't enough data to read. 608 */ 609 T read(T)(size_t size) pure @trusted if(isArray!T && (is(ForeachType!T : void) || canSwapEndianness!(ForeachType!T))) { 610 return this.read!(endian, T)(size); 611 } 612 613 /// 614 pure @safe unittest { 615 616 Buffer buffer = new Buffer("!hello"); 617 assert(buffer.read!(ubyte[])(1) == [33]); 618 assert(buffer.read!string(5) == "hello"); 619 620 buffer.data = [1, 2, 3]; 621 version(BigEndian) assert(buffer.data!ubyte == [0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3]); 622 version(LittleEndian) assert(buffer.data!ubyte == [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0]); 623 assert(buffer.read!(int[])(3) == [1, 2, 3]); 624 625 } 626 627 /** 628 * Reads a varint. 629 * Throws: BufferOverflowException if there isn't enough data to read. 630 */ 631 T readVar(T)() pure @safe if(isIntegral!T && T.sizeof > 1) { 632 return Var!T.decode!true(this); 633 } 634 635 /// ditto 636 B read(T:Var!B, B)() pure @safe { 637 return this.readVar!B(); 638 } 639 640 /// 641 pure @safe unittest { 642 643 import xbuffer.varint; 644 645 Buffer buffer = new Buffer(cast(ubyte[])[2, 1]); 646 assert(buffer.readVar!int() == 1); 647 assert(buffer.read!varuint() == 1); 648 649 } 650 651 // ---- 652 // peek 653 // ---- 654 655 /** 656 * Peeks some data (read it but without changing the buffer's index). 657 * Throws: BufferOverflowException if there isn't enough data to read. 658 */ 659 void[] peekData(size_t size) pure { 660 if(!this.canRead(size)) throw new BufferOverflowException(); 661 return _data[_rindex.._rindex+size]; 662 } 663 664 unittest { 665 666 Buffer buffer = new Buffer([1]); 667 assert(buffer.rindex == 0); 668 buffer.peekData(4); 669 assert(buffer.rindex == 0); 670 671 } 672 673 /** 674 * Peeks a value using the given endianness. 675 * Throws: BufferOverflowException if there isn't enough data to read. 676 */ 677 T peek(Endian endianness, T)() pure @trusted if(canSwapEndianness!T) { 678 EndianSwapper!T swapper = EndianSwapper!T(this.peekData(T.sizeof)); 679 static if(endianness != endian) swapper.swap(); 680 return swapper.value; 681 } 682 683 /// 684 pure @safe unittest { 685 686 Buffer buffer = new Buffer(cast(ubyte[])[0, 0, 0, 1]); 687 assert(buffer.peek!(Endian.bigEndian, int)() == 1); 688 assert(buffer.peek!(Endian.littleEndian, int)() == 1 << 24); 689 assert(buffer.peek!(Endian.bigEndian, short)() == 0); 690 691 } 692 693 /** 694 * Peeks some data using the system's endianness. 695 * Throws: BufferOverflowException if there isn't enough data to read. 696 */ 697 T peek(T)() pure @safe if(canSwapEndianness!T) { 698 return this.peek!(endian, T)(); 699 } 700 701 /// 702 pure @safe unittest { 703 704 Buffer buffer = new Buffer([1, 2]); 705 assert(buffer.peek!int() == 1); 706 assert(buffer.rindex == 0); 707 assert(buffer.peek!int() == buffer.read!int()); 708 assert(buffer.rindex == 4); 709 assert(buffer.peek!int() == 2); 710 711 } 712 713 /** 714 * Peeks a varint. 715 * Throws: BufferOverflowException if there isn't enough data to read. 716 */ 717 T peekVar(T)() pure @safe if(isIntegral!T && T.sizeof > 1) { 718 return Var!T.decode!false(this); 719 } 720 721 /// ditto 722 B peek(T:Var!B, B)() pure @safe { 723 return this.peekVar!B(); 724 } 725 726 /// 727 pure @safe unittest { 728 729 import xbuffer.varint; 730 731 Buffer buffer = new Buffer(cast(ubyte[])[2]); 732 assert(buffer.peekVar!int() == 1); 733 assert(buffer.peek!varuint() == 2); 734 735 } 736 737 // ----------- 738 // destruction 739 // ----------- 740 741 void free() pure nothrow @nogc { 742 xfree(_data.ptr); 743 } 744 745 void __xdtor() pure nothrow @nogc { 746 this.free(); 747 } 748 749 ~this() { 750 this.free(); 751 } 752 753 } 754 755 /// 756 unittest { 757 758 import xbuffer.memory : xalloc; 759 760 // a buffer can be garbage collected 761 Buffer gc = new Buffer(16); 762 763 // or manually allocated 764 // alloc is a function provided by the xbuffer.memory module 765 Buffer b = xalloc!Buffer(16); 766 767 // the memory is realsed with free, which is called by the garbage 768 // collector or by the `free` function in the `xbuffer.memory` module 769 xfree(b); 770 771 } 772 773 unittest { 774 775 import xbuffer.memory : xalloc; 776 777 void[] data = xmalloc(923); 778 779 auto buffer = xalloc!Buffer(1024); 780 assert(buffer.windex == 0); 781 782 buffer.writeData(data); 783 assert(buffer.rindex == 0); 784 assert(buffer.windex == 923); 785 assert(buffer.capacity == 1024); 786 787 buffer.writeData(data); 788 assert(buffer.windex == 1846); 789 assert(buffer.capacity == 2048); 790 791 data = xrealloc(data.ptr, 1); 792 793 buffer.data = data; 794 assert(buffer.windex == 1); 795 assert(buffer.capacity == 2048); 796 797 data = xrealloc(data.ptr, 2049); 798 799 buffer.data = data; 800 assert(buffer.windex == 2049); 801 assert(buffer.capacity == 3072); 802 803 xfree(data.ptr); 804 xfree(buffer); 805 806 }