Add code
This commit is contained in:
parent
c0df3c9330
commit
a3efee6d7f
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
.dub
|
||||
__test__*__
|
17
dub.json
Normal file
17
dub.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "tanya",
|
||||
"description": "D library with event loop",
|
||||
"license": "MPL-2.0",
|
||||
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
||||
"authors": [
|
||||
"Eugene Wissner"
|
||||
],
|
||||
|
||||
"targetType": "library",
|
||||
|
||||
"configurations": [
|
||||
{
|
||||
"name": "library"
|
||||
}
|
||||
]
|
||||
}
|
641
source/tanya/container/buffer.d
Normal file
641
source/tanya/container/buffer.d
Normal file
@ -0,0 +1,641 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.container.buffer;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
@nogc:
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
private int fillBuffer(void* buffer,
|
||||
in size_t size,
|
||||
int start = 0,
|
||||
int end = 10)
|
||||
in
|
||||
{
|
||||
assert(start < end);
|
||||
}
|
||||
body
|
||||
{
|
||||
ubyte[] buf = cast(ubyte[]) buffer[0..size];
|
||||
auto numberRead = end - start;
|
||||
|
||||
for (ubyte i; i < numberRead; ++i)
|
||||
{
|
||||
buf[i] = cast(ubyte) (start + i);
|
||||
}
|
||||
return numberRead;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for implemeting input/output buffers.
|
||||
*/
|
||||
interface Buffer
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Appends some data to the buffer.
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
*/
|
||||
Buffer opOpAssign(string op)(void[] buffer)
|
||||
if (op == "~");
|
||||
}
|
||||
|
||||
/**
|
||||
* Buffer that can be used with C functions accepting void pointer and
|
||||
* returning the number of the read bytes.
|
||||
*/
|
||||
class ReadBuffer : Buffer
|
||||
{
|
||||
@nogc:
|
||||
/// Internal buffer.
|
||||
protected ubyte[] _buffer;
|
||||
|
||||
/// Filled buffer length.
|
||||
protected size_t _length;
|
||||
|
||||
/// Available space.
|
||||
protected immutable size_t minAvailable;
|
||||
|
||||
/// Size by which the buffer will grow.
|
||||
protected immutable size_t blockSize;
|
||||
|
||||
private Allocator allocator;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(_length <= _buffer.length);
|
||||
assert(blockSize > 0);
|
||||
assert(minAvailable > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
* ).
|
||||
*/
|
||||
this(size_t size = 8192,
|
||||
size_t minAvailable = 1024,
|
||||
Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
this.minAvailable = minAvailable;
|
||||
this.blockSize = size;
|
||||
resizeArray!ubyte(this.allocator, _buffer, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates the internal buffer.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, _buffer);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
assert(b.capacity == 8192);
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow
|
||||
{
|
||||
return _buffer.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow
|
||||
{
|
||||
return _length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow
|
||||
{
|
||||
return capacity - length;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
size_t numberRead;
|
||||
void* buf;
|
||||
|
||||
// Fills the buffer with values 0..10
|
||||
assert(b.free == b.blockSize);
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 0, 10);
|
||||
b ~= buf[0..numberRead];
|
||||
assert(b.free == b.blockSize - numberRead);
|
||||
b[];
|
||||
assert(b.free == b.blockSize);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to a chunk of the internal buffer. You can pass it to
|
||||
* a function that requires such a buffer.
|
||||
*
|
||||
* Set the buffer again after reading something into it. Append
|
||||
* $(D_KEYWORD ~=) a slice from the beginning of the buffer you got and
|
||||
* till the number of the read bytes. The data will be appended to the
|
||||
* existing buffer.
|
||||
*
|
||||
* Returns: A chunk of available buffer.
|
||||
*/
|
||||
@property void* buffer()
|
||||
{
|
||||
if (capacity - length < minAvailable)
|
||||
{
|
||||
resizeArray!ubyte(this.allocator, _buffer, capacity + blockSize);
|
||||
}
|
||||
return _buffer[_length..$].ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends some data to the buffer. Use only the buffer you got
|
||||
* with $(D_PSYMBOL buffer)!
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
*/
|
||||
ReadBuffer opOpAssign(string op)(void[] buffer)
|
||||
if (op == "~")
|
||||
{
|
||||
_length += buffer.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
size_t numberRead;
|
||||
void* buf;
|
||||
ubyte[] result;
|
||||
|
||||
// Fills the buffer with values 0..10
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 0, 10);
|
||||
b ~= buf[0..numberRead];
|
||||
|
||||
result = b[];
|
||||
assert(result[0] == 0);
|
||||
assert(result[1] == 1);
|
||||
assert(result[9] == 9);
|
||||
|
||||
// It shouldn't overwrite, but append another 5 bytes to the buffer
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 0, 10);
|
||||
b ~= buf[0..numberRead];
|
||||
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 20, 25);
|
||||
b ~= buf[0..numberRead];
|
||||
|
||||
result = b[];
|
||||
assert(result[0] == 0);
|
||||
assert(result[1] == 1);
|
||||
assert(result[9] == 9);
|
||||
assert(result[10] == 20);
|
||||
assert(result[14] == 24);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the buffer. The buffer is cleared after that. So you can get it
|
||||
* only one time.
|
||||
*
|
||||
* Returns: The buffer as array.
|
||||
*/
|
||||
@property ubyte[] opIndex()
|
||||
{
|
||||
auto ret = _buffer[0.._length];
|
||||
_length = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!ReadBuffer(defaultAllocator);
|
||||
size_t numberRead;
|
||||
void* buf;
|
||||
ubyte[] result;
|
||||
|
||||
// Fills the buffer with values 0..10
|
||||
buf = b.buffer;
|
||||
numberRead = fillBuffer(buf, b.free, 0, 10);
|
||||
b ~= buf[0..numberRead];
|
||||
|
||||
assert(b.length == 10);
|
||||
result = b[];
|
||||
assert(result[0] == 0);
|
||||
assert(result[9] == 9);
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Circular, self-expanding buffer that can be used with C functions accepting
|
||||
* void pointer and returning the number of the read 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.
|
||||
*/
|
||||
class WriteBuffer : Buffer
|
||||
{
|
||||
@nogc:
|
||||
/// Internal buffer.
|
||||
protected ubyte[] _buffer;
|
||||
|
||||
/// Buffer start position.
|
||||
protected size_t start;
|
||||
|
||||
/// Buffer ring area size. After this position begins buffer overflow area.
|
||||
protected size_t ring;
|
||||
|
||||
/// Size by which the buffer will grow.
|
||||
protected immutable size_t blockSize;
|
||||
|
||||
/// The position of the free area in the buffer.
|
||||
protected size_t position;
|
||||
|
||||
private Allocator allocator;
|
||||
|
||||
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.
|
||||
*/
|
||||
this(size_t size = 8192,
|
||||
Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
blockSize = size;
|
||||
ring = size - 1;
|
||||
resizeArray!ubyte(this.allocator, _buffer, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates the internal buffer.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, _buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @safe pure nothrow
|
||||
{
|
||||
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 buffer)
|
||||
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_PSYMBOL written)
|
||||
* until $(D_PSYMBOL length) returns 0.
|
||||
*
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @safe pure nothrow
|
||||
{
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
return ring - start + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return position - start;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 4);
|
||||
ubyte[3] buf = [48, 23, 255];
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 3);
|
||||
b.written = 2;
|
||||
assert(b.length == 1);
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 2);
|
||||
b.written = 2;
|
||||
assert(b.length == 2);
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 5);
|
||||
b.written = b.length;
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @safe pure nothrow
|
||||
{
|
||||
return capacity - length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends data to the buffer.
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
*/
|
||||
WriteBuffer opOpAssign(string op)(ubyte[] 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;
|
||||
|
||||
resizeArray!ubyte(this.allocator, _buffer, newSize);
|
||||
}
|
||||
_buffer[position..end] = buffer[start..$];
|
||||
position = end;
|
||||
if (this.start == 0)
|
||||
{
|
||||
ring = capacity - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 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.written = 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.written = 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);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
|
||||
b = make!WriteBuffer(defaultAllocator, 2);
|
||||
|
||||
b ~= buf;
|
||||
assert(b.start == 0);
|
||||
assert(b.capacity == 4);
|
||||
assert(b.ring == 3);
|
||||
assert(b.position == 3);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many bytes were written. It will shrink the buffer
|
||||
* appropriately. Always set this property after calling
|
||||
* $(D_PSYMBOL buffer).
|
||||
*
|
||||
* Params:
|
||||
* length = Length of the written data.
|
||||
*/
|
||||
@property void written(size_t length) @safe pure nothrow
|
||||
in
|
||||
{
|
||||
assert(length <= this.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto afterRing = ring + 1;
|
||||
auto oldStart = start;
|
||||
|
||||
if (length <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (position <= afterRing)
|
||||
{
|
||||
start += length;
|
||||
if (start > 0 && position == afterRing)
|
||||
{
|
||||
position = oldStart;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto overflow = position - afterRing;
|
||||
|
||||
if (overflow > length) {
|
||||
_buffer[start.. start + length] = _buffer[afterRing.. afterRing + length];
|
||||
_buffer[afterRing.. afterRing + length] = _buffer[afterRing + length ..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;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator);
|
||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||
|
||||
b ~= buf;
|
||||
assert(b.length == 6);
|
||||
b.written = 2;
|
||||
assert(b.length == 4);
|
||||
b.written = 4;
|
||||
assert(b.length == 0);
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pointer to a buffer chunk with data. You can pass it to
|
||||
* a function that requires such a buffer.
|
||||
*
|
||||
* After calling it, set $(D_PSYMBOL written) to the length could be
|
||||
* written.
|
||||
*
|
||||
* $(D_PSYMBOL buffer) may return only part of the data. You may need
|
||||
* to call it (and set $(D_PSYMBOL written) 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.
|
||||
*/
|
||||
@property void* buffer() @safe pure nothrow
|
||||
{
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
return _buffer[start.. ring + 1].ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _buffer[start..position].ptr;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = make!WriteBuffer(defaultAllocator, 6);
|
||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||
void* returnedBuf;
|
||||
|
||||
b ~= buf;
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[0..6]);
|
||||
b.written = 2;
|
||||
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[2..6]);
|
||||
|
||||
b ~= buf;
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[2..6]);
|
||||
b.written = b.length;
|
||||
|
||||
returnedBuf = b.buffer;
|
||||
assert(returnedBuf[0..b.length] == buf[0..6]);
|
||||
b.written = b.length;
|
||||
|
||||
finalize(defaultAllocator, b);
|
||||
}
|
||||
}
|
405
source/tanya/container/list.d
Normal file
405
source/tanya/container/list.d
Normal file
@ -0,0 +1,405 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.list;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Singly linked list.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class SList(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL SList).
|
||||
*
|
||||
* Params:
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the list.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
while (!empty)
|
||||
{
|
||||
static if (isFinalizable!T)
|
||||
{
|
||||
finalize(allocator, front);
|
||||
}
|
||||
popFront();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: First element.
|
||||
*/
|
||||
@property ref T front()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return first.next.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element at the beginning.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
@property void front(T x)
|
||||
{
|
||||
Entry* temp = make!Entry(allocator);
|
||||
|
||||
temp.content = x;
|
||||
temp.next = first.next;
|
||||
first.next = temp;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[2] values = [8, 9];
|
||||
|
||||
l.front = values[0];
|
||||
assert(l.front == values[0]);
|
||||
l.front = values[1];
|
||||
assert(l.front == values[1]);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element at the beginning.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) opOpAssign(string Op)(ref T x)
|
||||
if (Op == "~")
|
||||
{
|
||||
front = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int value = 5;
|
||||
|
||||
assert(l.empty);
|
||||
|
||||
l ~= value;
|
||||
|
||||
assert(l.front == value);
|
||||
assert(!l.empty);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the list is empty.
|
||||
*/
|
||||
@property bool empty() const @safe pure nothrow
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first element and moves to the next one.
|
||||
*
|
||||
* Returns: The first element.
|
||||
*/
|
||||
T popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
auto content = first.next.content;
|
||||
|
||||
finalize(allocator, first.next);
|
||||
first.next = n;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[2] values = [8, 9];
|
||||
|
||||
l.front = values[0];
|
||||
l.front = values[1];
|
||||
assert(l.front == values[1]);
|
||||
l.popFront();
|
||||
assert(l.front == values[0]);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current item from the list and removes from the list.
|
||||
*
|
||||
* Params:
|
||||
* x = The item should be removed.
|
||||
*
|
||||
* Returns: Removed item.
|
||||
*/
|
||||
T remove()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto temp = position.next.next;
|
||||
auto content = position.next.content;
|
||||
|
||||
finalize(allocator, position.next);
|
||||
position.next = temp;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[3] values = [8, 5, 4];
|
||||
|
||||
l.front = values[0];
|
||||
l.front = values[1];
|
||||
assert(l.remove() == 5);
|
||||
l.front = values[2];
|
||||
assert(l.remove() == 4);
|
||||
assert(l.remove() == 8);
|
||||
assert(l.empty);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the current position.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) reset()
|
||||
{
|
||||
position = &first;
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[2] values = [8, 5];
|
||||
|
||||
l.current = values[0];
|
||||
l.current = values[1];
|
||||
assert(l.current == 5);
|
||||
l.advance();
|
||||
assert(l.current == 8);
|
||||
l.reset();
|
||||
assert(l.current == 5);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_KEYWORD foreach) iteration.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*/
|
||||
int opApply(int delegate(ref size_t i, ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
size_t i;
|
||||
|
||||
for (position = first.next; position; position = position.next, ++i)
|
||||
{
|
||||
result = dg(i, position.content);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[3] values = [5, 4, 9];
|
||||
|
||||
l.front = values[0];
|
||||
l.front = values[1];
|
||||
l.front = values[2];
|
||||
foreach (i, e; l)
|
||||
{
|
||||
assert(i != 0 || e == values[2]);
|
||||
assert(i != 1 || e == values[1]);
|
||||
assert(i != 2 || e == values[0]);
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(int delegate(ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
for (position = first.next; position; position = position.next)
|
||||
{
|
||||
result = dg(position.content);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(defaultAllocator);
|
||||
int[3] values = [5, 4, 9];
|
||||
size_t i;
|
||||
|
||||
l.front = values[0];
|
||||
l.front = values[1];
|
||||
l.front = values[2];
|
||||
foreach (e; l)
|
||||
{
|
||||
assert(i != 0 || e == values[2]);
|
||||
assert(i != 1 || e == values[1]);
|
||||
assert(i != 2 || e == values[0]);
|
||||
++i;
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the current position is the end position.
|
||||
*/
|
||||
@property bool end() const
|
||||
{
|
||||
return empty || position.next.next is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves to the next element and returns it.
|
||||
*
|
||||
* Returns: The element on the next position.
|
||||
*/
|
||||
T advance()
|
||||
in
|
||||
{
|
||||
assert(!end);
|
||||
}
|
||||
body
|
||||
{
|
||||
position = position.next;
|
||||
return position.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Element on the current position.
|
||||
*/
|
||||
@property ref T current()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return position.next.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element at the current position.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
@property void current(T x)
|
||||
{
|
||||
Entry* temp = make!Entry(allocator);
|
||||
|
||||
temp.content = x;
|
||||
temp.next = position.next;
|
||||
position.next = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* List entry.
|
||||
*/
|
||||
protected struct Entry
|
||||
{
|
||||
/// List item content.
|
||||
T content;
|
||||
|
||||
/// Next list item.
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
/// 0th element of the list.
|
||||
protected Entry first;
|
||||
|
||||
/// Current position in the list.
|
||||
protected Entry* position;
|
||||
|
||||
private Allocator allocator;
|
||||
}
|
||||
|
||||
interface Stuff
|
||||
{
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!Stuff)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, l);
|
||||
}
|
16
source/tanya/container/package.d
Normal file
16
source/tanya/container/package.d
Normal file
@ -0,0 +1,16 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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;
|
||||
|
||||
public import tanya.container.buffer;
|
||||
public import tanya.container.list;
|
||||
public import tanya.container.vector;
|
||||
public import tanya.container.queue;
|
219
source/tanya/container/queue.d
Normal file
219
source/tanya/container/queue.d
Normal file
@ -0,0 +1,219 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.queue;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Queue.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class Queue(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Queue).
|
||||
*
|
||||
* Params:
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
foreach (e; this)
|
||||
{
|
||||
static if (isFinalizable!T)
|
||||
{
|
||||
finalize(allocator, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: First element.
|
||||
*/
|
||||
@property ref T front()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return first.next.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) insertBack(T x)
|
||||
{
|
||||
Entry* temp = make!Entry(allocator);
|
||||
|
||||
temp.content = x;
|
||||
|
||||
if (empty)
|
||||
{
|
||||
first.next = rear = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
rear.next = temp;
|
||||
rear = rear.next;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
alias insert = insertBack;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
int[2] values = [8, 9];
|
||||
|
||||
q.insertBack(values[0]);
|
||||
assert(q.front is values[0]);
|
||||
q.insertBack(values[1]);
|
||||
assert(q.front is values[0]);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) opOpAssign(string Op)(ref T x)
|
||||
if (Op == "~")
|
||||
{
|
||||
return insertBack(x);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
int value = 5;
|
||||
|
||||
assert(q.empty);
|
||||
|
||||
q ~= value;
|
||||
|
||||
assert(q.front == value);
|
||||
assert(!q.empty);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() const @safe pure nothrow
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
int value = 7;
|
||||
|
||||
assert(q.empty);
|
||||
q.insertBack(value);
|
||||
assert(!q.empty);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move position to the next element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
|
||||
finalize(allocator, first.next);
|
||||
first.next = n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
int[2] values = [8, 9];
|
||||
|
||||
q.insertBack(values[0]);
|
||||
q.insertBack(values[1]);
|
||||
assert(q.front is values[0]);
|
||||
q.popFront();
|
||||
assert(q.front is values[1]);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue entry.
|
||||
*/
|
||||
protected struct Entry
|
||||
{
|
||||
/// Queue item content.
|
||||
T content;
|
||||
|
||||
/// Next list item.
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
/// The first element of the list.
|
||||
protected Entry first;
|
||||
|
||||
/// The last element of the list.
|
||||
protected Entry* rear;
|
||||
|
||||
private Allocator allocator;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, q);
|
||||
}
|
420
source/tanya/container/vector.d
Normal file
420
source/tanya/container/vector.d
Normal file
@ -0,0 +1,420 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.vector;
|
||||
|
||||
import tanya.memory;
|
||||
|
||||
@nogc:
|
||||
|
||||
/**
|
||||
* One dimensional array. It allocates automatically if needed.
|
||||
*
|
||||
* If you assign a value:
|
||||
* ---
|
||||
* auto v = make!(Vector!int)(defaultAllocator);
|
||||
* int value = 5;
|
||||
*
|
||||
* v[1000] = value;
|
||||
*
|
||||
* finalize(defaultAllocator, v);
|
||||
* ---
|
||||
* it will allocate not only for one, but for 1000 elements. So this
|
||||
* implementation is more suitable for sequential data with random access.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class Vector(T)
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Vector).
|
||||
*
|
||||
* Params:
|
||||
* length = Initial length.
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(size_t length, Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
vector = makeArray!T(allocator, length);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this(0, allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the vector.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(allocator, vector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Vector length.
|
||||
*/
|
||||
@property size_t length() const
|
||||
{
|
||||
return vector.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expans/shrinks the vector.
|
||||
*
|
||||
* Params:
|
||||
* length = New length.
|
||||
*/
|
||||
@property void length(size_t length)
|
||||
{
|
||||
resizeArray!T(allocator, vector, length);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator);
|
||||
|
||||
v.length = 5;
|
||||
assert(v.length == 5);
|
||||
|
||||
// TODO
|
||||
v.length = 7;
|
||||
assert(v.length == 7);
|
||||
|
||||
v.length = 0;
|
||||
assert(v.length == 0);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the vector is empty.
|
||||
*/
|
||||
@property bool empty() const
|
||||
{
|
||||
return length == 0;
|
||||
}
|
||||
|
||||
static if (isFinalizable!T)
|
||||
{
|
||||
/**
|
||||
* Removes an elements from the vector.
|
||||
*
|
||||
* Params:
|
||||
* pos = Element index.
|
||||
*/
|
||||
void remove(size_t pos)
|
||||
{
|
||||
auto el = vector[pos];
|
||||
finalize(allocator, el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a value. Allocates if needed.
|
||||
*
|
||||
* Params:
|
||||
* value = Value.
|
||||
*
|
||||
* Returns: Assigned value.
|
||||
*/
|
||||
T opIndexAssign(T value, size_t pos)
|
||||
{
|
||||
if (pos >= length)
|
||||
{
|
||||
resizeArray!T(allocator, vector, pos + 1);
|
||||
}
|
||||
return vector[pos] = value;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
assert(v.length == 0);
|
||||
v[1] = values[0];
|
||||
assert(v.length == 2);
|
||||
v[3] = values[0];
|
||||
assert(v.length == 4);
|
||||
v[4] = values[1];
|
||||
assert(v.length == 5);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The value on index $(D_PARAM pos).
|
||||
*/
|
||||
ref T opIndex(in size_t pos)
|
||||
in
|
||||
{
|
||||
assert(length > pos);
|
||||
}
|
||||
body
|
||||
{
|
||||
return vector[pos];
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
v[1] = values[0];
|
||||
assert(v[1] is values[0]);
|
||||
v[3] = values[0];
|
||||
assert(v[3] is values[0]);
|
||||
v[4] = values[1];
|
||||
assert(v[4] is values[1]);
|
||||
v[0] = values[1];
|
||||
assert(v[0] is values[1]);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_KEYWORD foreach) iteration.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*/
|
||||
int opApply(int delegate(ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
foreach (e; vector)
|
||||
{
|
||||
result = dg(e);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(int delegate(ref size_t i, ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
foreach (i, e; vector)
|
||||
{
|
||||
result = dg(i, e);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator, 1);
|
||||
int[3] values = [5, 15, 8];
|
||||
|
||||
v[0] = values[0];
|
||||
v[1] = values[1];
|
||||
v[2] = values[2];
|
||||
|
||||
int i;
|
||||
foreach (e; v)
|
||||
{
|
||||
assert(i != 0 || e is values[0]);
|
||||
assert(i != 1 || e is values[1]);
|
||||
assert(i != 2 || e is values[2]);
|
||||
++i;
|
||||
}
|
||||
|
||||
foreach (j, e; v)
|
||||
{
|
||||
assert(j != 0 || e is values[0]);
|
||||
assert(j != 1 || e is values[1]);
|
||||
assert(j != 2 || e is values[2]);
|
||||
}
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first element. Allocates if the vector is empty.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
@property void front(ref T x)
|
||||
{
|
||||
this[0] = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The first element.
|
||||
*/
|
||||
@property ref inout(T) front() inout
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return vector[0];
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator, 1);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
v.front = values[0];
|
||||
assert(v.front == 5);
|
||||
|
||||
v.front = values[1];
|
||||
assert(v.front == 15);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move position to the next element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
vector[0 .. $ - 1] = vector[1..$];
|
||||
resizeArray(allocator, vector, length - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator, 1);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
v[0] = values[0];
|
||||
v[1] = values[1];
|
||||
assert(v.front is values[0]);
|
||||
assert(v.length == 2);
|
||||
v.popFront();
|
||||
assert(v.front is values[1]);
|
||||
assert(v.length == 1);
|
||||
v.popFront();
|
||||
assert(v.empty);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the last element. Allocates if the vector is empty.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
@property void back(ref T x)
|
||||
{
|
||||
vector[empty ? 0 : $ - 1] = x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The last element.
|
||||
*/
|
||||
@property ref inout(T) back() inout
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return vector[$ - 1];
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator, 1);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
v.back = values[0];
|
||||
assert(v.back == 5);
|
||||
|
||||
v.back = values[1];
|
||||
assert(v.back == 15);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move position to the previous element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) popBack()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
resizeArray(allocator, vector, length - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator, 1);
|
||||
int[2] values = [5, 15];
|
||||
|
||||
v[0] = values[0];
|
||||
v[1] = values[1];
|
||||
assert(v.back is values[1]);
|
||||
assert(v.length == 2);
|
||||
v.popBack();
|
||||
assert(v.back is values[0]);
|
||||
assert(v.length == 1);
|
||||
v.popBack();
|
||||
assert(v.empty);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
||||
|
||||
/// Container.
|
||||
protected T[] vector;
|
||||
|
||||
private Allocator allocator;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto v = make!(Vector!int)(defaultAllocator);
|
||||
|
||||
finalize(defaultAllocator, v);
|
||||
}
|
191
source/tanya/crypto/padding.d
Normal file
191
source/tanya/crypto/padding.d
Normal file
@ -0,0 +1,191 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.crypto.padding;
|
||||
|
||||
import tanya.memory;
|
||||
import std.algorithm.iteration;
|
||||
import std.typecons;
|
||||
|
||||
@nogc:
|
||||
|
||||
/**
|
||||
* Supported padding mode.
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL applyPadding)
|
||||
*/
|
||||
enum Mode
|
||||
{
|
||||
zero,
|
||||
pkcs7,
|
||||
ansiX923,
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* allocator = Allocator that should be used if the block should be extended
|
||||
* or a new block should be added.
|
||||
* input = Sequence that should be padded.
|
||||
* mode = Padding mode.
|
||||
* blockSize = Block size.
|
||||
*
|
||||
* Returns: The functions modifies the initial array and returns it.
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL Mode)
|
||||
*/
|
||||
ubyte[] applyPadding(ref ubyte[] input,
|
||||
in Mode mode,
|
||||
in ushort blockSize,
|
||||
Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(blockSize > 0 && blockSize <= 256);
|
||||
assert(blockSize % 64 == 0);
|
||||
assert(input.length > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
immutable rest = cast(ubyte) input.length % blockSize;
|
||||
immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize);
|
||||
immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0);
|
||||
|
||||
final switch (mode) with (Mode)
|
||||
{
|
||||
case zero:
|
||||
allocator.expandArray(input, needed);
|
||||
break;
|
||||
case pkcs7:
|
||||
if (needed)
|
||||
{
|
||||
allocator.expandArray(input, needed);
|
||||
input[input.length - needed ..$].each!((ref e) => e = needed);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.expandArray(input, blockSize);
|
||||
}
|
||||
break;
|
||||
case ansiX923:
|
||||
allocator.expandArray(input, needed ? needed : blockSize);
|
||||
input[$ - 1] = needed;
|
||||
break;
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
{ // Zeros
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
|
||||
applyPadding(input, Mode.zero, 64);
|
||||
assert(input.length == 64);
|
||||
|
||||
applyPadding(input, Mode.zero, 64);
|
||||
assert(input.length == 64);
|
||||
assert(input[63] == 0);
|
||||
|
||||
defaultAllocator.finalize(input);
|
||||
}
|
||||
{ // PKCS#7
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
applyPadding(input, Mode.pkcs7, 64);
|
||||
assert(input.length == 64);
|
||||
for (ubyte i; i < 64; ++i)
|
||||
{
|
||||
if (i >= 40 && i < 50)
|
||||
{
|
||||
assert(input[i] == 0);
|
||||
}
|
||||
else if (i >= 50)
|
||||
{
|
||||
assert(input[i] == 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(input[i] == i);
|
||||
}
|
||||
}
|
||||
|
||||
applyPadding(input, Mode.pkcs7, 64);
|
||||
assert(input.length == 128);
|
||||
for (ubyte i; i < 128; ++i)
|
||||
{
|
||||
if (i >= 64 || (i >= 40 && i < 50))
|
||||
{
|
||||
assert(input[i] == 0);
|
||||
}
|
||||
else if (i >= 50 && i < 64)
|
||||
{
|
||||
assert(input[i] == 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(input[i] == i);
|
||||
}
|
||||
}
|
||||
|
||||
defaultAllocator.finalize(input);
|
||||
}
|
||||
{ // ANSI X.923
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
}
|
||||
|
||||
applyPadding(input, Mode.ansiX923, 64);
|
||||
assert(input.length == 64);
|
||||
for (ubyte i; i < 64; ++i)
|
||||
{
|
||||
if (i < 40)
|
||||
{
|
||||
assert(input[i] == i);
|
||||
}
|
||||
else if (i == 63)
|
||||
{
|
||||
assert(input[i] == 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(input[i] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
applyPadding(input, Mode.pkcs7, 64);
|
||||
assert(input.length == 128);
|
||||
for (ubyte i = 0; i < 128; ++i)
|
||||
{
|
||||
if (i < 40)
|
||||
{
|
||||
assert(input[i] == i);
|
||||
}
|
||||
else if (i == 63)
|
||||
{
|
||||
assert(input[i] == 14);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(input[i] == 0);
|
||||
}
|
||||
}
|
||||
|
||||
defaultAllocator.finalize(input);
|
||||
}
|
||||
}
|
26
source/tanya/event/config.d
Normal file
26
source/tanya/event/config.d
Normal file
@ -0,0 +1,26 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.config;
|
||||
|
||||
package version (DisableBackends)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
version (linux)
|
||||
{
|
||||
enum UseEpoll = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
enum UseEpoll = false;
|
||||
}
|
||||
}
|
226
source/tanya/event/internal/epoll.d
Normal file
226
source/tanya/event/internal/epoll.d
Normal file
@ -0,0 +1,226 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.internal.epoll;
|
||||
|
||||
import tanya.event.config;
|
||||
|
||||
static if (UseEpoll):
|
||||
|
||||
public import core.sys.linux.epoll;
|
||||
import tanya.event.internal.selector;
|
||||
import tanya.event.protocol;
|
||||
import tanya.event.transport;
|
||||
import tanya.event.watcher;
|
||||
import tanya.event.loop;
|
||||
import tanya.container.list;
|
||||
import tanya.memory;
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.fcntl;
|
||||
import core.sys.posix.netinet.in_;
|
||||
import core.time;
|
||||
import std.algorithm.comparison;
|
||||
|
||||
@nogc:
|
||||
|
||||
extern (C) nothrow
|
||||
{ // TODO: Make a pull request for Phobos to mark this extern functions as @nogc.
|
||||
int epoll_create1(int __flags);
|
||||
int epoll_ctl(int __epfd, int __op, int __fd, epoll_event *__event);
|
||||
int epoll_wait(int __epfd, epoll_event *__events, int __maxevents, int __timeout);
|
||||
int accept4(int, sockaddr*, socklen_t*, int flags);
|
||||
}
|
||||
|
||||
private enum maxEvents = 128;
|
||||
|
||||
class EpollLoop : Loop
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
{
|
||||
super();
|
||||
|
||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
epollEvents = makeArray!epoll_event(defaultAllocator, maxEvents).ptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(defaultAllocator, epollEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
protected override bool modify(int socket, EventMask oldEvents, EventMask events)
|
||||
{
|
||||
int op = EPOLL_CTL_DEL;
|
||||
epoll_event ev;
|
||||
|
||||
if (events == oldEvents)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (events && oldEvents)
|
||||
{
|
||||
op = EPOLL_CTL_MOD;
|
||||
}
|
||||
else if (events && !oldEvents)
|
||||
{
|
||||
op = EPOLL_CTL_ADD;
|
||||
}
|
||||
|
||||
ev.data.fd = socket;
|
||||
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
||||
| (events & Event.write ? EPOLLOUT : 0)
|
||||
| EPOLLET;
|
||||
|
||||
return epoll_ctl(fd, op, socket, &ev) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept incoming connections.
|
||||
*
|
||||
* Params:
|
||||
* protocolFactory = Protocol factory.
|
||||
* socket = Socket.
|
||||
*/
|
||||
protected override void acceptConnection(Protocol delegate() @nogc protocolFactory,
|
||||
int socket)
|
||||
{
|
||||
sockaddr_in client_addr;
|
||||
socklen_t client_len = client_addr.sizeof;
|
||||
int client = accept4(socket,
|
||||
cast(sockaddr *)&client_addr,
|
||||
&client_len,
|
||||
O_NONBLOCK);
|
||||
while (client >= 0)
|
||||
{
|
||||
auto transport = make!SocketTransport(defaultAllocator, this, client);
|
||||
IOWatcher connection;
|
||||
|
||||
if (connections.length > client)
|
||||
{
|
||||
connection = cast(IOWatcher) connections[client];
|
||||
// If it is a ConnectionWatcher
|
||||
if (connection is null && connections[client] !is null)
|
||||
{
|
||||
finalize(defaultAllocator, connections[client]);
|
||||
connections[client] = null;
|
||||
}
|
||||
}
|
||||
if (connection !is null)
|
||||
{
|
||||
connection(protocolFactory, transport);
|
||||
}
|
||||
else
|
||||
{
|
||||
connections[client] = make!IOWatcher(defaultAllocator,
|
||||
protocolFactory,
|
||||
transport);
|
||||
}
|
||||
|
||||
modify(client, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
|
||||
swapPendings.insertBack(connections[client]);
|
||||
|
||||
client = accept4(socket,
|
||||
cast(sockaddr *)&client_addr,
|
||||
&client_len,
|
||||
O_NONBLOCK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected override void poll()
|
||||
{
|
||||
// Don't block
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
auto eventCount = epoll_wait(fd, epollEvents, maxEvents, timeout);
|
||||
|
||||
if (eventCount < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw make!BadLoopException(defaultAllocator);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < eventCount; ++i)
|
||||
{
|
||||
epoll_event *ev = epollEvents + i;
|
||||
auto connection = cast(IOWatcher) connections[ev.data.fd];
|
||||
|
||||
if (connection is null)
|
||||
{
|
||||
swapPendings.insertBack(connections[ev.data.fd]);
|
||||
// acceptConnection(connections[ev.data.fd].protocol,
|
||||
// connections[ev.data.fd].socket);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto transport = cast(SocketTransport) connection.transport;
|
||||
assert(transport !is null);
|
||||
|
||||
if (ev.events & (EPOLLIN | EPOLLPRI | EPOLLERR | EPOLLHUP))
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!transport.receive())
|
||||
{
|
||||
}
|
||||
swapPendings.insertBack(connection);
|
||||
}
|
||||
catch (TransportException e)
|
||||
{
|
||||
swapPendings.insertBack(connection);
|
||||
finalize(defaultAllocator, e);
|
||||
}
|
||||
}
|
||||
else if (ev.events & (EPOLLOUT | EPOLLERR | EPOLLHUP))
|
||||
{
|
||||
transport.writeReady = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
override protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
{
|
||||
return min(super.blockTime, 1.dur!"seconds");
|
||||
}
|
||||
|
||||
private int fd;
|
||||
private epoll_event* epollEvents;
|
||||
}
|
217
source/tanya/event/internal/selector.d
Normal file
217
source/tanya/event/internal/selector.d
Normal file
@ -0,0 +1,217 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.internal.selector;
|
||||
|
||||
import tanya.memory;
|
||||
import tanya.container.buffer;
|
||||
import tanya.event.loop;
|
||||
import tanya.event.protocol;
|
||||
import tanya.event.transport;
|
||||
import core.stdc.errno;
|
||||
import core.sys.posix.netinet.in_;
|
||||
import core.sys.posix.unistd;
|
||||
|
||||
/**
|
||||
* Transport for stream sockets.
|
||||
*/
|
||||
class SocketTransport : DuplexTransport
|
||||
{
|
||||
@nogc:
|
||||
private int socket_ = -1;
|
||||
|
||||
private Protocol protocol_;
|
||||
|
||||
/// Input buffer.
|
||||
private WriteBuffer input_;
|
||||
|
||||
/// Output buffer.
|
||||
private ReadBuffer output_;
|
||||
|
||||
private Loop loop;
|
||||
|
||||
private bool disconnected_;
|
||||
|
||||
package bool writeReady;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* loop = Event loop.
|
||||
* socket = Socket.
|
||||
* protocol = Protocol.
|
||||
*/
|
||||
this(Loop loop, int socket, Protocol protocol = null)
|
||||
{
|
||||
socket_ = socket;
|
||||
protocol_ = protocol;
|
||||
this.loop = loop;
|
||||
input_ = make!WriteBuffer(defaultAllocator);
|
||||
output_ = make!ReadBuffer(defaultAllocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the transport and deallocate the data buffers.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
close(socket);
|
||||
finalize(defaultAllocator, input_);
|
||||
finalize(defaultAllocator, output_);
|
||||
finalize(defaultAllocator, protocol_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Transport socket.
|
||||
*/
|
||||
int socket() const @safe pure nothrow
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Protocol.
|
||||
*/
|
||||
@property Protocol protocol() @safe pure nothrow
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the remote peer closed the connection,
|
||||
* $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
@property immutable(bool) disconnected() const @safe pure nothrow
|
||||
{
|
||||
return disconnected_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*/
|
||||
@property void protocol(Protocol protocol) @safe pure nothrow
|
||||
{
|
||||
protocol_ = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
@property inout(Protocol) protocol() inout @safe pure nothrow
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data)
|
||||
{
|
||||
// If the buffer wasn't empty the transport should be already there.
|
||||
if (!input.length && data.length)
|
||||
{
|
||||
loop.feed(this);
|
||||
}
|
||||
input ~= data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Input buffer.
|
||||
*/
|
||||
@property WriteBuffer input() @safe pure nothrow
|
||||
{
|
||||
return input_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Output buffer.
|
||||
*/
|
||||
@property ReadBuffer output() @safe pure nothrow
|
||||
{
|
||||
return output_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the socket. Returns $(D_KEYWORD true) if the reading
|
||||
* is completed. In the case that the peer closed the connection, returns
|
||||
* $(D_KEYWORD true) aswell.
|
||||
*
|
||||
* Returns: Whether the reading is completed.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
||||
*/
|
||||
bool receive()
|
||||
{
|
||||
auto readCount = recv(socket, output.buffer, output.free, 0);
|
||||
|
||||
if (readCount > 0)
|
||||
{
|
||||
output_ ~= output.buffer[0..readCount];
|
||||
return false;
|
||||
}
|
||||
else if (readCount == 0)
|
||||
{
|
||||
disconnected_ = true;
|
||||
return true;
|
||||
}
|
||||
else if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnected_ = true;
|
||||
throw make!TransportException(defaultAllocator,
|
||||
"Read from the socket failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether the writing is completed.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
||||
*/
|
||||
bool send()
|
||||
{
|
||||
auto sentCount = core.sys.posix.netinet.in_.send(socket,
|
||||
input.buffer,
|
||||
input.length,
|
||||
0);
|
||||
|
||||
input.written = sentCount;
|
||||
if (input.length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (sentCount >= 0)
|
||||
{
|
||||
loop.feed(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
else if (errno == EAGAIN || errno == EWOULDBLOCK)
|
||||
{
|
||||
writeReady = false;
|
||||
loop.feed(this);
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnected_ = true;
|
||||
loop.feed(this);
|
||||
throw make!TransportException(defaultAllocator,
|
||||
"Write to the socket failed.");
|
||||
}
|
||||
}
|
||||
}
|
279
source/tanya/event/loop.d
Normal file
279
source/tanya/event/loop.d
Normal file
@ -0,0 +1,279 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.loop;
|
||||
|
||||
import tanya.memory;
|
||||
import tanya.container.queue;
|
||||
import tanya.container.vector;
|
||||
import tanya.event.config;
|
||||
import tanya.event.protocol;
|
||||
import tanya.event.transport;
|
||||
import tanya.event.watcher;
|
||||
import tanya.container.buffer;
|
||||
import core.thread;
|
||||
import core.time;
|
||||
import std.algorithm.iteration;
|
||||
import std.algorithm.mutation;
|
||||
import std.typecons;
|
||||
|
||||
static if (UseEpoll)
|
||||
{
|
||||
import tanya.event.internal.epoll;
|
||||
}
|
||||
|
||||
@nogc:
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*/
|
||||
enum Event : uint
|
||||
{
|
||||
none = 0x00, /// No events.
|
||||
read = 0x01, /// Non-blocking read call.
|
||||
write = 0x02, /// Non-blocking write call.
|
||||
accept = 0x04, /// Connection made.
|
||||
}
|
||||
|
||||
alias EventMask = BitFlags!Event;
|
||||
|
||||
/**
|
||||
* Event loop.
|
||||
*/
|
||||
abstract class Loop
|
||||
{
|
||||
@nogc:
|
||||
/// Pending watchers.
|
||||
protected Queue!Watcher pendings;
|
||||
|
||||
protected Queue!Watcher swapPendings;
|
||||
|
||||
/// Pending connections.
|
||||
protected Vector!ConnectionWatcher connections;
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
{
|
||||
connections = make!(Vector!ConnectionWatcher)(defaultAllocator);
|
||||
pendings = make!(Queue!Watcher)(defaultAllocator);
|
||||
swapPendings = make!(Queue!Watcher)(defaultAllocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
finalize(defaultAllocator, connections);
|
||||
finalize(defaultAllocator, pendings);
|
||||
finalize(defaultAllocator, swapPendings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the loop.
|
||||
*/
|
||||
void run()
|
||||
{
|
||||
done_ = false;
|
||||
do
|
||||
{
|
||||
poll();
|
||||
|
||||
// Invoke pendings
|
||||
swapPendings.each!((ref p) => p.invoke());
|
||||
|
||||
swap(pendings, swapPendings);
|
||||
}
|
||||
while (!done_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Break out of the loop.
|
||||
*/
|
||||
void unloop() @safe pure nothrow
|
||||
{
|
||||
done_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void start(ConnectionWatcher watcher)
|
||||
{
|
||||
if (watcher.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
watcher.active = true;
|
||||
watcher.accept = &acceptConnection;
|
||||
connections[watcher.socket] = watcher;
|
||||
|
||||
modify(watcher.socket, EventMask(Event.none), EventMask(Event.accept));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop watching.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void stop(ConnectionWatcher watcher)
|
||||
{
|
||||
if (!watcher.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
watcher.active = false;
|
||||
|
||||
modify(watcher.socket, EventMask(Event.accept), EventMask(Event.none));
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds the given event set into the event loop, as if the specified event
|
||||
* had happened for the specified watcher.
|
||||
*
|
||||
* Params:
|
||||
* transport = Affected transport.
|
||||
*/
|
||||
void feed(DuplexTransport transport)
|
||||
{
|
||||
pendings.insertBack(connections[transport.socket]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called if the backend configuration changes.
|
||||
*
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
* oldEvents = The events were already set.
|
||||
* events = The events should be set.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
protected bool modify(int socket, EventMask oldEvents, EventMask events);
|
||||
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
{
|
||||
// Don't block if we have to do.
|
||||
return swapPendings.empty ? blockTime_ : Duration.zero;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blocking time for IO watchers.
|
||||
*
|
||||
* Params:
|
||||
* blockTime = The blocking time. Cannot be larger than
|
||||
* $(D_PSYMBOL maxBlockTime).
|
||||
*/
|
||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
|
||||
in
|
||||
{
|
||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
||||
assert(!blockTime.isNegative);
|
||||
}
|
||||
body
|
||||
{
|
||||
blockTime_ = blockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected void poll();
|
||||
|
||||
/**
|
||||
* Accept incoming connections.
|
||||
*
|
||||
* Params:
|
||||
* protocolFactory = Protocol factory.
|
||||
* socket = Socket.
|
||||
*/
|
||||
protected void acceptConnection(Protocol delegate() @nogc protocolFactory,
|
||||
int socket);
|
||||
|
||||
/// Whether the event loop should be stopped.
|
||||
private bool done_;
|
||||
|
||||
/// Maximal block time.
|
||||
protected Duration blockTime_ = 1.dur!"minutes";
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown on errors in the event loop.
|
||||
*/
|
||||
class BadLoopException : Exception
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* file = The file where the exception occurred.
|
||||
* line = The line number where the exception occurred.
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
||||
pure @safe nothrow const
|
||||
{
|
||||
super("Event loop cannot be initialized.", file, line, next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event loop used by default. If an event loop wasn't set with
|
||||
* $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL getDefaultLoop()) will try to
|
||||
* choose an event loop supported on the system.
|
||||
*
|
||||
* Returns: The default event loop.
|
||||
*/
|
||||
Loop getDefaultLoop()
|
||||
{
|
||||
if (_defaultLoop !is null)
|
||||
{
|
||||
return _defaultLoop;
|
||||
}
|
||||
|
||||
static if (UseEpoll)
|
||||
{
|
||||
_defaultLoop = make!EpollLoop(defaultAllocator);
|
||||
}
|
||||
|
||||
return _defaultLoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default event loop.
|
||||
*
|
||||
* This property makes it possible to implement your own backends or event
|
||||
* loops, for example, if the system is not supported or if you want to
|
||||
* extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass
|
||||
* your implementation to this property.
|
||||
*
|
||||
* Params:
|
||||
* loop = The event loop.
|
||||
*/
|
||||
@property void defaultLoop(Loop loop)
|
||||
in
|
||||
{
|
||||
assert(loop !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
_defaultLoop = loop;
|
||||
}
|
||||
|
||||
private Loop _defaultLoop;
|
16
source/tanya/event/package.d
Normal file
16
source/tanya/event/package.d
Normal file
@ -0,0 +1,16 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event;
|
||||
|
||||
public import tanya.event.loop;
|
||||
public import tanya.event.protocol;
|
||||
public import tanya.event.transport;
|
||||
public import tanya.event.watcher;
|
46
source/tanya/event/protocol.d
Normal file
46
source/tanya/event/protocol.d
Normal file
@ -0,0 +1,46 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.protocol;
|
||||
|
||||
import tanya.event.transport;
|
||||
|
||||
/**
|
||||
* Common protocol interface.
|
||||
*/
|
||||
interface Protocol
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* data = Read data.
|
||||
*/
|
||||
void received(ubyte[] data);
|
||||
|
||||
/**
|
||||
* Called when a connection is made.
|
||||
*
|
||||
* Params:
|
||||
* transport = Protocol transport.
|
||||
*/
|
||||
void connected(DuplexTransport transport);
|
||||
|
||||
/**
|
||||
* Called when a connection is lost.
|
||||
*/
|
||||
void disconnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for TCP.
|
||||
*/
|
||||
interface TransmissionControlProtocol : Protocol
|
||||
{
|
||||
}
|
138
source/tanya/event/transport.d
Normal file
138
source/tanya/event/transport.d
Normal file
@ -0,0 +1,138 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.transport;
|
||||
|
||||
import tanya.container.buffer;
|
||||
import tanya.event.protocol;
|
||||
|
||||
/**
|
||||
* Exception thrown on read/write errors.
|
||||
*/
|
||||
class TransportException : Exception
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* msg = Message to output.
|
||||
* file = The file where the exception occurred.
|
||||
* line = The line number where the exception occurred.
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string msg,
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__,
|
||||
Throwable next = null) pure @safe nothrow const
|
||||
{
|
||||
super(msg, file, line, next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base transport interface.
|
||||
*/
|
||||
interface Transport
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: Protocol.
|
||||
*/
|
||||
@property Protocol protocol() @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the peer closed the connection,
|
||||
* $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
@property immutable(bool) disconnected() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*/
|
||||
@property void protocol(Protocol protocol) @safe pure nothrow
|
||||
in
|
||||
{
|
||||
assert(protocol !is null, "protocolConnected cannot be unset.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
@property inout(Protocol) protocol() inout @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Transport socket.
|
||||
*/
|
||||
int socket() const @safe pure nothrow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for read-only transports.
|
||||
*/
|
||||
interface ReadTransport : Transport
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: Underlying output buffer.
|
||||
*/
|
||||
@property ReadBuffer output();
|
||||
|
||||
/**
|
||||
* Reads data into the buffer.
|
||||
*
|
||||
* Returns: Whether the reading is completed.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
||||
*/
|
||||
bool receive()
|
||||
in
|
||||
{
|
||||
assert(!disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for write-only transports.
|
||||
*/
|
||||
interface WriteTransport : Transport
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: Underlying input buffer.
|
||||
*/
|
||||
@property WriteBuffer input();
|
||||
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data);
|
||||
|
||||
/**
|
||||
* Returns: Whether the writing is completed.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL TransportException) if a read error is occured.
|
||||
*/
|
||||
bool send()
|
||||
in
|
||||
{
|
||||
assert(input.length);
|
||||
assert(!disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a bidirectional transport.
|
||||
*/
|
||||
abstract class DuplexTransport : ReadTransport, WriteTransport
|
||||
{
|
||||
}
|
210
source/tanya/event/watcher.d
Normal file
210
source/tanya/event/watcher.d
Normal file
@ -0,0 +1,210 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.event.watcher;
|
||||
|
||||
import tanya.event.protocol;
|
||||
import tanya.event.transport;
|
||||
import tanya.memory;
|
||||
import std.functional;
|
||||
|
||||
/**
|
||||
* A watcher is an opaque structure that you allocate and register to record
|
||||
* your interest in some event.
|
||||
*/
|
||||
abstract class Watcher
|
||||
{
|
||||
@nogc:
|
||||
/// Whether the watcher is active.
|
||||
bool active;
|
||||
|
||||
/**
|
||||
* Invoke some action on event.
|
||||
*/
|
||||
void invoke();
|
||||
}
|
||||
|
||||
class ConnectionWatcher : Watcher
|
||||
{
|
||||
@nogc:
|
||||
/// Watched file descriptor.
|
||||
private int socket_;
|
||||
|
||||
/// Protocol factory.
|
||||
protected Protocol delegate() protocolFactory;
|
||||
|
||||
/// Callback.
|
||||
package void delegate(Protocol delegate() @nogc protocolFactory,
|
||||
int socket) accept;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(socket_ >= 0, "Called with negative file descriptor.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* protocolFactory = Function returning a new $(D_PSYMBOL Protocol) instance.
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(Protocol function() @nogc protocolFactory, int socket)
|
||||
{
|
||||
this.protocolFactory = toDelegate(protocolFactory);
|
||||
socket_ = socket;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(Protocol delegate() @nogc protocolFactory, int socket)
|
||||
{
|
||||
this.protocolFactory = protocolFactory;
|
||||
socket_ = socket;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
protected this(Protocol function() @nogc protocolFactory)
|
||||
{
|
||||
this.protocolFactory = toDelegate(protocolFactory);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
protected this(Protocol delegate() @nogc protocolFactory)
|
||||
{
|
||||
this.protocolFactory = protocolFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property inout(int) socket() inout @safe pure nothrow
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Application protocol factory.
|
||||
*/
|
||||
@property inout(Protocol delegate() @nogc) protocol() inout
|
||||
{
|
||||
return protocolFactory;
|
||||
}
|
||||
|
||||
override void invoke()
|
||||
{
|
||||
accept(protocol, socket);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a pending watcher with the invoked events or a transport can be
|
||||
* read from.
|
||||
*/
|
||||
class IOWatcher : ConnectionWatcher
|
||||
{
|
||||
@nogc:
|
||||
/// References a watcher or a transport.
|
||||
DuplexTransport transport_;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* protocolFactory = Function returning application specific protocol.
|
||||
* transport = Transport.
|
||||
*/
|
||||
this(Protocol delegate() @nogc protocolFactory,
|
||||
DuplexTransport transport)
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
assert(protocolFactory !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
super(protocolFactory);
|
||||
this.transport_ = transport;
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
finalize(defaultAllocator, transport_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a transport.
|
||||
*
|
||||
* Params:
|
||||
* protocolFactory = Function returning application specific protocol.
|
||||
* transport = Transport.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
IOWatcher opCall(Protocol delegate() @nogc protocolFactory,
|
||||
DuplexTransport transport) @safe pure nothrow
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
assert(protocolFactory !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.protocolFactory = protocolFactory;
|
||||
this.transport_ = transport;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Transport used by this watcher.
|
||||
*/
|
||||
@property inout(DuplexTransport) transport() inout @safe pure nothrow
|
||||
{
|
||||
return transport_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
override @property inout(int) socket() inout @safe pure nothrow
|
||||
{
|
||||
return transport.socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the watcher callback.
|
||||
*
|
||||
* Finalizes the transport on disconnect.
|
||||
*/
|
||||
override void invoke()
|
||||
{
|
||||
if (transport.protocol is null)
|
||||
{
|
||||
transport.protocol = protocolFactory();
|
||||
transport.protocol.connected(transport);
|
||||
}
|
||||
else if (transport.disconnected)
|
||||
{
|
||||
transport.protocol.disconnected();
|
||||
finalize(defaultAllocator, transport_);
|
||||
protocolFactory = null;
|
||||
}
|
||||
else if (transport.output.length)
|
||||
{
|
||||
transport.protocol.received(transport.output[]);
|
||||
}
|
||||
else if (transport.input.length)
|
||||
{
|
||||
try
|
||||
{
|
||||
transport.send();
|
||||
}
|
||||
catch (TransportException e)
|
||||
{
|
||||
finalize(defaultAllocator, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
source/tanya/math.d
Normal file
112
source/tanya/math.d
Normal file
@ -0,0 +1,112 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.container.math;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
import std.algorithm.iteration;
|
||||
}
|
||||
|
||||
@nogc:
|
||||
|
||||
/**
|
||||
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
||||
*
|
||||
* Params:
|
||||
* x = Base.
|
||||
* y = Exponent.
|
||||
* z = Divisor.
|
||||
*
|
||||
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
||||
* by $(D_PARAM z).
|
||||
*/
|
||||
ulong pow(ulong x, ulong y, ulong z) @safe nothrow pure
|
||||
in
|
||||
{
|
||||
assert(z > 0);
|
||||
}
|
||||
out (result)
|
||||
{
|
||||
assert(result >= 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
ulong mask = ulong.max / 2 + 1, result;
|
||||
|
||||
if (y == 0)
|
||||
{
|
||||
return 1 % z;
|
||||
}
|
||||
else if (y == 1)
|
||||
{
|
||||
return x % z;
|
||||
}
|
||||
do
|
||||
{
|
||||
auto bit = y & mask;
|
||||
if (!result && bit)
|
||||
{
|
||||
result = x;
|
||||
continue;
|
||||
}
|
||||
|
||||
result *= result;
|
||||
if (bit)
|
||||
{
|
||||
result *= x;
|
||||
}
|
||||
result %= z;
|
||||
|
||||
}
|
||||
while (mask >>= 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
assert(pow(3, 5, 7) == 5);
|
||||
assert(pow(2, 2, 1) == 0);
|
||||
assert(pow(3, 3, 3) == 0);
|
||||
assert(pow(7, 4, 2) == 1);
|
||||
assert(pow(53, 0, 2) == 1);
|
||||
assert(pow(53, 1, 3) == 2);
|
||||
assert(pow(53, 2, 5) == 4);
|
||||
assert(pow(0, 0, 5) == 1);
|
||||
assert(pow(0, 5, 5) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $(D_PARAM x) is a prime.
|
||||
*
|
||||
* Params:
|
||||
* x = The number should be checked.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
|
||||
* $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool isPseudoprime(ulong x) @safe nothrow pure
|
||||
{
|
||||
return pow(2, x - 1, x) == 1;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
|
||||
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
|
||||
74827, 9973, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 899809363, 982451653];
|
||||
|
||||
known.each!((ref x) => assert(isPseudoprime(x)));
|
||||
}
|
58
source/tanya/memory/allocator.d
Normal file
58
source/tanya/memory/allocator.d
Normal file
@ -0,0 +1,58 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.memory.allocator;
|
||||
|
||||
/**
|
||||
* This interface should be similar to $(D_PSYMBOL
|
||||
* std.experimental.allocator.IAllocator), but usable in
|
||||
* $(D_KEYWORD @nogc)-code.
|
||||
*/
|
||||
interface Allocator
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Allocates $(D_PARAM s) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* s = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(size_t s) @safe;
|
||||
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) @safe;
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: Whether the reallocation was successful.
|
||||
*/
|
||||
bool reallocate(ref void[] p, size_t s) @safe;
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: An $(D_PSYMBOL Allocator) instance.
|
||||
*/
|
||||
static @property Allocator instance() @safe;
|
||||
}
|
207
source/tanya/memory/package.d
Normal file
207
source/tanya/memory/package.d
Normal file
@ -0,0 +1,207 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.memory;
|
||||
|
||||
public import tanya.memory.allocator;
|
||||
public import std.experimental.allocator : make, makeArray, expandArray, shrinkArray, IAllocator;
|
||||
import core.atomic;
|
||||
import core.stdc.stdlib;
|
||||
import std.traits;
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
import core.sys.windows.windows;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
public import tanya.memory.ullocator;
|
||||
import core.sys.posix.pthread;
|
||||
}
|
||||
|
||||
@nogc:
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
package alias Mutex = CRITICAL_SECTION;
|
||||
package alias destroyMutex = DeleteCriticalSection;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
package alias Mutex = pthread_mutex_t;
|
||||
package void destroyMutex(pthread_mutex_t* mtx)
|
||||
{
|
||||
pthread_mutex_destroy(mtx) && assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
@property void defaultAllocator(Allocator allocator) @safe nothrow
|
||||
{
|
||||
_defaultAllocator = allocator;
|
||||
}
|
||||
|
||||
@property Allocator defaultAllocator() @safe nothrow
|
||||
{
|
||||
return _defaultAllocator;
|
||||
}
|
||||
|
||||
static this() @safe nothrow
|
||||
{
|
||||
defaultAllocator = Ullocator.instance;
|
||||
}
|
||||
|
||||
package struct Monitor
|
||||
{
|
||||
Object.Monitor impl; // for user-level monitors
|
||||
void delegate(Object) @nogc[] devt; // for internal monitors
|
||||
size_t refs; // reference count
|
||||
version (Posix)
|
||||
{
|
||||
Mutex mtx;
|
||||
}
|
||||
}
|
||||
|
||||
package @property ref shared(Monitor*) monitor(Object h) pure nothrow
|
||||
{
|
||||
return *cast(shared Monitor**)&h.__monitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys and then deallocates (using $(D_PARAM allocator)) the class
|
||||
* object referred to by a $(D_KEYWORD class) or $(D_KEYWORD interface)
|
||||
* reference. It is assumed the respective entities had been allocated with
|
||||
* the same allocator.
|
||||
*
|
||||
* Params:
|
||||
* A = The type of the allocator used for the ojbect allocation.
|
||||
* T = The type of the object that should be destroyed.
|
||||
* allocator = The allocator used for the object allocation.
|
||||
* p = The object should be destroyed.
|
||||
*/
|
||||
void finalize(A, T)(auto ref A allocator, ref T p)
|
||||
if (is(T == class) || is(T == interface))
|
||||
{
|
||||
static if (is(T == interface))
|
||||
{
|
||||
auto ob = cast(Object) p;
|
||||
}
|
||||
else
|
||||
{
|
||||
alias ob = p;
|
||||
}
|
||||
auto pp = cast(void*) ob;
|
||||
auto ppv = cast(void**) pp;
|
||||
if (!pp || !*ppv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto support = (cast(void*) ob)[0 .. typeid(ob).initializer.length];
|
||||
auto pc = cast(ClassInfo*) *ppv;
|
||||
auto c = *pc;
|
||||
do
|
||||
{
|
||||
if (c.destructor)
|
||||
{
|
||||
(cast(void function(Object)) c.destructor)(ob);
|
||||
}
|
||||
} while ((c = c.base) !is null);
|
||||
|
||||
// Take care of monitors for synchronized blocks
|
||||
if (ppv[1])
|
||||
{
|
||||
shared(Monitor)* m = atomicLoad!(MemoryOrder.acq)(ob.monitor);
|
||||
if (m !is null)
|
||||
{
|
||||
auto mc = cast(Monitor*) m;
|
||||
if (!atomicOp!("-=")(m.refs, cast(size_t) 1))
|
||||
{
|
||||
foreach (v; mc.devt)
|
||||
{
|
||||
if (v)
|
||||
{
|
||||
v(ob);
|
||||
}
|
||||
}
|
||||
if (mc.devt.ptr)
|
||||
{
|
||||
free(mc.devt.ptr);
|
||||
}
|
||||
destroyMutex(&mc.mtx);
|
||||
free(mc);
|
||||
atomicStore!(MemoryOrder.rel)(ob.monitor, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
*ppv = null;
|
||||
|
||||
allocator.deallocate(support);
|
||||
p = null;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void finalize(A, T)(auto ref A allocator, ref T *p)
|
||||
if (is(T == struct))
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
*p.__xdtor();
|
||||
}
|
||||
allocator.deallocate((cast(void*)p)[0 .. T.sizeof]);
|
||||
p = null;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void finalize(A, T)(auto ref A allocator, ref T[] p)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
foreach (ref e; p)
|
||||
{
|
||||
finalize(allocator, e);
|
||||
}
|
||||
}
|
||||
allocator.deallocate(p);
|
||||
p = null;
|
||||
}
|
||||
|
||||
bool resizeArray(T, A)(auto ref A allocator, ref T[] array, in size_t length)
|
||||
@trusted
|
||||
{
|
||||
if (length == array.length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (array is null && length > 0)
|
||||
{
|
||||
array = makeArray!T(allocator, length);
|
||||
return array !is null;
|
||||
}
|
||||
if (length == 0)
|
||||
{
|
||||
finalize(allocator, array);
|
||||
return true;
|
||||
}
|
||||
void[] buf = array;
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
array = cast(T[]) buf;
|
||||
return true;
|
||||
}
|
||||
|
||||
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|
||||
|| hasElaborateDestructor!T || isDynamicArray!T;
|
||||
|
||||
private Allocator _defaultAllocator;
|
423
source/tanya/memory/ullocator.d
Normal file
423
source/tanya/memory/ullocator.d
Normal file
@ -0,0 +1,423 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* 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.memory.ullocator;
|
||||
|
||||
import tanya.memory.allocator;
|
||||
|
||||
@nogc:
|
||||
|
||||
version (Posix):
|
||||
|
||||
import core.sys.posix.sys.mman;
|
||||
import core.sys.posix.unistd;
|
||||
|
||||
/**
|
||||
* Allocator for Posix systems with mmap/munmap support.
|
||||
*
|
||||
* This allocator allocates memory in regions (multiple of 4 KB for example).
|
||||
* Each region is then splitted in blocks. So it doesn't request the memory
|
||||
* from the operating system on each call, but only if there are no large
|
||||
* enought free blocks in the available regions.
|
||||
* Deallocation works in the same way. Deallocation doesn't immediately
|
||||
* gives the memory back to the operating system, but marks the appropriate
|
||||
* block as free and only if all blocks in the region are free, the complet
|
||||
* region is deallocated.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* | | | | | || | | |
|
||||
* | |prev <----------- | || | | |
|
||||
* | R | B | | B | || R | B | |
|
||||
* | E | L | | L | next E | L | |
|
||||
* | G | O | DATA | O | FREE ---> G | O | DATA |
|
||||
* | I | C | | C | <--- I | C | |
|
||||
* | O | K | | K | prev O | K | |
|
||||
* | N | -----------> next| || N | | |
|
||||
* | | | | | || | | |
|
||||
* --------------------------------------------------- ------------------------
|
||||
*/
|
||||
class Ullocator : Allocator
|
||||
{
|
||||
@nogc:
|
||||
@disable this();
|
||||
|
||||
shared static this() @safe nothrow
|
||||
{
|
||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(size_t size) @trusted nothrow
|
||||
{
|
||||
immutable dataSize = addAlignment(size);
|
||||
|
||||
void* data = findBlock(dataSize);
|
||||
if (data is null)
|
||||
{
|
||||
data = initializeRegion(dataSize);
|
||||
}
|
||||
|
||||
return data is null ? null : data[0..size];
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto p = Ullocator.instance.allocate(20);
|
||||
|
||||
assert(p);
|
||||
|
||||
Ullocator.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a block large enough to keep $(D_PARAM size) and split it
|
||||
* into two blocks if the block is too large.
|
||||
*
|
||||
* Params:
|
||||
* size = Minimum size the block should have.
|
||||
*
|
||||
* Returns: Data the block points to or $(D_KEYWORD null).
|
||||
*/
|
||||
private void* findBlock(size_t size) nothrow
|
||||
{
|
||||
Block block1;
|
||||
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
||||
{
|
||||
block1 = cast(Block) (cast(void*) r + regionEntrySize);
|
||||
do
|
||||
{
|
||||
if (block1.free && block1.size >= size)
|
||||
{
|
||||
break RegionLoop;
|
||||
}
|
||||
}
|
||||
while ((block1 = block1.next) !is null);
|
||||
}
|
||||
if (block1 is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (block1.size >= size + alignment + blockEntrySize)
|
||||
{ // Split the block if needed
|
||||
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
|
||||
block2.prev = block1;
|
||||
if (block1.next is null)
|
||||
{
|
||||
block2.next = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
block2.next = block1.next.next;
|
||||
}
|
||||
block1.next = block2;
|
||||
|
||||
block1.free = false;
|
||||
block2.free = true;
|
||||
|
||||
block2.size = block1.size - blockEntrySize - size;
|
||||
block1.size = size;
|
||||
|
||||
block2.region = block1.region;
|
||||
++block1.region.blocks;
|
||||
}
|
||||
else
|
||||
{
|
||||
block1.free = false;
|
||||
++block1.region.blocks;
|
||||
}
|
||||
return cast(void*) block1 + blockEntrySize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) @trusted nothrow
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Block block = cast(Block) (p.ptr - blockEntrySize);
|
||||
if (block.region.blocks <= 1)
|
||||
{
|
||||
if (block.region.prev !is null)
|
||||
{
|
||||
block.region.prev.next = block.region.next;
|
||||
}
|
||||
else // Replace the list head. It is being deallocated
|
||||
{
|
||||
head = block.region.next;
|
||||
}
|
||||
if (block.region.next !is null)
|
||||
{
|
||||
block.region.next.prev = block.region.prev;
|
||||
}
|
||||
return munmap(block.region, block.region.size) == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.free = true;
|
||||
--block.region.blocks;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto p = Ullocator.instance.allocate(20);
|
||||
|
||||
assert(Ullocator.instance.deallocate(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: Whether the reallocation was successful.
|
||||
*/
|
||||
bool reallocate(ref void[] p, size_t size) @trusted nothrow
|
||||
{
|
||||
if (size == p.length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
auto reallocP = allocate(size);
|
||||
if (reallocP is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (p !is null)
|
||||
{
|
||||
if (size > p.length)
|
||||
{
|
||||
reallocP[0..p.length] = p[0..$];
|
||||
}
|
||||
else
|
||||
{
|
||||
reallocP[0..size] = p[0..size];
|
||||
}
|
||||
deallocate(p);
|
||||
}
|
||||
p = reallocP;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
void[] p;
|
||||
Ullocator.instance.reallocate(p, 10 * int.sizeof);
|
||||
(cast(int[]) p)[7] = 123;
|
||||
|
||||
assert(p.length == 40);
|
||||
|
||||
Ullocator.instance.reallocate(p, 8 * int.sizeof);
|
||||
|
||||
assert(p.length == 32);
|
||||
assert((cast(int[]) p)[7] == 123);
|
||||
|
||||
Ullocator.instance.reallocate(p, 20 * int.sizeof);
|
||||
(cast(int[]) p)[15] = 8;
|
||||
|
||||
assert(p.length == 80);
|
||||
assert((cast(int[]) p)[15] == 8);
|
||||
assert((cast(int[]) p)[7] == 123);
|
||||
|
||||
Ullocator.instance.reallocate(p, 8 * int.sizeof);
|
||||
|
||||
assert(p.length == 32);
|
||||
assert((cast(int[]) p)[7] == 123);
|
||||
|
||||
Ullocator.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: The global $(D_PSYMBOL Allocator) instance.
|
||||
*/
|
||||
static @property Ullocator instance() @trusted nothrow
|
||||
{
|
||||
if (instance_ is null)
|
||||
{
|
||||
immutable instanceSize = addAlignment(__traits(classInstanceSize, Ullocator));
|
||||
|
||||
Region head; // Will become soon our region list head
|
||||
void* data = initializeRegion(instanceSize, head);
|
||||
|
||||
if (data is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
data[0..instanceSize] = typeid(Ullocator).initializer[];
|
||||
instance_ = cast(Ullocator) data;
|
||||
instance_.head = head;
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
assert(instance is instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a region for one element.
|
||||
*
|
||||
* Params:
|
||||
* size = Aligned size of the first data block in the region.
|
||||
* head = Region list head.
|
||||
*
|
||||
* Returns: A pointer to the data.
|
||||
*/
|
||||
pragma(inline)
|
||||
private static void* initializeRegion(size_t size,
|
||||
ref Region head) nothrow
|
||||
{
|
||||
immutable regionSize = calculateRegionSize(size);
|
||||
void* p = mmap(null,
|
||||
regionSize,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANON,
|
||||
-1,
|
||||
0);
|
||||
if (p is MAP_FAILED)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Region region = cast(Region) p;
|
||||
region.blocks = 1;
|
||||
region.size = regionSize;
|
||||
|
||||
// Set the pointer to the head of the region list
|
||||
if (head !is null)
|
||||
{
|
||||
head.prev = region;
|
||||
}
|
||||
region.next = head;
|
||||
region.prev = null;
|
||||
head = region;
|
||||
|
||||
// Initialize the data block
|
||||
void* memoryPointer = p + regionEntrySize;
|
||||
Block block1 = cast(Block) memoryPointer;
|
||||
block1.size = size;
|
||||
block1.free = false;
|
||||
|
||||
// It is what we want to return
|
||||
void* data = memoryPointer + blockEntrySize;
|
||||
|
||||
// Free block after data
|
||||
memoryPointer = data + size;
|
||||
Block block2 = cast(Block) memoryPointer;
|
||||
block1.prev = block2.next = null;
|
||||
block1.next = block2;
|
||||
block2.prev = block1;
|
||||
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
|
||||
block2.free = true;
|
||||
block1.region = block2.region = region;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
private void* initializeRegion(size_t size) nothrow
|
||||
{
|
||||
return initializeRegion(size, head);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* x = Space to be aligned.
|
||||
*
|
||||
* Returns: Aligned size of $(D_PARAM x).
|
||||
*/
|
||||
pragma(inline)
|
||||
private static immutable(size_t) addAlignment(size_t x) @safe pure nothrow
|
||||
out (result)
|
||||
{
|
||||
assert(result > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
return (x - 1) / alignment * alignment + alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* x = Required space.
|
||||
*
|
||||
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
||||
*/
|
||||
pragma(inline)
|
||||
private static immutable(size_t) calculateRegionSize(size_t x)
|
||||
@safe pure nothrow
|
||||
out (result)
|
||||
{
|
||||
assert(result > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
x += regionEntrySize + blockEntrySize * 2;
|
||||
return x / pageSize * pageSize + pageSize;
|
||||
}
|
||||
|
||||
enum alignment = 8;
|
||||
|
||||
private static Ullocator instance_;
|
||||
|
||||
private shared static immutable long pageSize;
|
||||
|
||||
private struct RegionEntry
|
||||
{
|
||||
Region prev;
|
||||
Region next;
|
||||
uint blocks;
|
||||
ulong size;
|
||||
}
|
||||
private alias Region = RegionEntry*;
|
||||
private enum regionEntrySize = 32;
|
||||
|
||||
private Region head;
|
||||
|
||||
private struct BlockEntry
|
||||
{
|
||||
Block prev;
|
||||
Block next;
|
||||
bool free;
|
||||
ulong size;
|
||||
Region region;
|
||||
}
|
||||
private alias Block = BlockEntry*;
|
||||
private enum blockEntrySize = 40;
|
||||
}
|
324
source/tanya/random.d
Normal file
324
source/tanya/random.d
Normal file
@ -0,0 +1,324 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.random;
|
||||
|
||||
import tanya.memory;
|
||||
import std.digest.sha;
|
||||
import std.typecons;
|
||||
|
||||
@nogc:
|
||||
|
||||
/// Block size of entropy accumulator (SHA-512).
|
||||
enum blockSize = 64;
|
||||
|
||||
/// Maximum amount gathered from the entropy sources.
|
||||
enum maxGather = 128;
|
||||
|
||||
/**
|
||||
* Exception thrown if random number generating fails.
|
||||
*/
|
||||
class EntropyException : Exception
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* msg = Message to output.
|
||||
* file = The file where the exception occurred.
|
||||
* line = The line number where the exception occurred.
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string msg,
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__,
|
||||
Throwable next = null) pure @safe nothrow const
|
||||
{
|
||||
super(msg, file, line, next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for implementing entropy sources.
|
||||
*/
|
||||
abstract class EntropySource
|
||||
{
|
||||
@nogc:
|
||||
/// Amount of already generated entropy.
|
||||
protected ushort size_;
|
||||
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
@property immutable(ubyte) threshold() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
@property immutable(bool) strong() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Amount of already generated entropy.
|
||||
*/
|
||||
@property ushort size() const @safe pure nothrow
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Amount of already generated entropy. Cannot be smaller than the
|
||||
* already set value.
|
||||
*/
|
||||
@property void size(ushort size) @safe pure nothrow
|
||||
{
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
Nullable!ubyte poll(out ubyte[maxGather] output);
|
||||
}
|
||||
|
||||
version (linux)
|
||||
{
|
||||
extern (C) long syscall(long number, ...) nothrow;
|
||||
|
||||
/**
|
||||
* Uses getrandom system call.
|
||||
*/
|
||||
class PlatformEntropySource : EntropySource
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
override @property immutable(ubyte) threshold() const @safe pure nothrow
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
override @property immutable(bool) strong() const @safe pure nothrow
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
|
||||
out (length)
|
||||
{
|
||||
assert(length <= maxGather);
|
||||
}
|
||||
body
|
||||
{
|
||||
// int getrandom(void *buf, size_t buflen, unsigned int flags);
|
||||
auto length = syscall(318, output.ptr, output.length, 0);
|
||||
Nullable!ubyte ret;
|
||||
|
||||
if (length >= 0)
|
||||
{
|
||||
ret = cast(ubyte) length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pseudorandom number generator.
|
||||
* ---
|
||||
* auto entropy = defaultAllocator.make!Entropy;
|
||||
*
|
||||
* ubyte[blockSize] output;
|
||||
*
|
||||
* output = entropy.random;
|
||||
*
|
||||
* defaultAllocator.finalize(entropy);
|
||||
* ---
|
||||
*/
|
||||
class Entropy
|
||||
{
|
||||
@nogc:
|
||||
/// Entropy sources.
|
||||
protected EntropySource[] sources;
|
||||
|
||||
private ubyte sourceCount_;
|
||||
|
||||
private Allocator allocator;
|
||||
|
||||
/// Entropy accumulator.
|
||||
protected SHA!(maxGather * 8, 512) accumulator;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* maxSources = Maximum amount of entropy sources can be set.
|
||||
* allocator = Allocator to allocate entropy sources available on the
|
||||
* system.
|
||||
*/
|
||||
this(size_t maxSources = 20, Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator.resizeArray(sources, maxSources);
|
||||
|
||||
version (linux)
|
||||
{
|
||||
this ~= allocator.make!PlatformEntropySource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Amount of the registered entropy sources.
|
||||
*/
|
||||
@property ubyte sourceCount() const @safe pure nothrow
|
||||
{
|
||||
return sourceCount_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entropy source.
|
||||
*
|
||||
* Params:
|
||||
* source = Entropy source.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL this).
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL EntropySource)
|
||||
*/
|
||||
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
|
||||
if (Op == "~")
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ <= sources.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
sources[sourceCount_++] = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Generated random sequence.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
|
||||
* registered or it failed.
|
||||
*/
|
||||
@property ubyte[blockSize] random()
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ > 0, "No entropy sources defined.");
|
||||
}
|
||||
body
|
||||
{
|
||||
bool haveStrong;
|
||||
ushort done;
|
||||
ubyte[blockSize] output;
|
||||
|
||||
do
|
||||
{
|
||||
ubyte[maxGather] buffer;
|
||||
|
||||
// Run through our entropy sources
|
||||
for (ubyte i; i < sourceCount; ++i)
|
||||
{
|
||||
auto outputLength = sources[i].poll(buffer);
|
||||
|
||||
if (!outputLength.isNull)
|
||||
{
|
||||
if (outputLength > 0)
|
||||
{
|
||||
update(i, buffer, outputLength);
|
||||
sources[i].size = cast(ushort) (sources[i].size + outputLength);
|
||||
}
|
||||
if (sources[i].size < sources[i].threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (sources[i].strong)
|
||||
{
|
||||
haveStrong = true;
|
||||
}
|
||||
}
|
||||
done = 257;
|
||||
}
|
||||
}
|
||||
while (++done < 256);
|
||||
|
||||
if (!haveStrong)
|
||||
{
|
||||
throw allocator.make!EntropyException("No strong entropy source defined.");
|
||||
}
|
||||
|
||||
output = accumulator.finish();
|
||||
|
||||
// Reset accumulator and counters and recycle existing entropy
|
||||
accumulator.start();
|
||||
|
||||
// Perform second SHA-512 on entropy
|
||||
output = sha512Of(output);
|
||||
|
||||
for (ubyte i = 0; i < sourceCount; ++i)
|
||||
{
|
||||
sources[i].size = 0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entropy accumulator.
|
||||
*
|
||||
* Params:
|
||||
* sourceId = Entropy source index in $(D_PSYMBOL sources).
|
||||
* data = Data got from the entropy source.
|
||||
* length = Length of the received data.
|
||||
*/
|
||||
protected void update(in ubyte sourceId,
|
||||
ref ubyte[maxGather] data,
|
||||
ubyte length) @safe pure nothrow
|
||||
{
|
||||
ubyte[2] header;
|
||||
|
||||
if (length > blockSize)
|
||||
{
|
||||
data[0..64] = sha512Of(data);
|
||||
length = blockSize;
|
||||
}
|
||||
|
||||
header[0] = sourceId;
|
||||
header[1] = length;
|
||||
|
||||
accumulator.put(header);
|
||||
accumulator.put(data[0..length]);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user