691 lines
17 KiB
D
691 lines
17 KiB
D
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/**
|
|
* This module contains buffers designed for C-style input/output APIs.
|
|
*
|
|
* Copyright: Eugene Wissner 2016-2017.
|
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
|
* Mozilla Public License, v. 2.0).
|
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
|
*/
|
|
module tanya.container.buffer;
|
|
|
|
import std.traits;
|
|
import tanya.memory;
|
|
|
|
version (unittest)
|
|
{
|
|
private int fillBuffer(ubyte[] buffer,
|
|
in size_t size,
|
|
int start = 0,
|
|
int end = 10) @nogc pure nothrow
|
|
in
|
|
{
|
|
assert(start < end);
|
|
}
|
|
body
|
|
{
|
|
auto numberRead = end - start;
|
|
for (ubyte i; i < numberRead; ++i)
|
|
{
|
|
buffer[i] = cast(ubyte) (start + i);
|
|
}
|
|
return numberRead;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Self-expanding buffer, that can be used with functions returning the number
|
|
* of the read bytes.
|
|
*
|
|
* This buffer supports asynchronous reading. It means you can pass a new chunk
|
|
* to an asynchronous read function during you are working with already
|
|
* available data. But only one asynchronous call at a time is supported. Be
|
|
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
|
|
* of the pended asynchronous call.
|
|
*
|
|
* Params:
|
|
* T = Buffer type.
|
|
*/
|
|
struct ReadBuffer(T = ubyte)
|
|
if (isScalarType!T)
|
|
{
|
|
/// Internal buffer.
|
|
private T[] buffer_;
|
|
|
|
/// Filled buffer length.
|
|
private size_t length_;
|
|
|
|
/// Start of available data.
|
|
private size_t start;
|
|
|
|
/// Last position returned with $(D_KEYWORD []).
|
|
private size_t ring;
|
|
|
|
/// Available space.
|
|
private immutable size_t minAvailable = 1024;
|
|
|
|
/// Size by which the buffer will grow.
|
|
private immutable size_t blockSize = 8192;
|
|
|
|
invariant
|
|
{
|
|
assert(length_ <= buffer_.length);
|
|
assert(blockSize > 0);
|
|
assert(minAvailable > 0);
|
|
}
|
|
|
|
/**
|
|
* Creates a new read buffer.
|
|
*
|
|
* Params:
|
|
* size = Initial buffer size and the size by which the buffer
|
|
* will grow.
|
|
* minAvailable = minimal size should be always available to fill.
|
|
* So it will reallocate if $(D_INLINECODE
|
|
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
|
|
* allocator = Allocator.
|
|
*/
|
|
this(in size_t size,
|
|
in size_t minAvailable = 1024,
|
|
shared Allocator allocator = defaultAllocator) @trusted
|
|
{
|
|
this(allocator);
|
|
this.minAvailable = minAvailable;
|
|
this.blockSize = size;
|
|
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
|
}
|
|
|
|
/// Ditto.
|
|
this(shared Allocator allocator)
|
|
in
|
|
{
|
|
assert(allocator_ is null);
|
|
}
|
|
body
|
|
{
|
|
allocator_ = allocator;
|
|
}
|
|
|
|
/**
|
|
* Deallocates the internal buffer.
|
|
*/
|
|
~this() @trusted
|
|
{
|
|
allocator.deallocate(buffer_);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
ReadBuffer!ubyte b;
|
|
assert(b.capacity == 0);
|
|
assert(b.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Returns: The size of the internal buffer.
|
|
*/
|
|
@property size_t capacity() const
|
|
{
|
|
return buffer_.length;
|
|
}
|
|
|
|
/**
|
|
* Returns: Data size.
|
|
*/
|
|
@property size_t length() const
|
|
{
|
|
return length_ - start;
|
|
}
|
|
|
|
/// Ditto.
|
|
alias opDollar = length;
|
|
|
|
/**
|
|
* Clears the buffer.
|
|
*
|
|
* Returns: $(D_KEYWORD this).
|
|
*/
|
|
void clear()
|
|
{
|
|
start = length_ = ring;
|
|
}
|
|
|
|
/**
|
|
* Returns: Available space.
|
|
*/
|
|
@property size_t free() const
|
|
{
|
|
return length > ring ? capacity - length : capacity - ring;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
ReadBuffer!ubyte b;
|
|
size_t numberRead;
|
|
|
|
assert(b.free == 0);
|
|
|
|
// Fills the buffer with values 0..10
|
|
numberRead = fillBuffer(b[], b.free, 0, 10);
|
|
b += numberRead;
|
|
assert(b.free == b.blockSize - numberRead);
|
|
b.clear();
|
|
assert(b.free == b.blockSize);
|
|
}
|
|
|
|
/**
|
|
* Appends some data to the buffer.
|
|
*
|
|
* Params:
|
|
* length = Number of the bytes read.
|
|
*
|
|
* Returns: $(D_KEYWORD this).
|
|
*/
|
|
ref ReadBuffer opOpAssign(string op)(in size_t length)
|
|
if (op == "+")
|
|
{
|
|
length_ += length;
|
|
ring = start;
|
|
return this;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
ReadBuffer!ubyte b;
|
|
size_t numberRead;
|
|
ubyte[] result;
|
|
|
|
// Fills the buffer with values 0..10
|
|
numberRead = fillBuffer(b[], b.free, 0, 10);
|
|
b += numberRead;
|
|
|
|
result = b[0 .. $];
|
|
assert(result[0] == 0);
|
|
assert(result[1] == 1);
|
|
assert(result[9] == 9);
|
|
b.clear();
|
|
|
|
// It shouldn't overwrite, but append another 5 bytes to the buffer
|
|
numberRead = fillBuffer(b[], b.free, 0, 10);
|
|
b += numberRead;
|
|
|
|
numberRead = fillBuffer(b[], b.free, 20, 25);
|
|
b += numberRead;
|
|
|
|
result = b[0..$];
|
|
assert(result[0] == 0);
|
|
assert(result[1] == 1);
|
|
assert(result[9] == 9);
|
|
assert(result[10] == 20);
|
|
assert(result[14] == 24);
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* start = Start position.
|
|
* end = End position.
|
|
*
|
|
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
|
*/
|
|
T[] opSlice(in size_t start, in size_t end)
|
|
{
|
|
return buffer_[this.start + start .. this.start + end];
|
|
}
|
|
|
|
/**
|
|
* Returns a free chunk of the buffer.
|
|
*
|
|
* Add ($(D_KEYWORD +=)) the number of the read bytes after using it.
|
|
*
|
|
* Returns: A free chunk of the buffer.
|
|
*/
|
|
T[] opIndex()
|
|
{
|
|
if (start > 0)
|
|
{
|
|
auto ret = buffer_[0 .. start];
|
|
ring = 0;
|
|
return ret;
|
|
}
|
|
else
|
|
{
|
|
if (capacity - length < minAvailable)
|
|
{
|
|
void[] buf = buffer_;
|
|
immutable cap = capacity;
|
|
() @trusted {
|
|
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
|
|
buffer_ = cast(T[]) buf;
|
|
}();
|
|
}
|
|
ring = length_;
|
|
return buffer_[length_ .. $];
|
|
}
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
ReadBuffer!ubyte b;
|
|
size_t numberRead;
|
|
ubyte[] result;
|
|
|
|
// Fills the buffer with values 0..10
|
|
numberRead = fillBuffer(b[], b.free, 0, 10);
|
|
b += numberRead;
|
|
|
|
assert(b.length == 10);
|
|
result = b[0 .. $];
|
|
assert(result[0] == 0);
|
|
assert(result[9] == 9);
|
|
b.clear();
|
|
assert(b.length == 0);
|
|
}
|
|
|
|
mixin DefaultAllocator;
|
|
}
|
|
|
|
private unittest
|
|
{
|
|
static assert(is(ReadBuffer!int));
|
|
}
|
|
|
|
/**
|
|
* Circular, self-expanding buffer with overflow support. Can be used with
|
|
* functions returning the number of the transferred bytes.
|
|
*
|
|
* The buffer is optimized for situations where you read all the data from it
|
|
* at once (without writing to it occasionally). It can become ineffective if
|
|
* you permanently keep some data in the buffer and alternate writing and
|
|
* reading, because it may allocate and move elements.
|
|
*
|
|
* Params:
|
|
* T = Buffer type.
|
|
*/
|
|
struct WriteBuffer(T = ubyte)
|
|
if (isScalarType!T)
|
|
{
|
|
/// Internal buffer.
|
|
private T[] buffer_;
|
|
|
|
/// Buffer start position.
|
|
private size_t start;
|
|
|
|
/// Buffer ring area size. After this position begins buffer overflow area.
|
|
private size_t ring;
|
|
|
|
/// Size by which the buffer will grow.
|
|
private immutable size_t blockSize;
|
|
|
|
/// The position of the free area in the buffer.
|
|
private size_t position;
|
|
|
|
invariant
|
|
{
|
|
assert(blockSize > 0);
|
|
// Position can refer to an element outside the buffer if the buffer is full.
|
|
assert(position <= buffer_.length);
|
|
}
|
|
|
|
/**
|
|
* Params:
|
|
* size = Initial buffer size and the size by which the buffer will
|
|
* grow.
|
|
* allocator = Allocator.
|
|
*
|
|
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
|
|
*/
|
|
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
|
|
in
|
|
{
|
|
assert(size > 0);
|
|
assert(allocator !is null);
|
|
}
|
|
body
|
|
{
|
|
blockSize = size;
|
|
ring = size - 1;
|
|
allocator_ = allocator;
|
|
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
|
}
|
|
|
|
@disable this();
|
|
|
|
/**
|
|
* Deallocates the internal buffer.
|
|
*/
|
|
~this()
|
|
{
|
|
allocator.deallocate(buffer_);
|
|
}
|
|
|
|
/**
|
|
* Returns: The size of the internal buffer.
|
|
*/
|
|
@property size_t capacity() const
|
|
{
|
|
return buffer_.length;
|
|
}
|
|
|
|
/**
|
|
* Note that $(D_PSYMBOL length) doesn't return the real length of the data,
|
|
* but only the array length that will be returned with $(D_PSYMBOL opIndex)
|
|
* next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
|
|
* until $(D_PSYMBOL length) returns 0.
|
|
*
|
|
* Returns: Data size.
|
|
*/
|
|
@property size_t length() const
|
|
{
|
|
if (position > ring || position < start) // Buffer overflowed
|
|
{
|
|
return ring - start + 1;
|
|
}
|
|
else
|
|
{
|
|
return position - start;
|
|
}
|
|
}
|
|
|
|
/// Ditto.
|
|
alias opDollar = length;
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto b = WriteBuffer!ubyte(4);
|
|
ubyte[3] buf = [48, 23, 255];
|
|
|
|
b ~= buf;
|
|
assert(b.length == 3);
|
|
b += 2;
|
|
assert(b.length == 1);
|
|
|
|
b ~= buf;
|
|
assert(b.length == 2);
|
|
b += 2;
|
|
assert(b.length == 2);
|
|
|
|
b ~= buf;
|
|
assert(b.length == 5);
|
|
b += b.length;
|
|
assert(b.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Returns: Available space.
|
|
*/
|
|
@property size_t free() const
|
|
{
|
|
return capacity - length;
|
|
}
|
|
|
|
/**
|
|
* Appends data to the buffer.
|
|
*
|
|
* Params:
|
|
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
|
|
*/
|
|
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
|
|
if (op == "~")
|
|
{
|
|
size_t end, start;
|
|
|
|
if (position >= this.start && position <= ring)
|
|
{
|
|
auto afterRing = ring + 1;
|
|
|
|
end = position + buffer.length;
|
|
if (end > afterRing)
|
|
{
|
|
end = afterRing;
|
|
}
|
|
start = end - position;
|
|
buffer_[position .. end] = buffer[0 .. start];
|
|
if (end == afterRing)
|
|
{
|
|
position = this.start == 0 ? afterRing : 0;
|
|
}
|
|
else
|
|
{
|
|
position = end;
|
|
}
|
|
}
|
|
|
|
// Check if we have some free space at the beginning
|
|
if (start < buffer.length && position < this.start)
|
|
{
|
|
end = position + buffer.length - start;
|
|
if (end > this.start)
|
|
{
|
|
end = this.start;
|
|
}
|
|
auto areaEnd = end - position + start;
|
|
buffer_[position .. end] = buffer[start .. areaEnd];
|
|
position = end == this.start ? ring + 1 : end - position;
|
|
start = areaEnd;
|
|
}
|
|
|
|
// And if we still haven't found any place, save the rest in the overflow area
|
|
if (start < buffer.length)
|
|
{
|
|
end = position + buffer.length - start;
|
|
if (end > capacity)
|
|
{
|
|
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
|
|
() @trusted {
|
|
void[] buf = buffer_;
|
|
allocator.reallocate(buf, newSize);
|
|
buffer_ = cast(T[]) buf;
|
|
}();
|
|
}
|
|
buffer_[position .. end] = buffer[start .. $];
|
|
position = end;
|
|
if (this.start == 0)
|
|
{
|
|
ring = capacity - 1;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto b = WriteBuffer!ubyte(4);
|
|
ubyte[3] buf = [48, 23, 255];
|
|
|
|
b ~= buf;
|
|
assert(b.capacity == 4);
|
|
assert(b.buffer_[0] == 48 && b.buffer_[1] == 23 && b.buffer_[2] == 255);
|
|
|
|
b += 2;
|
|
b ~= buf;
|
|
assert(b.capacity == 4);
|
|
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
|
&& b.buffer_[2] == 255 && b.buffer_[3] == 48);
|
|
|
|
b += 2;
|
|
b ~= buf;
|
|
assert(b.capacity == 8);
|
|
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
|
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto b = WriteBuffer!ubyte(2);
|
|
ubyte[3] buf = [48, 23, 255];
|
|
|
|
b ~= buf;
|
|
assert(b.start == 0);
|
|
assert(b.capacity == 4);
|
|
assert(b.ring == 3);
|
|
assert(b.position == 3);
|
|
}
|
|
|
|
/**
|
|
* Sets how many bytes were written. It will shrink the buffer
|
|
* appropriately. Always call it after $(D_PSYMBOL opIndex).
|
|
*
|
|
* Params:
|
|
* length = Length of the written data.
|
|
*
|
|
* Returns: $(D_KEYWORD this).
|
|
*/
|
|
ref WriteBuffer opOpAssign(string op)(in size_t length)
|
|
if (op == "+")
|
|
in
|
|
{
|
|
assert(length <= this.length);
|
|
}
|
|
body
|
|
{
|
|
auto afterRing = ring + 1;
|
|
auto oldStart = start;
|
|
|
|
if (length <= 0)
|
|
{
|
|
return this;
|
|
}
|
|
else if (position <= afterRing)
|
|
{
|
|
start += length;
|
|
if (start > 0 && position == afterRing)
|
|
{
|
|
position = oldStart;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
auto overflow = position - afterRing;
|
|
|
|
if (overflow > length)
|
|
{
|
|
immutable afterLength = afterRing + length;
|
|
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
|
|
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
|
|
position -= length;
|
|
}
|
|
else if (overflow == length)
|
|
{
|
|
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
|
|
position -= overflow;
|
|
}
|
|
else
|
|
{
|
|
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
|
|
position = overflow;
|
|
}
|
|
start += length;
|
|
|
|
if (start == position)
|
|
{
|
|
if (position != afterRing)
|
|
{
|
|
position = 0;
|
|
}
|
|
start = 0;
|
|
ring = capacity - 1;
|
|
}
|
|
}
|
|
if (start > ring)
|
|
{
|
|
start = 0;
|
|
}
|
|
return this;
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto b = WriteBuffer!ubyte(6);
|
|
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
|
|
|
b ~= buf;
|
|
assert(b.length == 6);
|
|
b += 2;
|
|
assert(b.length == 4);
|
|
b += 4;
|
|
assert(b.length == 0);
|
|
}
|
|
|
|
/**
|
|
* Returns a chunk with data.
|
|
*
|
|
* After calling it, set $(D_KEYWORD +=) to the length could be
|
|
* written.
|
|
*
|
|
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
|
* to call it and set $(D_KEYWORD +=) several times until
|
|
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
|
* maximally 3 calls are required.
|
|
*
|
|
* Returns: A chunk of data buffer.
|
|
*/
|
|
T[] opSlice(in size_t start, in size_t end)
|
|
{
|
|
immutable internStart = this.start + start;
|
|
|
|
if (position > ring || position < start) // Buffer overflowed
|
|
{
|
|
return buffer_[this.start .. ring + 1 - length + end];
|
|
}
|
|
else
|
|
{
|
|
return buffer_[this.start .. this.start + end];
|
|
}
|
|
}
|
|
|
|
///
|
|
unittest
|
|
{
|
|
auto b = WriteBuffer!ubyte(6);
|
|
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
|
|
|
b ~= buf;
|
|
assert(b[0 .. $] == buf[0 .. 6]);
|
|
b += 2;
|
|
|
|
assert(b[0 .. $] == buf[2 .. 6]);
|
|
|
|
b ~= buf;
|
|
assert(b[0 .. $] == buf[2 .. 6]);
|
|
b += b.length;
|
|
|
|
assert(b[0 .. $] == buf[0 .. 6]);
|
|
b += b.length;
|
|
}
|
|
|
|
/**
|
|
* After calling it, set $(D_KEYWORD +=) to the length could be
|
|
* written.
|
|
*
|
|
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
|
* to call it and set $(D_KEYWORD +=) several times until
|
|
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
|
* maximally 3 calls are required.
|
|
*
|
|
* Returns: A chunk of data buffer.
|
|
*/
|
|
T[] opIndex()
|
|
{
|
|
return opSlice(0, length);
|
|
}
|
|
|
|
mixin DefaultAllocator;
|
|
}
|
|
|
|
private unittest
|
|
{
|
|
static assert(is(typeof(WriteBuffer!int(5))));
|
|
}
|