62 Commits

Author SHA1 Message Date
976eb4bfbc Add downloads button 2017-01-07 15:25:05 +01:00
fb843e3473 Fix #3 2017-01-07 09:30:42 +01:00
f3d48234c0 MmapPool: add expand and empty methods. 2017-01-06 23:12:19 +01:00
254b881da6 Fix block size calculation 2017-01-06 11:56:54 +01:00
8e0b742748 MmapPool: Merge blocks on deallocation if possible 2017-01-05 14:25:54 +01:00
a35e04c049 Don't throw in the allocator, return null 2017-01-05 07:42:23 +01:00
4271c8583e Remove static constructor from the MmapPool 2017-01-05 07:35:29 +01:00
e27d0fe58c Fix Vector.remove not destroying from the end copied elements 2017-01-04 20:37:55 +01:00
67952dabdb Implement Vector.remove 2017-01-03 13:21:19 +01:00
b8d5d4c2bd Fix template condition for Vector.insertBack 2017-01-03 10:03:28 +01:00
b6413823cd Add opEquals for all combinations of vector ranges 2017-01-02 17:33:01 +01:00
48e355b87f Vector: allow insert multiple items in insertBack 2017-01-02 12:47:41 +01:00
b3f4ea572e Vector: Use opEquals if defined to compare items 2017-01-02 06:59:05 +01:00
c73e704421 Fix constness of Vector range, optimizing 2017-01-01 02:51:49 +01:00
0561e96f21 Fix build with 2.071.2 2016-12-28 07:57:36 +01:00
86d87430da Fix socket build on Windows 2016-12-25 12:54:04 +01:00
0156c5a883 Don't allocate watcher queue on the heap 2016-12-25 00:54:05 +01:00
c966b42ac3 Fix FreeBSD build 2016-12-24 22:25:34 +01:00
200fff3714 Fix #1 2016-12-22 22:05:48 +01:00
28755b4d01 Rename module traits into enums 2016-12-22 22:05:06 +01:00
8bd6a14988 Fix issue going out of the range with back() 2016-12-22 22:01:45 +01:00
b41dcc9f37 Fix compatibility issue with dmd 2.071 2016-12-22 22:01:11 +01:00
38addb7a5b Add support for pow for big integers 2016-12-22 21:51:16 +01:00
f7fb89fed0 Move random.d into math submodule 2016-12-22 21:50:33 +01:00
e32af2d09e Add scalar type template parameter for buffers 2016-12-19 21:24:28 +01:00
f1bc4dc2e2 Add length and opCmp to the Queue 2016-12-19 16:33:16 +01:00
40857e69b7 Add capacity capabilities to the vector 2016-12-18 18:48:25 +01:00
c1fb89af99 Implement insertion into the vector 2016-12-15 15:00:06 +01:00
061cd6264b Use auto ref for templated overloaded functions 2016-12-13 10:59:05 +01:00
f437dafa6b Fix dispose for structs 2016-12-13 10:58:11 +01:00
54d0597657 Use resizeArray instead of expand/shrinkArray 2016-12-13 10:57:12 +01:00
ab9f96e0c7 Replace class Queue with the struct Queue 2016-12-13 10:56:29 +01:00
711855474c Remove unused buffer interface 2016-12-13 10:54:27 +01:00
b20f367aa8 Array support for refCounted factory function 2016-12-11 11:42:09 +01:00
a2dadda511 Fix subtraction of numbers with different signs 2016-12-08 18:30:22 +01:00
77dca31261 Add license info 2016-12-08 15:07:58 +01:00
b87aed4395 Add travis to README 2016-12-08 15:00:09 +01:00
42bbb3b023 Add travis 2016-12-08 14:58:59 +01:00
4309a30dfe Add opBinary for the other math operations on Integer 2016-12-08 14:51:49 +01:00
9362287938 Fix error with assignin long numbers to Integer 2016-12-08 14:43:50 +01:00
78bd901339 Add short description of the packages to the README 2016-12-07 23:16:49 +01:00
c8e6d44f7b Implement own dispose 2016-12-07 11:01:51 +01:00
f75433e0e6 Implement operations on negative numbers 2016-12-06 23:22:12 +01:00
fa607141e4 Make allocator shared and fix some RefCounted bugs 2016-12-06 21:29:08 +01:00
b3fdd6fd4a Implement unary operation for multiple precision integers 2016-12-05 22:06:06 +01:00
86c08e7af6 Use RefCounted as math.mp.Integer internal storage 2016-12-04 22:51:21 +01:00
1c5796eb96 Add RefCounted 2016-12-04 14:05:53 +01:00
f7f92e7906 Switch to container.queue. Remove PendingQueue 2016-12-02 19:18:37 +01:00
1123d01e6c Implement opApply for the Queue 2016-12-02 17:31:57 +01:00
c53d319337 Implement foreach_reverse for the vector 2016-12-02 14:12:12 +01:00
7c36dbb8f0 Rename SList front property into insertFront 2016-12-02 10:50:54 +01:00
dd3becf6b7 Implement slicing for the vector 2016-12-02 10:29:30 +01:00
b78ecdf4c5 Make tanya.memory.allocator compatible with dmd < 2.072 2016-12-01 20:04:04 +01:00
a4aa5bcb2e Make opApply delegate scoped for SList 2016-12-01 20:03:21 +01:00
edd3ec4b32 Add URL parser 2016-12-01 20:02:49 +01:00
9fdcef86e7 Replace defaultAllocator with theAllocator 2016-11-30 21:54:31 +01:00
ed0eb4ac74 Fix epoll connection bugs 2016-11-30 21:53:30 +01:00
192ee20bf7 Remove shared from the allocators 2016-11-30 21:20:18 +01:00
965ca0088e Add multiple precision unsigned integer module 2016-11-30 20:24:51 +01:00
b752acdff7 Fix tanya.math module name 2016-11-30 16:48:46 +01:00
cbeb0395f9 Remove @safe from potentially unsafe code 2016-11-30 16:21:20 +01:00
5e6f8446d8 Add an abandoned notice 2016-10-08 20:45:03 +02:00
35 changed files with 8771 additions and 4734 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@
# D # D
.dub .dub
__test__*__ __test__*__
__test__*__.core

17
.travis.yml Normal file
View File

@ -0,0 +1,17 @@
sudo: false
os:
- linux
language: d
d:
- dmd-2.072.2
- dmd-2.071.2
env:
matrix:
- ARCH=x86_64
script:
- dub test --arch=$ARCH

70
README.md Normal file
View File

@ -0,0 +1,70 @@
# Tanya
[![Build Status](https://travis-ci.org/caraus-ecms/tanya.svg?branch=master)](https://travis-ci.org/caraus-ecms/tanya)
[![Dub version](https://img.shields.io/dub/v/tanya.svg)](https://code.dlang.org/packages/tanya)
[![Dub downloads](https://img.shields.io/dub/dt/tanya.svg)](https://code.dlang.org/packages/tanya)
[![License](https://img.shields.io/badge/license-MPL_2.0-blue.svg)](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
Tanya is a general purpose library for D programming language that doesn't
rely on the Garbage Collector.
Its aim is to simplify the manual memory management in D and to provide a
guarantee with @nogc attribute that there are no hidden allocations on the
Garbage Collector heap. Everything in the library is usable in @nogc code.
Tanya extends Phobos functionality and provides alternative implementations for
data structures and utilities that depend on the Garbage Collector in Phobos.
## Overview
Tanya consists of the following packages:
* `async`: Event loop (epoll, kqueue and IOCP).
* `container`: Queue, Vector, Singly linked list, buffers.
* `crypto`: Work in progress TLS implementation.
* `math`: Multiple precision integer and a set of functions.
* `memory`: Tools for manual memory management (allocator, reference counting,
helper functions).
* `network`: URL-Parsing, sockets.
### Supported compilers
* dmd 2.072.2
* dmd 2.071.2
### Current status
The library is currently under development, but some parts of it can already be
used.
Containers were newly reworked and the API won't change significantly, but will
be only extended. The same is true for the `memory` package.
`network` and `async` packages should be reviewed in the future and the API may
change.
`math` package contains an arbitrary precision integer implementation that has
a stable API (that mostly consists of operator overloads), but still needs
testing and work on its performance.
I'm currently mostly working on `crypto` that is not a complete cryptographic
suite, but contains (will contain) algorithm implementations required by TLS.
### Further characteristics
* Tanya is a native D library.
* Documentation and usage examples can be found in the source code.
Online documentation will be published soon.
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
is being tested on Windows and FreeBSD as well.
* The library isn't thread-safe. Thread-safity should be added later.
## Contributing
Since I'm mostly busy writing new code and implementing new features I would
appreciate, if anyone uses the library. It would help me to improve the
codebase and fix issues.
Feel free to contact me if you have any questions.

View File

@ -26,153 +26,162 @@ import core.sys.posix.unistd;
import core.time; import core.time;
import std.algorithm.comparison; import std.algorithm.comparison;
extern (C) nothrow @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);
}
class EpollLoop : SelectorLoop class EpollLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private epoll_event[] events; private epoll_event[] events;
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() this() @nogc
{ {
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0) if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
{ {
throw MmapPool.instance.make!BadLoopException("epoll initialization failed"); throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
} }
super(); super();
events = MmapPool.instance.makeArray!epoll_event(maxEvents); MmapPool.instance.resizeArray(events, maxEvents);
} }
/** /**
* Free loop internals. * Free loop internals.
*/ */
~this() ~this() @nogc
{ {
MmapPool.instance.dispose(events); MmapPool.instance.dispose(events);
close(fd); close(fd);
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events) protected override bool reify(ConnectionWatcher watcher,
in EventMask oldEvents,
{ EventMask events) @nogc
assert(watcher !is null); in
} {
body assert(watcher !is null);
{ }
int op = EPOLL_CTL_DEL; body
epoll_event ev; {
int op = EPOLL_CTL_DEL;
epoll_event ev;
if (events == oldEvents) if (events == oldEvents)
{ {
return true; return true;
} }
if (events && oldEvents) if (events && oldEvents)
{ {
op = EPOLL_CTL_MOD; op = EPOLL_CTL_MOD;
} }
else if (events && !oldEvents) else if (events && !oldEvents)
{ {
op = EPOLL_CTL_ADD; op = EPOLL_CTL_ADD;
} }
ev.data.fd = watcher.socket.handle; ev.data.fd = watcher.socket.handle;
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0) ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
| (events & Event.write ? EPOLLOUT : 0) | (events & Event.write ? EPOLLOUT : 0)
| EPOLLET; | EPOLLET;
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0; return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
protected override void poll() protected override void poll() @nogc
{ {
// Don't block // Don't block
immutable timeout = cast(immutable int) blockTime.total!"msecs"; immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto eventCount = epoll_wait(fd, events.ptr, maxEvents, timeout); auto eventCount = epoll_wait(fd, events.ptr, maxEvents, timeout);
if (eventCount < 0) if (eventCount < 0)
{ {
if (errno != EINTR) if (errno != EINTR)
{ {
throw defaultAllocator.make!BadLoopException(); throw defaultAllocator.make!BadLoopException();
} }
return; return;
} }
for (auto i = 0; i < eventCount; ++i) for (auto i = 0; i < eventCount; ++i)
{ {
auto io = cast(IOWatcher) connections[events[i].data.fd]; auto io = cast(IOWatcher) connections[events[i].data.fd];
if (io is null) if (io is null)
{ {
acceptConnections(connections[events[i].data.fd]); acceptConnections(connections[events[i].data.fd]);
} }
else if (events[i].events & EPOLLERR) else if (events[i].events & EPOLLERR)
{ {
kill(io, null); kill(io, null);
} }
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP)) else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
{ {
auto transport = cast(SelectorStreamTransport) io.transport; auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
SocketException exception; SocketException exception;
try try
{ {
ptrdiff_t received; ptrdiff_t received;
do do
{ {
received = transport.socket.receive(io.output[]); received = transport.socket.receive(io.output[]);
io.output += received; io.output += received;
} }
while (received); while (received);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
kill(io, exception); kill(io, exception);
} }
else if (io.output.length) else if (io.output.length)
{ {
swapPendings.insertBack(io); swapPendings.insertBack(io);
} }
} }
else if (events[i].events & EPOLLOUT) else if (events[i].events & EPOLLOUT)
{ {
auto transport = cast(SelectorStreamTransport) io.transport; auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
transport.writeReady = true; transport.writeReady = true;
if (transport.input.length) if (transport.input.length)
{ {
feed(transport); feed(transport);
} }
} }
} }
} }
/** /**
* Returns: The blocking time. * Returns: The blocking time.
*/ */
override protected @property inout(Duration) blockTime() override protected @property inout(Duration) blockTime()
inout @safe pure nothrow inout @safe pure nothrow
{ {
return min(super.blockTime, 1.dur!"seconds"); return min(super.blockTime, 1.dur!"seconds");
} }
} }

View File

@ -28,267 +28,264 @@ import core.sys.windows.winsock2;
class IOCPStreamTransport : StreamTransport class IOCPStreamTransport : StreamTransport
{ {
private OverlappedConnectedSocket socket_; private OverlappedConnectedSocket socket_;
private WriteBuffer input; private WriteBuffer!ubyte input;
/** /**
* Creates new completion port transport. * Creates new completion port transport.
* Params: * Params:
* socket = Socket. * socket = Socket.
*/ */
this(OverlappedConnectedSocket socket) this(OverlappedConnectedSocket socket) @nogc
in in
{ {
assert(socket !is null); assert(socket !is null);
} }
body body
{ {
socket_ = socket; socket_ = socket;
input = MmapPool.instance.make!WriteBuffer(); input = WriteBuffer!ubyte(8192, MmapPool.instance);
} }
~this() @property inout(OverlappedConnectedSocket) socket()
{ inout pure nothrow @safe @nogc
MmapPool.instance.dispose(input); {
} return socket_;
}
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc /**
{ * Write some data to the transport.
return socket_; *
} * Params:
* data = Data to send.
/** */
* Write some data to the transport. void write(ubyte[] data) @nogc
* {
* Params: immutable empty = input.length == 0;
* data = Data to send. input ~= data;
*/ if (empty)
void write(ubyte[] data) {
{ SocketState overlapped;
immutable empty = input.length == 0; try
input ~= data; {
if (empty) overlapped = MmapPool.instance.make!SocketState;
{ socket.beginSend(input[], overlapped);
SocketState overlapped; }
try catch (SocketException e)
{ {
overlapped = MmapPool.instance.make!SocketState; MmapPool.instance.dispose(overlapped);
socket.beginSend(input[], overlapped); MmapPool.instance.dispose(e);
} }
catch (SocketException e) }
{ }
MmapPool.instance.dispose(overlapped);
MmapPool.instance.dispose(e);
}
}
}
} }
class IOCPLoop : Loop class IOCPLoop : Loop
{ {
protected HANDLE completionPort; protected HANDLE completionPort;
protected OVERLAPPED overlap; protected OVERLAPPED overlap;
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() this() @nogc
{ {
super(); super();
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
if (!completionPort) if (!completionPort)
{ {
throw defaultAllocator.make!BadLoopException("Creating completion port failed"); throw make!BadLoopException(defaultAllocator,
} "Creating completion port failed");
} }
}
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
override protected bool reify(ConnectionWatcher watcher, override protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) EventMask events) @nogc
{ {
SocketState overlapped; SocketState overlapped;
if (!(oldEvents & Event.accept) && (events & Event.accept)) if (!(oldEvents & Event.accept) && (events & Event.accept))
{ {
auto socket = cast(OverlappedStreamSocket) watcher.socket; auto socket = cast(OverlappedStreamSocket) watcher.socket;
assert(socket !is null); assert(socket !is null);
if (CreateIoCompletionPort(cast(HANDLE) socket.handle, if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
completionPort, completionPort,
cast(ULONG_PTR) (cast(void*) watcher), cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort) 0) !is completionPort)
{ {
return false; return false;
} }
try try
{ {
overlapped = MmapPool.instance.make!SocketState; overlapped = MmapPool.instance.make!SocketState;
socket.beginAccept(overlapped); socket.beginAccept(overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
return false; return false;
} }
} }
if (!(oldEvents & Event.read) && (events & Event.read) if (!(oldEvents & Event.read) && (events & Event.read)
|| !(oldEvents & Event.write) && (events & Event.write)) || !(oldEvents & Event.write) && (events & Event.write))
{ {
auto io = cast(IOWatcher) watcher; auto io = cast(IOWatcher) watcher;
assert(io !is null); assert(io !is null);
auto transport = cast(IOCPStreamTransport) io.transport; auto transport = cast(IOCPStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle, if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
completionPort, completionPort,
cast(ULONG_PTR) (cast(void*) watcher), cast(ULONG_PTR) (cast(void*) watcher),
0) !is completionPort) 0) !is completionPort)
{ {
return false; return false;
} }
// Begin to read // Begin to read
if (!(oldEvents & Event.read) && (events & Event.read)) if (!(oldEvents & Event.read) && (events & Event.read))
{ {
try try
{ {
overlapped = MmapPool.instance.make!SocketState; overlapped = MmapPool.instance.make!SocketState;
transport.socket.beginReceive(io.output[], overlapped); transport.socket.beginReceive(io.output[], overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
return false; return false;
} }
} }
} }
return true; return true;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
override protected void poll() override protected void poll() @nogc
{ {
DWORD lpNumberOfBytes; DWORD lpNumberOfBytes;
ULONG_PTR key; ULONG_PTR key;
LPOVERLAPPED overlap; LPOVERLAPPED overlap;
immutable timeout = cast(immutable int) blockTime.total!"msecs"; immutable timeout = cast(immutable int) blockTime.total!"msecs";
auto result = GetQueuedCompletionStatus(completionPort, auto result = GetQueuedCompletionStatus(completionPort,
&lpNumberOfBytes, &lpNumberOfBytes,
&key, &key,
&overlap, &overlap,
timeout); timeout);
if (result == FALSE && overlap == NULL) if (result == FALSE && overlap == NULL)
{ {
return; // Timeout return; // Timeout
} }
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8)); auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
assert(overlapped !is null); assert(overlapped !is null);
scope (failure) scope (failure)
{ {
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
} }
switch (overlapped.event) switch (overlapped.event)
{ {
case OverlappedSocketEvent.accept: case OverlappedSocketEvent.accept:
auto connection = cast(ConnectionWatcher) (cast(void*) key); auto connection = cast(ConnectionWatcher) (cast(void*) key);
assert(connection !is null); assert(connection !is null);
auto listener = cast(OverlappedStreamSocket) connection.socket; auto listener = cast(OverlappedStreamSocket) connection.socket;
assert(listener !is null); assert(listener !is null);
auto socket = listener.endAccept(overlapped); auto socket = listener.endAccept(overlapped);
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket); auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol); auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
connection.incoming.insertBack(io); connection.incoming.insertBack(io);
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write)); reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
swapPendings.insertBack(connection); swapPendings.insertBack(connection);
listener.beginAccept(overlapped); listener.beginAccept(overlapped);
break; break;
case OverlappedSocketEvent.read: case OverlappedSocketEvent.read:
auto io = cast(IOWatcher) (cast(void*) key); auto io = cast(IOWatcher) (cast(void*) key);
assert(io !is null); assert(io !is null);
if (!io.active) if (!io.active)
{ {
MmapPool.instance.dispose(io); MmapPool.instance.dispose(io);
MmapPool.instance.dispose(overlapped); MmapPool.instance.dispose(overlapped);
return; return;
} }
auto transport = cast(IOCPStreamTransport) io.transport; auto transport = cast(IOCPStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
int received; int received;
SocketException exception; SocketException exception;
try try
{ {
received = transport.socket.endReceive(overlapped); received = transport.socket.endReceive(overlapped);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
// We want to get one last notification to destroy the watcher // We want to get one last notification to destroy the watcher
transport.socket.beginReceive(io.output[], overlapped); transport.socket.beginReceive(io.output[], overlapped);
kill(io, exception); kill(io, exception);
} }
else if (received > 0) else if (received > 0)
{ {
immutable full = io.output.free == received; immutable full = io.output.free == received;
io.output += received; io.output += received;
// Receive was interrupted because the buffer is full. We have to continue // Receive was interrupted because the buffer is full. We have to continue
if (full) if (full)
{ {
transport.socket.beginReceive(io.output[], overlapped); transport.socket.beginReceive(io.output[], overlapped);
} }
swapPendings.insertBack(io); swapPendings.insertBack(io);
} }
break; break;
case OverlappedSocketEvent.write: case OverlappedSocketEvent.write:
auto io = cast(IOWatcher) (cast(void*) key); auto io = cast(IOWatcher) (cast(void*) key);
assert(io !is null); assert(io !is null);
auto transport = cast(IOCPStreamTransport) io.transport; auto transport = cast(IOCPStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
transport.input += transport.socket.endSend(overlapped); transport.input += transport.socket.endSend(overlapped);
if (transport.input.length) if (transport.input.length)
{ {
transport.socket.beginSend(transport.input[], overlapped); transport.socket.beginSend(transport.input[], overlapped);
} }
else else
{ {
transport.socket.beginReceive(io.output[], overlapped); transport.socket.beginReceive(io.output[], overlapped);
} }
break; break;
default: default:
assert(false, "Unknown event"); assert(false, "Unknown event");
} }
} }
} }

View File

@ -6,134 +6,109 @@
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/ */
module tanya.async.event.kqueue; module tanya.async.event.kqueue;
version (OSX) version (OSX)
{ {
version = MissingKevent; version = MacBSD;
} }
else version (iOS) else version (iOS)
{ {
version = MissingKevent; version = MacBSD;
} }
else version (TVOS) else version (TVOS)
{ {
version = MissingKevent; version = MacBSD;
} }
else version (WatchOS) else version (WatchOS)
{ {
version = MissingKevent; version = MacBSD;
}
else version (OpenBSD)
{
version = MissingKevent;
}
else version (DragonFlyBSD)
{
version = MissingKevent;
}
version (MissingKevent)
{
extern (C):
nothrow:
@nogc:
import core.stdc.stdint; // intptr_t, uintptr_t
import core.sys.posix.time; // timespec
enum : short
{
EVFILT_READ = -1,
EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11
}
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
{
*kevp = kevent_t(args);
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
ushort flags;
uint fflags;
intptr_t data;
void *udata; /* opaque user data identifier */
}
enum
{
/* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */
EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */
}
int kqueue();
int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents,
const timespec *timeout);
}
version (OSX)
{
version = MacBSD;
}
else version (iOS)
{
version = MacBSD;
} }
else version (FreeBSD) else version (FreeBSD)
{ {
version = MacBSD; version = MacBSD;
public import core.sys.freebsd.sys.event;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
version = MacBSD; version = MacBSD;
} }
else version (DragonFlyBSD) else version (DragonFlyBSD)
{ {
version = MacBSD; version = MacBSD;
} }
version (MacBSD): version (MacBSD):
import dlib.async.event.selector; import core.stdc.stdint; // intptr_t, uintptr_t
import dlib.async.loop; import core.sys.posix.time; // timespec
import dlib.async.transport;
import dlib.async.watcher; void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
import dlib.memory; {
import dlib.memory.mmappool; *kevp = kevent_t(args);
import dlib.network.socket; }
enum : short
{
EVFILT_READ = -1,
EVFILT_WRITE = -2,
EVFILT_AIO = -3, /* attached to aio requests */
EVFILT_VNODE = -4, /* attached to vnodes */
EVFILT_PROC = -5, /* attached to struct proc */
EVFILT_SIGNAL = -6, /* attached to struct proc */
EVFILT_TIMER = -7, /* timers */
EVFILT_MACHPORT = -8, /* Mach portsets */
EVFILT_FS = -9, /* filesystem events */
EVFILT_USER = -10, /* User events */
EVFILT_VM = -12, /* virtual memory events */
EVFILT_SYSCOUNT = 11
}
struct kevent_t
{
uintptr_t ident; /* identifier for this event */
short filter; /* filter for event */
ushort flags;
uint fflags;
intptr_t data;
void *udata; /* opaque user data identifier */
}
enum
{
/* actions */
EV_ADD = 0x0001, /* add event to kq (implies enable) */
EV_DELETE = 0x0002, /* delete event from kq */
EV_ENABLE = 0x0004, /* enable event */
EV_DISABLE = 0x0008, /* disable event (not reported) */
/* flags */
EV_ONESHOT = 0x0010, /* only report one occurrence */
EV_CLEAR = 0x0020, /* clear event state after reporting */
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
EV_DISPATCH = 0x0080, /* disable event after reporting */
EV_SYSFLAGS = 0xF000, /* reserved by system */
EV_FLAG1 = 0x2000, /* filter-specific flag */
/* returned values */
EV_EOF = 0x8000, /* EOF detected */
EV_ERROR = 0x4000, /* error, data contains errno */
}
extern(C) int kqueue() nothrow @nogc;
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
kevent_t *eventlist, int nevents, const timespec *timeout)
nothrow @nogc;
import tanya.async.event.selector;
import tanya.async.loop;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.stdc.errno; import core.stdc.errno;
import core.sys.posix.unistd; import core.sys.posix.unistd;
import core.sys.posix.sys.time; import core.sys.posix.sys.time;
@ -142,208 +117,210 @@ import std.algorithm.comparison;
class KqueueLoop : SelectorLoop class KqueueLoop : SelectorLoop
{ {
protected int fd; protected int fd;
private kevent_t[] events; private kevent_t[] events;
private kevent_t[] changes; private kevent_t[] changes;
private size_t changeCount; private size_t changeCount;
/** /**
* Returns: Maximal event count can be got at a time * Returns: Maximal event count can be got at a time
* (should be supported by the backend). * (should be supported by the backend).
*/ */
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc override protected @property inout(uint) maxEvents()
{ inout const pure nothrow @safe @nogc
return cast(uint) events.length; {
} return cast(uint) events.length;
}
this() this() @nogc
{ {
super(); super();
if ((fd = kqueue()) == -1) if ((fd = kqueue()) == -1)
{ {
throw MmapPool.instance.make!BadLoopException("epoll initialization failed"); throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
} }
events = MmapPool.instance.makeArray!kevent_t(64); MmapPool.instance.resizeArray(events, 64);
changes = MmapPool.instance.makeArray!kevent_t(64); MmapPool.instance.resizeArray(changes, 64);
} }
/** /**
* Free loop internals. * Free loop internals.
*/ */
~this() ~this() @nogc
{ {
MmapPool.instance.dispose(events); MmapPool.instance.dispose(events);
MmapPool.instance.dispose(changes); MmapPool.instance.dispose(changes);
close(fd); close(fd);
} }
private void set(socket_t socket, short filter, ushort flags) private void set(socket_t socket, short filter, ushort flags) @nogc
{ {
if (changes.length <= changeCount) if (changes.length <= changeCount)
{ {
MmapPool.instance.resizeArray(changes, changeCount + maxEvents); MmapPool.instance.resizeArray(changes, changeCount + maxEvents);
} }
EV_SET(&changes[changeCount], EV_SET(&changes[changeCount],
cast(ulong) socket, cast(ulong) socket,
filter, filter,
flags, flags,
0U, 0U,
0L, 0L,
null); null);
++changeCount; ++changeCount;
} }
/** /**
* Should be called if the backend configuration changes. * Should be called if the backend configuration changes.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
* oldEvents = The events were already set. * oldEvents = The events were already set.
* events = The events should be set. * events = The events should be set.
* *
* Returns: $(D_KEYWORD true) if the operation was successful. * Returns: $(D_KEYWORD true) if the operation was successful.
*/ */
override protected bool reify(ConnectionWatcher watcher, override protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents, EventMask oldEvents,
EventMask events) EventMask events) @nogc
{ {
if (events != oldEvents) if (events != oldEvents)
{ {
if (oldEvents & Event.read || oldEvents & Event.accept) if (oldEvents & Event.read || oldEvents & Event.accept)
{ {
set(watcher.socket.handle, EVFILT_READ, EV_DELETE); set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
} }
if (oldEvents & Event.write) if (oldEvents & Event.write)
{ {
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE); set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
} }
} }
if (events & (Event.read | events & Event.accept)) if (events & (Event.read | events & Event.accept))
{ {
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE); set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
} }
if (events & Event.write) if (events & Event.write)
{ {
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH); set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
} }
return true; return true;
} }
/** /**
* Does the actual polling. * Does the actual polling.
*/ */
protected override void poll() protected override void poll() @nogc
{ {
timespec ts; timespec ts;
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec); blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
if (changeCount > maxEvents) if (changeCount > maxEvents)
{ {
MmapPool.instance.resizeArray(events, changes.length); MmapPool.instance.resizeArray(events, changes.length);
} }
auto eventCount = kevent(fd, changes.ptr, cast(int) changeCount, events.ptr, maxEvents, &ts); auto eventCount = kevent(fd, changes.ptr, cast(int) changeCount, events.ptr, maxEvents, &ts);
changeCount = 0; changeCount = 0;
if (eventCount < 0) if (eventCount < 0)
{ {
if (errno != EINTR) if (errno != EINTR)
{ {
throw defaultAllocator.make!BadLoopException(); throw defaultAllocator.make!BadLoopException();
} }
return; return;
} }
for (int i; i < eventCount; ++i) for (int i; i < eventCount; ++i)
{ {
assert(connections.length > events[i].ident); assert(connections.length > events[i].ident);
IOWatcher io = cast(IOWatcher) connections[events[i].ident]; IOWatcher io = cast(IOWatcher) connections[events[i].ident];
// If it is a ConnectionWatcher. Accept connections. // If it is a ConnectionWatcher. Accept connections.
if (io is null) if (io is null)
{ {
acceptConnections(connections[events[i].ident]); acceptConnections(connections[events[i].ident]);
} }
else if (events[i].flags & EV_ERROR) else if (events[i].flags & EV_ERROR)
{ {
kill(io, null); kill(io, null);
} }
else if (events[i].filter == EVFILT_READ) else if (events[i].filter == EVFILT_READ)
{ {
auto transport = cast(SelectorStreamTransport) io.transport; auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
SocketException exception; SocketException exception;
try try
{ {
ptrdiff_t received; ptrdiff_t received;
do do
{ {
received = transport.socket.receive(io.output[]); received = transport.socket.receive(io.output[]);
io.output += received; io.output += received;
} }
while (received); while (received);
} }
catch (SocketException e) catch (SocketException e)
{ {
exception = e; exception = e;
} }
if (transport.socket.disconnected) if (transport.socket.disconnected)
{ {
kill(io, exception); kill(io, exception);
} }
else if (io.output.length) else if (io.output.length)
{ {
swapPendings.insertBack(io); swapPendings.insertBack(io);
} }
} }
else if (events[i].filter == EVFILT_WRITE) else if (events[i].filter == EVFILT_WRITE)
{ {
auto transport = cast(SelectorStreamTransport) io.transport; auto transport = cast(SelectorStreamTransport) io.transport;
assert(transport !is null); assert(transport !is null);
transport.writeReady = true; transport.writeReady = true;
if (transport.input.length) if (transport.input.length)
{ {
feed(transport); feed(transport);
} }
} }
} }
} }
/** /**
* Returns: The blocking time. * Returns: The blocking time.
*/ */
override protected @property inout(Duration) blockTime() override protected @property inout(Duration) blockTime()
inout @safe pure nothrow inout @nogc @safe pure nothrow
{ {
return min(super.blockTime, 1.dur!"seconds"); return min(super.blockTime, 1.dur!"seconds");
} }
/** /**
* If the transport couldn't send the data, the further sending should * If the transport couldn't send the data, the further sending should
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* Returns: $(D_KEYWORD true) if the operation could be successfully * Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the * completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport is be destroyed then). * transport is be destroyed then).
*/ */
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null) protected override bool feed(SelectorStreamTransport transport,
{ SocketException exception = null) @nogc
if (!super.feed(transport, exception)) {
{ if (!super.feed(transport, exception))
return false; {
} return false;
if (!transport.writeReady) }
{ if (!transport.writeReady)
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH); {
return true; set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
} return true;
return false; }
} return false;
}
} }

View File

@ -27,240 +27,233 @@ import core.stdc.errno;
*/ */
class SelectorStreamTransport : StreamTransport class SelectorStreamTransport : StreamTransport
{ {
private ConnectedSocket socket_; private ConnectedSocket socket_;
/// Input buffer. /// Input buffer.
package WriteBuffer input; package WriteBuffer!ubyte input;
private SelectorLoop loop; private SelectorLoop loop;
/// Received notification that the underlying socket is write-ready. /// Received notification that the underlying socket is write-ready.
package bool writeReady; package bool writeReady;
/** /**
* Params: * Params:
* loop = Event loop. * loop = Event loop.
* socket = Socket. * socket = Socket.
*/ */
this(SelectorLoop loop, ConnectedSocket socket) this(SelectorLoop loop, ConnectedSocket socket) @nogc
{ {
socket_ = socket; socket_ = socket;
this.loop = loop; this.loop = loop;
input = MmapPool.instance.make!WriteBuffer(); input = WriteBuffer!ubyte(8192, MmapPool.instance);
} }
/** /**
* Close the transport and deallocate the data buffers. * Returns: Transport socket.
*/ */
~this() inout(ConnectedSocket) socket() inout pure nothrow @safe @nogc
{ {
MmapPool.instance.dispose(input); return socket_;
} }
/** /**
* Returns: Transport socket. * Write some data to the transport.
*/ *
inout(ConnectedSocket) socket() inout pure nothrow @safe @nogc * Params:
{ * data = Data to send.
return socket_; */
} void write(ubyte[] data) @nogc
{
/** if (!data.length)
* Write some data to the transport. {
* return;
* Params: }
* data = Data to send. // Try to write if the socket is write ready.
*/ if (writeReady)
void write(ubyte[] data) {
{ ptrdiff_t sent;
if (!data.length) SocketException exception;
{ try
return; {
} sent = socket.send(data);
// Try to write if the socket is write ready. if (sent == 0)
if (writeReady) {
{ writeReady = false;
ptrdiff_t sent; }
SocketException exception; }
try catch (SocketException e)
{ {
sent = socket.send(data); writeReady = false;
if (sent == 0) exception = e;
{ }
writeReady = false; if (sent < data.length)
} {
} input ~= data[sent..$];
catch (SocketException e) loop.feed(this, exception);
{ }
writeReady = false; }
exception = e; else
} {
if (sent < data.length) input ~= data;
{ }
input ~= data[sent..$]; }
loop.feed(this, exception);
}
}
else
{
input ~= data;
}
}
} }
abstract class SelectorLoop : Loop abstract class SelectorLoop : Loop
{ {
/// Pending connections. /// Pending connections.
protected ConnectionWatcher[] connections; protected ConnectionWatcher[] connections;
this() this() @nogc
{ {
super(); super();
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents); MmapPool.instance.resizeArray(connections, maxEvents);
} }
~this() ~this() @nogc
{ {
foreach (ref connection; connections) foreach (ref connection; connections)
{ {
// We want to free only IOWatchers. ConnectionWatcher are created by the // We want to free only IOWatchers. ConnectionWatcher are created by the
// user and should be freed by himself. // user and should be freed by himself.
auto io = cast(IOWatcher) connection; auto io = cast(IOWatcher) connection;
if (io !is null) if (io !is null)
{ {
MmapPool.instance.dispose(io); MmapPool.instance.dispose(io);
connection = null; connection = null;
} }
} }
MmapPool.instance.dispose(connections); MmapPool.instance.dispose(connections);
} }
/** /**
* If the transport couldn't send the data, the further sending should * If the transport couldn't send the data, the further sending should
* be handled by the event loop. * be handled by the event loop.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* exception = Exception thrown on sending. * exception = Exception thrown on sending.
* *
* Returns: $(D_KEYWORD true) if the operation could be successfully * Returns: $(D_KEYWORD true) if the operation could be successfully
* completed or scheduled, $(D_KEYWORD false) otherwise (the * completed or scheduled, $(D_KEYWORD false) otherwise (the
* transport will be destroyed then). * transport will be destroyed then).
*/ */
protected bool feed(SelectorStreamTransport transport, SocketException exception = null) protected bool feed(SelectorStreamTransport transport,
{ SocketException exception = null) @nogc
while (transport.input.length && transport.writeReady) {
{ while (transport.input.length && transport.writeReady)
try {
{ try
ptrdiff_t sent = transport.socket.send(transport.input[]); {
if (sent == 0) ptrdiff_t sent = transport.socket.send(transport.input[]);
{ if (sent == 0)
transport.writeReady = false; {
} transport.writeReady = false;
else }
{ else
transport.input += sent; {
} transport.input += sent;
} }
catch (SocketException e) }
{ catch (SocketException e)
exception = e; {
transport.writeReady = false; exception = e;
} transport.writeReady = false;
} }
if (exception !is null) }
{ if (exception !is null)
auto watcher = cast(IOWatcher) connections[transport.socket.handle]; {
assert(watcher !is null); auto watcher = cast(IOWatcher) connections[transport.socket.handle];
assert(watcher !is null);
kill(watcher, exception); kill(watcher, exception);
return false; return false;
} }
return true; return true;
} }
/** /**
* Start watching. * Start watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
override void start(ConnectionWatcher watcher) override void start(ConnectionWatcher watcher) @nogc
{ {
if (watcher.active) if (watcher.active)
{ {
return; return;
} }
if (connections.length <= watcher.socket) if (connections.length <= watcher.socket)
{ {
MmapPool.instance.resizeArray(connections, watcher.socket.handle + maxEvents / 2); MmapPool.instance.resizeArray(connections, watcher.socket.handle + maxEvents / 2);
} }
connections[watcher.socket.handle] = watcher; connections[watcher.socket.handle] = watcher;
super.start(watcher); super.start(watcher);
} }
/** /**
* Accept incoming connections. * Accept incoming connections.
* *
* Params: * Params:
* connection = Connection watcher ready to accept. * connection = Connection watcher ready to accept.
*/ */
package void acceptConnections(ConnectionWatcher connection) package void acceptConnections(ConnectionWatcher connection) @nogc
in in
{ {
assert(connection !is null); assert(connection !is null);
} }
body body
{ {
while (true) while (true)
{ {
ConnectedSocket client; ConnectedSocket client;
try try
{ {
client = (cast(StreamSocket) connection.socket).accept(); client = (cast(StreamSocket) connection.socket).accept();
} }
catch (SocketException e) catch (SocketException e)
{ {
defaultAllocator.dispose(e); defaultAllocator.dispose(e);
break; break;
} }
if (client is null) if (client is null)
{ {
break; break;
} }
IOWatcher io; IOWatcher io;
auto transport = MmapPool.instance.make!SelectorStreamTransport(this, client); auto transport = MmapPool.instance.make!SelectorStreamTransport(this, client);
if (connections.length >= client.handle) if (connections.length > client.handle)
{ {
io = cast(IOWatcher) connections[client.handle]; io = cast(IOWatcher) connections[client.handle];
} }
else else
{ {
MmapPool.instance.resizeArray(connections, client.handle + maxEvents / 2); MmapPool.instance.resizeArray(connections, client.handle + maxEvents / 2);
} }
if (io is null) if (io is null)
{ {
io = MmapPool.instance.make!IOWatcher(transport, io = MmapPool.instance.make!IOWatcher(transport,
connection.protocol); connection.protocol);
connections[client.handle] = io; connections[client.handle] = io;
} }
else else
{ {
io(transport, connection.protocol); io(transport, connection.protocol);
} }
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write)); reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
connection.incoming.insertBack(io); connection.incoming.insertBack(io);
} }
if (!connection.incoming.empty) if (!connection.incoming.empty)
{ {
swapPendings.insertBack(connection); swapPendings.insertBack(connection);
} }
} }
} }

View File

@ -21,12 +21,12 @@ import core.sys.windows.windef;
*/ */
class State class State
{ {
/// For internal use by Windows API. /// For internal use by Windows API.
align(1) OVERLAPPED overlapped; align(1) OVERLAPPED overlapped;
/// File/socket handle. /// File/socket handle.
HANDLE handle; HANDLE handle;
/// For keeping events or event masks. /// For keeping events or event masks.
int event; int event;
} }

View File

@ -10,99 +10,105 @@
* *
* --- * ---
* import tanya.async; * import tanya.async;
* import tanya.memory;
* import tanya.network.socket; * import tanya.network.socket;
* *
* class EchoProtocol : TransmissionControlProtocol * class EchoProtocol : TransmissionControlProtocol
* { * {
* private DuplexTransport transport; * private DuplexTransport transport;
* *
* void received(ubyte[] data) * void received(ubyte[] data) @nogc
* { * {
* transport.write(data); * transport.write(data);
* } * }
* *
* void connected(DuplexTransport transport) * void connected(DuplexTransport transport) @nogc
* { * {
* this.transport = transport; * this.transport = transport;
* } * }
* *
* void disconnected(SocketException exception = null) * void disconnected(SocketException e = null) @nogc
* { * {
* } * }
* } * }
* *
* void main() * void main()
* { * {
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192); * auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
* version (Windows)
* {
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
* }
* else
* {
* auto sock = new StreamSocket(AddressFamily.INET);
* sock.blocking = false;
* }
* *
* sock.bind(address); * version (Windows)
* sock.listen(5); * {
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
* }
* else
* {
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
* sock.blocking = false;
* }
* *
* auto io = new ConnectionWatcher(sock); * sock.bind(address);
* io.setProtocol!EchoProtocol; * sock.listen(5);
* *
* defaultLoop.start(io); * auto io = defaultAllocator.make!ConnectionWatcher(sock);
* defaultLoop.run(); * io.setProtocol!EchoProtocol;
* *
* sock.shutdown(); * defaultLoop.start(io);
* defaultLoop.run();
*
* sock.shutdown();
* defaultAllocator.dispose(io);
* defaultAllocator.dispose(sock);
* defaultAllocator.dispose(address);
* } * }
* --- * ---
*/ */
module tanya.async.loop; module tanya.async.loop;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
import core.time; import core.time;
import std.algorithm.iteration; import std.algorithm.iteration;
import std.algorithm.mutation; import std.algorithm.mutation;
import std.typecons; import std.typecons;
import tanya.async.protocol;
import tanya.async.transport;
import tanya.async.watcher;
import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory;
import tanya.memory.mmappool;
import tanya.network.socket;
version (DisableBackends) version (DisableBackends)
{ {
} }
else version (linux) else version (linux)
{ {
import tanya.async.event.epoll; import tanya.async.event.epoll;
version = Epoll; version = Epoll;
} }
else version (Windows) else version (Windows)
{ {
import tanya.async.event.iocp; import tanya.async.event.iocp;
version = IOCP; version = IOCP;
} }
else version (OSX) else version (OSX)
{ {
version = Kqueue; version = Kqueue;
} }
else version (iOS) else version (iOS)
{ {
version = Kqueue; version = Kqueue;
} }
else version (FreeBSD) else version (FreeBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (OpenBSD) else version (OpenBSD)
{ {
version = Kqueue; version = Kqueue;
} }
else version (DragonFlyBSD) else version (DragonFlyBSD)
{ {
version = Kqueue; version = Kqueue;
} }
/** /**
@ -110,186 +116,186 @@ else version (DragonFlyBSD)
*/ */
enum Event : uint enum Event : uint
{ {
none = 0x00, /// No events. none = 0x00, /// No events.
read = 0x01, /// Non-blocking read call. read = 0x01, /// Non-blocking read call.
write = 0x02, /// Non-blocking write call. write = 0x02, /// Non-blocking write call.
accept = 0x04, /// Connection made. accept = 0x04, /// Connection made.
error = 0x80000000, /// Sent when an error occurs. error = 0x80000000, /// Sent when an error occurs.
} }
alias EventMask = BitFlags!Event; alias EventMask = BitFlags!Event;
/**
* Tries to set $(D_PSYMBOL MmapPool) to the default allocator.
*/
shared static this()
{
if (allocator is null)
{
allocator = MmapPool.instance;
}
}
/** /**
* Event loop. * Event loop.
*/ */
abstract class Loop abstract class Loop
{ {
/// Pending watchers. /// Pending watchers.
protected PendingQueue!Watcher pendings; protected Queue!Watcher* pendings;
protected PendingQueue!Watcher swapPendings; /// Ditto.
protected Queue!Watcher* swapPendings;
/** /**
* Returns: Maximal event count can be got at a time * Returns: Maximal event count can be got at a time
* (should be supported by the backend). * (should be supported by the backend).
*/ */
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc protected @property inout(uint) maxEvents()
{ inout const pure nothrow @safe @nogc
return 128U; {
} return 128U;
}
/** /**
* Initializes the loop. * Initializes the loop.
*/ */
this() this() @nogc
{ {
pendings = MmapPool.instance.make!(PendingQueue!Watcher); pendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher); swapPendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
} }
/** /**
* Frees loop internals. * Frees loop internals.
*/ */
~this() ~this() @nogc
{ {
MmapPool.instance.dispose(pendings); foreach (w; *pendings)
MmapPool.instance.dispose(swapPendings); {
} MmapPool.instance.dispose(w);
}
MmapPool.instance.dispose(pendings);
/** foreach (w; *swapPendings)
* Starts the loop. {
*/ MmapPool.instance.dispose(w);
void run() }
{ MmapPool.instance.dispose(swapPendings);
done_ = false; }
do
{
poll();
// Invoke pendings /**
swapPendings.each!((ref p) => p.invoke()); * Starts the loop.
*/
void run() @nogc
{
done_ = false;
do
{
poll();
swap(pendings, swapPendings); // Invoke pendings
} swapPendings.each!((ref p) @nogc => p.invoke());
while (!done_);
}
/** swap(pendings, swapPendings);
* Break out of the loop. }
*/ while (!done_);
void unloop() @safe pure nothrow }
{
done_ = true;
}
/** /**
* Start watching. * Break out of the loop.
* */
* Params: void unloop() @safe pure nothrow @nogc
* watcher = Watcher. {
*/ done_ = true;
void start(ConnectionWatcher watcher) }
{
if (watcher.active)
{
return;
}
watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
}
/** /**
* Stop watching. * Start watching.
* *
* Params: * Params:
* watcher = Watcher. * watcher = Watcher.
*/ */
void stop(ConnectionWatcher watcher) void start(ConnectionWatcher watcher) @nogc
{ {
if (!watcher.active) if (watcher.active)
{ {
return; return;
} }
watcher.active = false; watcher.active = true;
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
}
reify(watcher, EventMask(Event.accept), EventMask(Event.none)); /**
} * Stop watching.
*
* Params:
* watcher = Watcher.
*/
void stop(ConnectionWatcher watcher) @nogc
{
if (!watcher.active)
{
return;
}
watcher.active = false;
/** reify(watcher, EventMask(Event.accept), EventMask(Event.none));
* Should be called if the backend configuration changes. }
*
* Params:
* watcher = Watcher.
* oldEvents = The events were already set.
* events = The events should be set.
*
* Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events);
/** /**
* Returns: The blocking time. * Should be called if the backend configuration changes.
*/ *
protected @property inout(Duration) blockTime() * Params:
inout @safe pure nothrow * watcher = Watcher.
{ * oldEvents = The events were already set.
// Don't block if we have to do. * events = The events should be set.
return swapPendings.empty ? blockTime_ : Duration.zero; *
} * Returns: $(D_KEYWORD true) if the operation was successful.
*/
abstract protected bool reify(ConnectionWatcher watcher,
EventMask oldEvents,
EventMask events) @nogc;
/** /**
* Sets the blocking time for IO watchers. * Returns: The blocking time.
* */
* Params: protected @property inout(Duration) blockTime()
* blockTime = The blocking time. Cannot be larger than inout @safe pure nothrow @nogc
* $(D_PSYMBOL maxBlockTime). {
*/ // Don't block if we have to do.
protected @property void blockTime(in Duration blockTime) @safe pure nothrow return swapPendings.empty ? blockTime_ : Duration.zero;
in }
{
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative);
}
body
{
blockTime_ = blockTime;
}
/** /**
* Kills the watcher and closes the connection. * Sets the blocking time for IO watchers.
*/ *
protected void kill(IOWatcher watcher, SocketException exception) * Params:
{ * blockTime = The blocking time. Cannot be larger than
watcher.socket.shutdown(); * $(D_PSYMBOL maxBlockTime).
defaultAllocator.dispose(watcher.socket); */
MmapPool.instance.dispose(watcher.transport); protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
watcher.exception = exception; in
swapPendings.insertBack(watcher); {
} assert(blockTime <= 1.dur!"hours", "Too long to wait.");
assert(!blockTime.isNegative);
}
body
{
blockTime_ = blockTime;
}
/** /**
* Does the actual polling. * Kills the watcher and closes the connection.
*/ */
abstract protected void poll(); protected void kill(IOWatcher watcher, SocketException exception) @nogc
{
watcher.socket.shutdown();
defaultAllocator.dispose(watcher.socket);
MmapPool.instance.dispose(watcher.transport);
watcher.exception = exception;
swapPendings.insertBack(watcher);
}
/// Whether the event loop should be stopped. /**
private bool done_; * Does the actual polling.
*/
abstract protected void poll() @nogc;
/// Maximal block time. /// Whether the event loop should be stopped.
protected Duration blockTime_ = 1.dur!"minutes"; private bool done_;
/// Maximal block time.
protected Duration blockTime_ = 1.dur!"minutes";
} }
/** /**
@ -297,18 +303,17 @@ abstract class Loop
*/ */
class BadLoopException : Exception class BadLoopException : Exception
{ {
@nogc: /**
/** * Params:
* Params: * file = The file where the exception occurred.
* file = The file where the exception occurred. * line = The line number where the exception occurred.
* line = The line number where the exception occurred. * next = The previous exception in the chain of exceptions, if any.
* next = The previous exception in the chain of exceptions, if any. */
*/ this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null) pure nothrow const @safe @nogc
pure @safe nothrow const {
{ super("Event loop cannot be initialized.", file, line, next);
super("Event loop cannot be initialized.", file, line, next); }
}
} }
/** /**
@ -318,26 +323,26 @@ class BadLoopException : Exception
* *
* Returns: The default event loop. * Returns: The default event loop.
*/ */
@property Loop defaultLoop() @property Loop defaultLoop() @nogc
{ {
if (defaultLoop_ !is null) if (defaultLoop_ !is null)
{ {
return defaultLoop_; return defaultLoop_;
} }
version (Epoll) version (Epoll)
{ {
defaultLoop_ = MmapPool.instance.make!EpollLoop; defaultLoop_ = MmapPool.instance.make!EpollLoop;
} }
else version (IOCP) else version (IOCP)
{ {
defaultLoop_ = MmapPool.instance.make!IOCPLoop; defaultLoop_ = MmapPool.instance.make!IOCPLoop;
} }
else version (Kqueue) else version (Kqueue)
{ {
import tanya.async.event.kqueue; import tanya.async.event.kqueue;
defaultLoop_ = MmapPool.instance.make!KqueueLoop; defaultLoop_ = MmapPool.instance.make!KqueueLoop;
} }
return defaultLoop_; return defaultLoop_;
} }
/** /**
@ -349,145 +354,16 @@ class BadLoopException : Exception
* your implementation to this property. * your implementation to this property.
* *
* Params: * Params:
* loop = The event loop. * loop = The event loop.
*/ */
@property void defaultLoop(Loop loop) @property void defaultLoop(Loop loop) @nogc
in in
{ {
assert(loop !is null); assert(loop !is null);
} }
body body
{ {
defaultLoop_ = loop; defaultLoop_ = loop;
} }
private Loop defaultLoop_; private Loop defaultLoop_;
/**
* Queue.
*
* Params:
* T = Content type.
*/
class PendingQueue(T)
{
/**
* Creates a new $(D_PSYMBOL Queue).
*/
this()
{
}
/**
* Removes all elements from the queue.
*/
~this()
{
foreach (e; this)
{
MmapPool.instance.dispose(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 = MmapPool.instance.make!Entry;
temp.content = x;
if (empty)
{
first.next = rear = temp;
}
else
{
rear.next = temp;
rear = rear.next;
}
return this;
}
alias insert = insertBack;
/**
* 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);
}
/**
* Returns: $(D_KEYWORD true) if the queue is empty.
*/
@property bool empty() const @safe pure nothrow
{
return first.next is null;
}
/**
* Move position to the next element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) popFront()
in
{
assert(!empty);
}
body
{
auto n = first.next.next;
MmapPool.instance.dispose(first.next);
first.next = n;
return this;
}
/**
* 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;
}

View File

@ -10,10 +10,7 @@
*/ */
module tanya.async; module tanya.async;
public public import tanya.async.loop;
{ public import tanya.async.protocol;
import tanya.async.loop; public import tanya.async.transport;
import tanya.async.protocol; public import tanya.async.watcher;
import tanya.async.transport;
import tanya.async.watcher;
}

View File

@ -18,28 +18,28 @@ import tanya.async.transport;
*/ */
interface Protocol interface Protocol
{ {
/** /**
* Params: * Params:
* data = Read data. * data = Read data.
*/ */
void received(ubyte[] data); void received(ubyte[] data) @nogc;
/** /**
* Called when a connection is made. * Called when a connection is made.
* *
* Params: * Params:
* transport = Protocol transport. * transport = Protocol transport.
*/ */
void connected(DuplexTransport transport); void connected(DuplexTransport transport) @nogc;
/** /**
* Called when a connection is lost. * Called when a connection is lost.
* *
* Params: * Params:
* exception = $(D_PSYMBOL Exception) if an error caused * exception = $(D_PSYMBOL Exception) if an error caused
* the disconnect, $(D_KEYWORD null) otherwise. * the disconnect, $(D_KEYWORD null) otherwise.
*/ */
void disconnected(SocketException exception = null); void disconnected(SocketException exception = null) @nogc;
} }
/** /**

View File

@ -31,13 +31,13 @@ interface ReadTransport : Transport
*/ */
interface WriteTransport : Transport interface WriteTransport : Transport
{ {
/** /**
* Write some data to the transport. * Write some data to the transport.
* *
* Params: * Params:
* data = Data to send. * data = Data to send.
*/ */
void write(ubyte[] data); void write(ubyte[] data) @nogc;
} }
/** /**
@ -52,7 +52,7 @@ interface DuplexTransport : ReadTransport, WriteTransport
*/ */
interface SocketTransport : Transport interface SocketTransport : Transport
{ {
@property inout(Socket) socket() inout pure nothrow @safe @nogc; @property inout(Socket) socket() inout pure nothrow @safe @nogc;
} }
/** /**

View File

@ -10,23 +10,24 @@
*/ */
module tanya.async.watcher; module tanya.async.watcher;
import std.functional;
import std.exception;
import tanya.async.loop; import tanya.async.loop;
import tanya.async.protocol; import tanya.async.protocol;
import tanya.async.transport; import tanya.async.transport;
import tanya.container.buffer; import tanya.container.buffer;
import tanya.container.queue;
import tanya.memory; import tanya.memory;
import tanya.memory.mmappool; import tanya.memory.mmappool;
import tanya.network.socket; import tanya.network.socket;
import std.functional;
import std.exception;
version (Windows) version (Windows)
{ {
import core.sys.windows.basetyps; import core.sys.windows.basetyps;
import core.sys.windows.mswsock; import core.sys.windows.mswsock;
import core.sys.windows.winbase; import core.sys.windows.winbase;
import core.sys.windows.windef; import core.sys.windows.windef;
import core.sys.windows.winsock2; import core.sys.windows.winsock2;
} }
/** /**
@ -35,85 +36,87 @@ version (Windows)
*/ */
abstract class Watcher abstract class Watcher
{ {
/// Whether the watcher is active. /// Whether the watcher is active.
bool active; bool active;
/** /**
* Invoke some action on event. * Invoke some action on event.
*/ */
void invoke(); void invoke() @nogc;
} }
class ConnectionWatcher : Watcher class ConnectionWatcher : Watcher
{ {
/// Watched socket. /// Watched socket.
private Socket socket_; private Socket socket_;
/// Protocol factory. /// Protocol factory.
protected Protocol delegate() protocolFactory; protected Protocol delegate() @nogc protocolFactory;
package PendingQueue!IOWatcher incoming; package Queue!IOWatcher incoming;
/** /**
* Params: * Params:
* socket = Socket. * socket = Socket.
*/ */
this(Socket socket) this(Socket socket) @nogc
{ {
socket_ = socket; socket_ = socket;
incoming = MmapPool.instance.make!(PendingQueue!IOWatcher); }
}
/// Ditto. /// Ditto.
protected this() protected this() pure nothrow @safe @nogc
{ {
} }
~this() ~this() @nogc
{ {
MmapPool.instance.dispose(incoming); foreach (w; incoming)
} {
MmapPool.instance.dispose(w);
}
}
/* /**
* Params: * Params:
* P = Protocol should be used. * P = Protocol should be used.
*/ */
void setProtocol(P : Protocol)() void setProtocol(P : Protocol)() @nogc
{ {
this.protocolFactory = () => cast(Protocol) MmapPool.instance.make!P; this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
} }
/** /**
* Returns: Socket. * Returns: Socket.
*/ */
@property inout(Socket) socket() inout pure nothrow @nogc @property inout(Socket) socket() inout pure nothrow @nogc
{ {
return socket_; return socket_;
} }
/** /**
* Returns: New protocol instance. * Returns: New protocol instance.
*/ */
@property Protocol protocol() @property Protocol protocol() @nogc
in in
{ {
assert(protocolFactory !is null, "Protocol isn't set."); assert(protocolFactory !is null, "Protocol isn't set.");
} }
body body
{ {
return protocolFactory(); return protocolFactory();
} }
/** /**
* Invokes new connection callback. * Invokes new connection callback.
*/ */
override void invoke() override void invoke() @nogc
{ {
foreach (io; incoming) foreach (io; incoming)
{ {
io.protocol.connected(cast(DuplexTransport) io.transport); io.protocol.connected(cast(DuplexTransport) io.transport);
} }
} }
} }
/** /**
@ -122,121 +125,121 @@ class ConnectionWatcher : Watcher
*/ */
class IOWatcher : ConnectionWatcher class IOWatcher : ConnectionWatcher
{ {
/// If an exception was thrown the transport should be already invalid. /// If an exception was thrown the transport should be already invalid.
private union private union
{ {
StreamTransport transport_; StreamTransport transport_;
SocketException exception_; SocketException exception_;
} }
private Protocol protocol_; private Protocol protocol_;
/** /**
* Returns: Underlying output buffer. * Returns: Underlying output buffer.
*/ */
package ReadBuffer output; package ReadBuffer!ubyte output;
/** /**
* Params: * Params:
* transport = Transport. * transport = Transport.
* protocol = New instance of the application protocol. * protocol = New instance of the application protocol.
*/ */
this(StreamTransport transport, Protocol protocol) this(StreamTransport transport, Protocol protocol) @nogc
in in
{ {
assert(transport !is null); assert(transport !is null);
assert(protocol !is null); assert(protocol !is null);
} }
body body
{ {
super(); super();
transport_ = transport; transport_ = transport;
protocol_ = protocol; protocol_ = protocol;
output = MmapPool.instance.make!ReadBuffer(); output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
active = true; active = true;
} }
/** /**
* Destroys the watcher. * Destroys the watcher.
*/ */
protected ~this() ~this() @nogc
{ {
MmapPool.instance.dispose(output); MmapPool.instance.dispose(protocol_);
MmapPool.instance.dispose(protocol_); }
}
/** /**
* Assigns a transport. * Assigns a transport.
* *
* Params: * Params:
* transport = Transport. * transport = Transport.
* protocol = Application protocol. * protocol = Application protocol.
* *
* Returns: $(D_KEYWORD this). * Returns: $(D_KEYWORD this).
*/ */
IOWatcher opCall(StreamTransport transport, Protocol protocol) pure nothrow @safe @nogc IOWatcher opCall(StreamTransport transport, Protocol protocol)
in pure nothrow @nogc
{ in
assert(transport !is null); {
assert(protocol !is null); assert(transport !is null);
} assert(protocol !is null);
body }
{ body
transport_ = transport; {
protocol_ = protocol; transport_ = transport;
active = true; protocol_ = protocol;
return this; active = true;
} return this;
}
/** /**
* Returns: Transport used by this watcher. * Returns: Transport used by this watcher.
*/ */
@property inout(StreamTransport) transport() inout pure nothrow @nogc @property inout(StreamTransport) transport() inout pure nothrow @nogc
{ {
return transport_; return transport_;
} }
/** /**
* Sets an exception occurred during a read/write operation. * Sets an exception occurred during a read/write operation.
* *
* Params: * Params:
* exception = Thrown exception. * exception = Thrown exception.
*/ */
@property void exception(SocketException exception) pure nothrow @nogc @property void exception(SocketException exception) pure nothrow @nogc
{ {
exception_ = exception; exception_ = exception;
} }
/** /**
* Returns: Application protocol. * Returns: Application protocol.
*/ */
override @property Protocol protocol() pure nothrow @safe @nogc override @property Protocol protocol() pure nothrow @safe @nogc
{ {
return protocol_; return protocol_;
} }
/** /**
* Returns: Socket. * Returns: Socket.
*/ */
override @property inout(Socket) socket() inout pure nothrow @nogc override @property inout(Socket) socket() inout pure nothrow @nogc
{ {
return transport.socket; return transport.socket;
} }
/** /**
* Invokes the watcher callback. * Invokes the watcher callback.
*/ */
override void invoke() override void invoke() @nogc
{ {
if (output.length) if (output.length)
{ {
protocol.received(output[0..$]); protocol.received(output[0..$]);
output.clear(); output.clear();
} }
else else
{ {
protocol.disconnected(exception_); protocol.disconnected(exception_);
active = false; active = false;
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
/* 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/. */
/*
* Internal package used by containers that rely on entries/nodes.
*
* 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.entry;
version (unittest)
{
package struct ConstEqualsStruct
{
int opEquals(typeof(this) that) const @nogc
{
return true;
}
}
package struct MutableEqualsStruct
{
int opEquals(typeof(this) that) @nogc
{
return true;
}
}
package struct NoEqualsStruct
{
}
}
package struct Entry(T)
{
/// Item content.
T content;
/// Next item.
Entry* next;
}

View File

@ -10,48 +10,51 @@
*/ */
module tanya.container.list; module tanya.container.list;
import tanya.container.entry;
import tanya.memory; import tanya.memory;
/** /**
* Singly linked list. * Singly-linked list.
* *
* Params: * Params:
* T = Content type. * T = Content type.
*/ */
class SList(T) struct SList(T)
{ {
/**
* Creates a new $(D_PSYMBOL SList).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(shared Allocator allocator = defaultAllocator)
{
this.allocator = allocator;
reset();
}
/** /**
* Removes all elements from the list. * Removes all elements from the list.
*/ */
~this() ~this()
{
clear();
}
/**
* Removes all contents from the list.
*/
void clear()
{ {
while (!empty) while (!empty)
{ {
static if (isFinalizable!T)
{
dispose(allocator, front);
}
popFront(); popFront();
} }
} }
///
unittest
{
SList!int l;
l.insertFront(8);
l.insertFront(5);
l.clear();
assert(l.empty);
}
/** /**
* Returns: First element. * Returns: First element.
*/ */
@property ref T front() @property ref inout(T) front() inout
in in
{ {
assert(!empty); assert(!empty);
@ -67,64 +70,39 @@ class SList(T)
* Params: * Params:
* x = New element. * x = New element.
*/ */
@property void front(T x) void insertFront(ref T x)
{ {
Entry* temp = make!Entry(allocator); auto temp = allocator.make!(Entry!T);
temp.content = x; temp.content = x;
temp.next = first.next; temp.next = first.next;
first.next = temp; first.next = temp;
} }
/// /// Ditto.
unittest void insertFront(T x)
{ {
auto l = make!(SList!int)(defaultAllocator); insertFront(x);
int[2] values = [8, 9];
l.front = values[0];
assert(l.front == values[0]);
l.front = values[1];
assert(l.front == values[1]);
dispose(defaultAllocator, l);
} }
/** /// Ditto.
* Inserts a new element at the beginning. alias insert = insertFront;
*
* Params:
* x = New element.
*
* Returns: $(D_KEYWORD this).
*/
typeof(this) opOpAssign(string Op)(ref T x)
if (Op == "~")
{
front = x;
return this;
}
/// ///
unittest unittest
{ {
auto l = make!(SList!int)(defaultAllocator); SList!int l;
int value = 5;
assert(l.empty); l.insertFront(8);
assert(l.front == 8);
l ~= value; l.insertFront(9);
assert(l.front == 9);
assert(l.front == value);
assert(!l.empty);
dispose(defaultAllocator, l);
} }
/** /**
* Returns: $(D_KEYWORD true) if the list is empty. * Returns: $(D_KEYWORD true) if the list is empty.
*/ */
@property bool empty() const @safe pure nothrow @property bool empty() const
{ {
return first.next is null; return first.next is null;
} }
@ -134,7 +112,7 @@ class SList(T)
* *
* Returns: The first element. * Returns: The first element.
*/ */
T popFront() void popFront()
in in
{ {
assert(!empty); assert(!empty);
@ -142,96 +120,60 @@ class SList(T)
body body
{ {
auto n = first.next.next; auto n = first.next.next;
auto content = first.next.content;
dispose(allocator, first.next); allocator.dispose(first.next);
first.next = n; first.next = n;
return content;
} }
/// ///
unittest unittest
{ {
auto l = make!(SList!int)(defaultAllocator); SList!int l;
int[2] values = [8, 9];
l.front = values[0]; l.insertFront(8);
l.front = values[1]; l.insertFront(9);
assert(l.front == values[1]); assert(l.front == 9);
l.popFront(); l.popFront();
assert(l.front == values[0]); assert(l.front == 8);
dispose(defaultAllocator, l);
} }
/** /**
* Returns the current item from the list and removes from the list. * Removes $(D_PARAM howMany) elements from the list.
*
* Unlike $(D_PSYMBOL popFront()), this method doesn't fail, if it could not
* remove $(D_PARAM howMany) elements. Instead, if $(D_PARAM howMany) is
* greater than the list length, all elements are removed.
* *
* Params: * Params:
* x = The item should be removed. * howMany = How many elements should be removed.
* *
* Returns: Removed item. * Returns: The number of elements removed.
*/ */
T remove() size_t removeFront(in size_t howMany = 1)
in
{ {
assert(!empty); size_t i;
for (; i < howMany && !empty; ++i)
{
popFront();
}
return i;
} }
body
{
auto temp = position.next.next;
auto content = position.next.content;
dispose(allocator, position.next); /// Ditto.
position.next = temp; alias remove = removeFront;
return content;
}
/// ///
unittest unittest
{ {
auto l = make!(SList!int)(defaultAllocator); SList!int l;
int[3] values = [8, 5, 4];
l.front = values[0]; l.insertFront(8);
l.front = values[1]; l.insertFront(5);
assert(l.remove() == 5); l.insertFront(4);
l.front = values[2]; assert(l.removeFront(0) == 0);
assert(l.remove() == 4); assert(l.removeFront(2) == 2);
assert(l.remove() == 8); assert(l.removeFront(3) == 1);
assert(l.empty); assert(l.removeFront(3) == 0);
dispose(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);
dispose(defaultAllocator, l);
} }
/** /**
@ -240,165 +182,85 @@ class SList(T)
* Params: * Params:
* dg = $(D_KEYWORD foreach) body. * dg = $(D_KEYWORD foreach) body.
*/ */
int opApply(int delegate(ref size_t i, ref T) dg) int opApply(scope int delegate(ref size_t i, ref T) dg)
{ {
int result; int result;
size_t i; size_t i;
for (position = first.next; position; position = position.next, ++i) for (auto pos = first.next; pos; pos = pos.next, ++i)
{ {
result = dg(i, position.content); result = dg(i, pos.content);
if (result != 0) if (result != 0)
{ {
return result; return result;
} }
} }
reset();
return result; 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]);
}
dispose(defaultAllocator, l);
}
/// Ditto. /// Ditto.
int opApply(int delegate(ref T) dg) int opApply(scope int delegate(ref T) dg)
{ {
int result; int result;
for (position = first.next; position; position = position.next) for (auto pos = first.next; pos; pos = pos.next)
{ {
result = dg(position.content); result = dg(pos.content);
if (result != 0) if (result != 0)
{ {
return result; return result;
} }
} }
reset();
return result; return result;
} }
/// ///
unittest unittest
{ {
auto l = make!(SList!int)(defaultAllocator); SList!int l;
int[3] values = [5, 4, 9];
size_t i;
l.front = values[0]; l.insertFront(5);
l.front = values[1]; l.insertFront(4);
l.front = values[2]; l.insertFront(9);
foreach (e; l) foreach (i, e; l)
{ {
assert(i != 0 || e == values[2]); assert(i != 0 || e == 9);
assert(i != 1 || e == values[1]); assert(i != 1 || e == 4);
assert(i != 2 || e == values[0]); assert(i != 2 || e == 5);
++i;
} }
dispose(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. /// 0th element of the list.
protected Entry first; private Entry!T first;
/// Current position in the list. mixin DefaultAllocator;
protected Entry* position;
private shared Allocator allocator;
}
interface Stuff
{
} }
/// ///
unittest unittest
{ {
auto l = make!(SList!Stuff)(defaultAllocator); SList!int l;
size_t i;
dispose(defaultAllocator, l); l.insertFront(5);
l.insertFront(4);
l.insertFront(9);
foreach (e; l)
{
assert(i != 0 || e == 9);
assert(i != 1 || e == 4);
assert(i != 2 || e == 5);
++i;
}
assert(i == 3);
}
private unittest
{
interface Stuff
{
}
static assert(is(SList!Stuff));
} }

View File

@ -10,46 +10,187 @@
*/ */
module tanya.container.queue; module tanya.container.queue;
import tanya.container.entry;
import std.traits;
import tanya.memory; import tanya.memory;
/** /**
* Queue. * FIFO queue.
* *
* Params: * Params:
* T = Content type. * T = Content type.
*/ */
class Queue(T) struct Queue(T)
{ {
/**
* Creates a new $(D_PSYMBOL Queue).
*
* Params:
* allocator = The allocator should be used for the element
* allocations.
*/
this(shared Allocator allocator = defaultAllocator)
{
this.allocator = allocator;
}
/** /**
* Removes all elements from the queue. * Removes all elements from the queue.
*/ */
~this() ~this()
{ {
foreach (e; this) clear();
}
/**
* Removes all elements from the queue.
*/
void clear()
{
while (!empty)
{ {
static if (isFinalizable!T) popFront();
{
dispose(allocator, e);
}
} }
} }
///
unittest
{
Queue!int q;
assert(q.empty);
q.insertBack(8);
q.insertBack(9);
q.clear();
assert(q.empty);
}
/**
* Returns how many elements are in the queue. It iterates through the queue
* to count the elements.
*
* Returns: How many elements are in the queue.
*/
size_t length() const
{
size_t len;
for (const(Entry!T)* i = first.next; i !is null; i = i.next)
{
++len;
}
return len;
}
///
unittest
{
Queue!int q;
assert(q.length == 0);
q.insertBack(5);
assert(q.length == 1);
q.insertBack(4);
assert(q.length == 2);
q.insertBack(9);
assert(q.length == 3);
q.popFront();
assert(q.length == 2);
q.popFront();
assert(q.length == 1);
q.popFront();
assert(q.length == 0);
}
version (D_Ddoc)
{
/**
* Compares two queues. Checks if all elements of the both queues are equal.
*
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
*/
int opEquals(ref typeof(this) that);
/// Ditto.
int opEquals(typeof(this) that);
}
else static if (!hasMember!(T, "opEquals")
|| (functionAttributes!(T.opEquals) & FunctionAttribute.const_))
{
bool opEquals(in ref typeof(this) that) const
{
const(Entry!T)* i = first.next;
const(Entry!T)* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
/// Ditto.
bool opEquals(in typeof(this) that) const
{
return opEquals(that);
}
}
else
{
/**
* Compares two queues. Checks if all elements of the both queues are equal.
*
* Returns: How many elements are in the queue.
*/
bool opEquals(ref typeof(this) that)
{
Entry!T* i = first.next;
Entry!T* j = that.first.next;
while (i !is null && j !is null)
{
if (i.content != j.content)
{
return false;
}
i = i.next;
j = j.next;
}
return i is null && j is null;
}
/// Ditto.
bool opEquals(typeof(this) that)
{
return opEquals(that);
}
}
///
unittest
{
Queue!int q1, q2;
q1.insertBack(5);
q1.insertBack(4);
q2.insertBack(5);
assert(q1 != q2);
q2.insertBack(4);
assert(q1 == q2);
q2.popFront();
assert(q1 != q2);
q1.popFront();
assert(q1 == q2);
q1.popFront();
q2.popFront();
assert(q1 == q2);
}
private unittest
{
static assert(is(Queue!ConstEqualsStruct));
static assert(is(Queue!MutableEqualsStruct));
static assert(is(Queue!NoEqualsStruct));
}
/** /**
* Returns: First element. * Returns: First element.
*/ */
@property ref T front() @property ref inout(T) front() inout
in in
{ {
assert(!empty); assert(!empty);
@ -64,12 +205,10 @@ class Queue(T)
* *
* Params: * Params:
* x = New element. * x = New element.
*
* Returns: $(D_KEYWORD this).
*/ */
typeof(this) insertBack(T x) void insertBack(ref T x)
{ {
Entry* temp = make!Entry(allocator); auto temp = allocator.make!(Entry!T);
temp.content = x; temp.content = x;
@ -82,60 +221,33 @@ class Queue(T)
rear.next = temp; rear.next = temp;
rear = rear.next; rear = rear.next;
} }
return this;
} }
/// Ditto.
void insertBack(T x)
{
insertBack(x);
}
/// Ditto.
alias insert = insertBack; alias insert = insertBack;
/// ///
unittest unittest
{ {
auto q = make!(Queue!int)(defaultAllocator); Queue!int q;
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]);
dispose(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); assert(q.empty);
q.insertBack(8);
q ~= value; assert(q.front == 8);
q.insertBack(9);
assert(q.front == value); assert(q.front == 8);
assert(!q.empty);
dispose(defaultAllocator, q);
} }
/** /**
* Returns: $(D_KEYWORD true) if the queue is empty. * Returns: $(D_KEYWORD true) if the queue is empty.
*/ */
@property bool empty() const @safe pure nothrow @property bool empty() const
{ {
return first.next is null; return first.next is null;
} }
@ -143,25 +255,22 @@ class Queue(T)
/// ///
unittest unittest
{ {
auto q = make!(Queue!int)(defaultAllocator); Queue!int q;
int value = 7; int value = 7;
assert(q.empty); assert(q.empty);
q.insertBack(value); q.insertBack(value);
assert(!q.empty); assert(!q.empty);
dispose(defaultAllocator, q);
} }
/** /**
* Move position to the next element. * Move the position to the next element.
*
* Returns: $(D_KEYWORD this).
*/ */
typeof(this) popFront() void popFront()
in in
{ {
assert(!empty); assert(!empty);
assert(allocator !is null);
} }
body body
{ {
@ -169,50 +278,122 @@ class Queue(T)
dispose(allocator, first.next); dispose(allocator, first.next);
first.next = n; first.next = n;
return this;
} }
/// ///
unittest unittest
{ {
auto q = make!(Queue!int)(defaultAllocator); Queue!int q;
int[2] values = [8, 9];
q.insertBack(values[0]); q.insertBack(8);
q.insertBack(values[1]); q.insertBack(9);
assert(q.front is values[0]); assert(q.front == 8);
q.popFront(); q.popFront();
assert(q.front is values[1]); assert(q.front == 9);
dispose(defaultAllocator, q);
} }
/** /**
* Queue entry. * $(D_KEYWORD foreach) iteration. The elements will be automatically
* dequeued.
*
* Params:
* dg = $(D_KEYWORD foreach) body.
*/ */
protected struct Entry int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
{ {
/// Queue item content. int result;
T content;
/// Next list item. for (size_t i = 0; !empty; ++i)
Entry* next; {
if ((result = dg(i, front)) != 0)
{
return result;
}
popFront();
}
return result;
}
/// Ditto.
int opApply(scope int delegate(ref T) @nogc dg)
{
int result;
while (!empty)
{
if ((result = dg(front)) != 0)
{
return result;
}
popFront();
}
return result;
}
///
unittest
{
Queue!int q;
size_t j;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
foreach (i, e; q)
{
assert(i != 2 || e == 9);
assert(i != 1 || e == 4);
assert(i != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
j = 0;
q.insertBack(5);
q.insertBack(4);
q.insertBack(9);
foreach (e; q)
{
assert(j != 2 || e == 9);
assert(j != 1 || e == 4);
assert(j != 0 || e == 5);
++j;
}
assert(j == 3);
assert(q.empty);
} }
/// The first element of the list. /// The first element of the list.
protected Entry first; private Entry!T first;
/// The last element of the list. /// The last element of the list.
protected Entry* rear; private Entry!T* rear;
private shared Allocator allocator; mixin DefaultAllocator;
} }
/// ///
unittest unittest
{ {
auto q = make!(Queue!int)(defaultAllocator); Queue!int q;
dispose(defaultAllocator, q); q.insertBack(5);
assert(!q.empty);
q.insertBack(4);
assert(q.front == 5);
q.insertBack(9);
assert(q.front == 5);
q.popFront();
assert(q.front == 4);
foreach (i, ref e; q)
{
assert(i != 0 || e == 4);
assert(i != 1 || e == 9);
}
assert(q.empty);
} }

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/ */
module tanya.container.bit; module tanya.crypto.bit;
/** /**
* Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array. * Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array.

View File

@ -10,7 +10,7 @@
*/ */
module tanya.crypto.des; module tanya.crypto.des;
import tanya.container.bit; import tanya.crypto.bit;
import tanya.crypto.symmetric; import tanya.crypto.symmetric;
/// Initial permutation table. /// Initial permutation table.

View File

@ -20,26 +20,26 @@ import std.typecons;
* Supported padding mode. * Supported padding mode.
* *
* See_Also: * See_Also:
* $(D_PSYMBOL pad) * $(D_PSYMBOL pad)
*/ */
enum PaddingMode enum PaddingMode
{ {
zero, zero,
pkcs7, pkcs7,
ansiX923, ansiX923,
} }
/** /**
* Params: * Params:
* input = Sequence that should be padded. * input = Sequence that should be padded.
* mode = Padding mode. * mode = Padding mode.
* blockSize = Block size. * blockSize = Block size.
* allocator = Allocator was used to allocate $(D_PARAM input). * allocator = Allocator was used to allocate $(D_PARAM input).
* *
* Returns: The function modifies the initial array and returns it. * Returns: The function modifies the initial array and returns it.
* *
* See_Also: * See_Also:
* $(D_PSYMBOL PaddingMode) * $(D_PSYMBOL PaddingMode)
*/ */
ubyte[] pad(ref ubyte[] input, ubyte[] pad(ref ubyte[] input,
in PaddingMode mode, in PaddingMode mode,
@ -47,159 +47,159 @@ ubyte[] pad(ref ubyte[] input,
shared Allocator allocator = defaultAllocator) shared Allocator allocator = defaultAllocator)
in in
{ {
assert(blockSize > 0 && blockSize <= 256); assert(blockSize > 0 && blockSize <= 256);
assert(blockSize % 64 == 0); assert(blockSize % 64 == 0);
assert(input.length > 0); assert(input.length > 0);
} }
body body
{ {
immutable rest = cast(ubyte) input.length % blockSize; immutable rest = cast(ubyte) input.length % blockSize;
immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize); immutable size_t lastBlock = input.length - (rest > 0 ? rest : blockSize);
immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0); immutable needed = cast(ubyte) (rest > 0 ? blockSize - rest : 0);
final switch (mode) with (PaddingMode) final switch (mode) with (PaddingMode)
{ {
case zero: case zero:
allocator.expandArray(input, needed); allocator.resizeArray(input, input.length + needed);
break; break;
case pkcs7: case pkcs7:
if (needed) if (needed)
{ {
allocator.expandArray(input, needed); allocator.resizeArray(input, input.length + needed);
input[input.length - needed ..$].each!((ref e) => e = needed); input[input.length - needed ..$].each!((ref e) => e = needed);
} }
else else
{ {
allocator.expandArray(input, blockSize); allocator.resizeArray(input, input.length + blockSize);
} }
break; break;
case ansiX923: case ansiX923:
allocator.expandArray(input, needed ? needed : blockSize); allocator.resizeArray(input, input.length + (needed ? needed : blockSize));
input[$ - 1] = needed; input[$ - 1] = needed;
break; break;
} }
return input; return input;
} }
/// ///
unittest unittest
{ {
{ // Zeros { // Zeros
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64); pad(input, PaddingMode.zero, 64);
assert(input.length == 64); assert(input.length == 64);
pad(input, PaddingMode.zero, 64); pad(input, PaddingMode.zero, 64);
assert(input.length == 64); assert(input.length == 64);
assert(input[63] == 0); assert(input[63] == 0);
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
} }
{ // PKCS#7 { // PKCS#7
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i) for (ubyte i; i < 40; ++i)
{ {
input[i] = i; input[i] = i;
} }
pad(input, PaddingMode.pkcs7, 64); pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 64); assert(input.length == 64);
for (ubyte i; i < 64; ++i) for (ubyte i; i < 64; ++i)
{ {
if (i >= 40 && i < 50) if (i >= 40 && i < 50)
{ {
assert(input[i] == 0); assert(input[i] == 0);
} }
else if (i >= 50) else if (i >= 50)
{ {
assert(input[i] == 14); assert(input[i] == 14);
} }
else else
{ {
assert(input[i] == i); assert(input[i] == i);
} }
} }
pad(input, PaddingMode.pkcs7, 64); pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 128); assert(input.length == 128);
for (ubyte i; i < 128; ++i) for (ubyte i; i < 128; ++i)
{ {
if (i >= 64 || (i >= 40 && i < 50)) if (i >= 64 || (i >= 40 && i < 50))
{ {
assert(input[i] == 0); assert(input[i] == 0);
} }
else if (i >= 50 && i < 64) else if (i >= 50 && i < 64)
{ {
assert(input[i] == 14); assert(input[i] == 14);
} }
else else
{ {
assert(input[i] == i); assert(input[i] == i);
} }
} }
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
} }
{ // ANSI X.923 { // ANSI X.923
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i) for (ubyte i; i < 40; ++i)
{ {
input[i] = i; input[i] = i;
} }
pad(input, PaddingMode.ansiX923, 64); pad(input, PaddingMode.ansiX923, 64);
assert(input.length == 64); assert(input.length == 64);
for (ubyte i; i < 64; ++i) for (ubyte i; i < 64; ++i)
{ {
if (i < 40) if (i < 40)
{ {
assert(input[i] == i); assert(input[i] == i);
} }
else if (i == 63) else if (i == 63)
{ {
assert(input[i] == 14); assert(input[i] == 14);
} }
else else
{ {
assert(input[i] == 0); assert(input[i] == 0);
} }
} }
pad(input, PaddingMode.pkcs7, 64); pad(input, PaddingMode.pkcs7, 64);
assert(input.length == 128); assert(input.length == 128);
for (ubyte i = 0; i < 128; ++i) for (ubyte i = 0; i < 128; ++i)
{ {
if (i < 40) if (i < 40)
{ {
assert(input[i] == i); assert(input[i] == i);
} }
else if (i == 63) else if (i == 63)
{ {
assert(input[i] == 14); assert(input[i] == 14);
} }
else else
{ {
assert(input[i] == 0); assert(input[i] == 0);
} }
} }
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
} }
} }
/** /**
* Params: * Params:
* input = Sequence that should be padded. * input = Sequence that should be padded.
* mode = Padding mode. * mode = Padding mode.
* blockSize = Block size. * blockSize = Block size.
* allocator = Allocator was used to allocate $(D_PARAM input). * allocator = Allocator was used to allocate $(D_PARAM input).
* *
* Returns: The function modifies the initial array and returns it. * Returns: The function modifies the initial array and returns it.
* *
* See_Also: * See_Also:
* $(D_PSYMBOL pad) * $(D_PSYMBOL pad)
*/ */
ref ubyte[] unpad(ref ubyte[] input, ref ubyte[] unpad(ref ubyte[] input,
in PaddingMode mode, in PaddingMode mode,
@ -207,73 +207,73 @@ ref ubyte[] unpad(ref ubyte[] input,
shared Allocator allocator = defaultAllocator) shared Allocator allocator = defaultAllocator)
in in
{ {
assert(input.length != 0); assert(input.length != 0);
assert(input.length % 64 == 0); assert(input.length % 64 == 0);
} }
body body
{ {
final switch (mode) with (PaddingMode) final switch (mode) with (PaddingMode)
{ {
case zero: case zero:
break; break;
case pkcs7: case pkcs7:
case ansiX923: case ansiX923:
immutable last = input[$ - 1]; immutable last = input[$ - 1];
allocator.shrinkArray(input, last ? last : blockSize); allocator.resizeArray(input, input.length - (last ? last : blockSize));
break; break;
} }
return input; return input;
} }
/// ///
unittest unittest
{ {
{ // Zeros { // Zeros
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50); auto inputDup = defaultAllocator.makeArray!ubyte(50);
pad(input, PaddingMode.zero, 64); pad(input, PaddingMode.zero, 64);
pad(inputDup, PaddingMode.zero, 64); pad(inputDup, PaddingMode.zero, 64);
unpad(input, PaddingMode.zero, 64); unpad(input, PaddingMode.zero, 64);
assert(input == inputDup); assert(input == inputDup);
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup); defaultAllocator.dispose(inputDup);
} }
{ // PKCS#7 { // PKCS#7
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50); auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i) for (ubyte i; i < 40; ++i)
{ {
input[i] = i; input[i] = i;
inputDup[i] = i; inputDup[i] = i;
} }
pad(input, PaddingMode.pkcs7, 64); pad(input, PaddingMode.pkcs7, 64);
unpad(input, PaddingMode.pkcs7, 64); unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup); assert(input == inputDup);
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup); defaultAllocator.dispose(inputDup);
} }
{ // ANSI X.923 { // ANSI X.923
auto input = defaultAllocator.makeArray!ubyte(50); auto input = defaultAllocator.makeArray!ubyte(50);
auto inputDup = defaultAllocator.makeArray!ubyte(50); auto inputDup = defaultAllocator.makeArray!ubyte(50);
for (ubyte i; i < 40; ++i) for (ubyte i; i < 40; ++i)
{ {
input[i] = i; input[i] = i;
inputDup[i] = i; inputDup[i] = i;
} }
pad(input, PaddingMode.pkcs7, 64); pad(input, PaddingMode.pkcs7, 64);
unpad(input, PaddingMode.pkcs7, 64); unpad(input, PaddingMode.pkcs7, 64);
assert(input == inputDup); assert(input == inputDup);
defaultAllocator.dispose(input); defaultAllocator.dispose(input);
defaultAllocator.dispose(inputDup); defaultAllocator.dispose(inputDup);
} }
} }

View File

@ -10,9 +10,7 @@
*/ */
module tanya.crypto; module tanya.crypto;
public public import tanya.crypto.bit;
{ public import tanya.crypto.des;
import tanya.crypto.des; public import tanya.crypto.mode;
import tanya.crypto.mode; public import tanya.crypto.symmetric;
import tanya.crypto.symmetric;
}

View File

@ -1,112 +0,0 @@
/* 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)));
}

1060
source/tanya/math/mp.d Normal file

File diff suppressed because it is too large Load Diff

172
source/tanya/math/package.d Normal file
View File

@ -0,0 +1,172 @@
/* 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.math;
import std.traits;
public import tanya.math.mp;
public import tanya.math.random;
version (unittest)
{
import std.algorithm.iteration;
}
/**
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
*
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
* is used to allocate the result.
*
* Params:
* I = Base type.
* G = Exponent type.
* H = Divisor type:
* 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).
*
* Precondition: $(D_INLINECODE z > 0)
*/
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
if (isIntegral!I && isIntegral!G && isIntegral!H)
in
{
assert(z > 0, "Division by zero.");
}
body
{
G mask = G.max / 2 + 1;
H result;
if (y == 0)
{
return 1 % z;
}
else if (y == 1)
{
return x % z;
}
do
{
immutable bit = y & mask;
if (!result && bit)
{
result = x;
continue;
}
result *= result;
if (bit)
{
result *= x;
}
result %= z;
}
while (mask >>= 1);
return result;
}
/// Ditto.
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
if (is(I == Integer))
in
{
assert(z.length > 0, "Division by zero.");
}
body
{
size_t i = y.length;
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
Integer result = Integer(x.allocator);
if (x.length == 0 && i != 0)
{
i = 0;
}
else
{
result = 1;
}
while (i)
{
--i;
for (ubyte mask = 0x01; mask; mask <<= 1)
{
if (y.rep[i] & mask)
{
result *= tmp1;
result %= z;
}
tmp2 = tmp1;
tmp1 *= tmp2;
tmp1 %= z;
}
}
return result;
}
///
pure nothrow @safe @nogc 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);
}
///
unittest
{
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
assert(cast(long) pow(Integer(0), Integer(5), Integer(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) nothrow pure @safe @nogc
{
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)));
}

View File

@ -3,16 +3,18 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/** /**
* Random number generator.
*
* Copyright: Eugene Wissner 2016. * Copyright: Eugene Wissner 2016.
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
* Mozilla Public License, v. 2.0). * Mozilla Public License, v. 2.0).
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner) * Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
*/ */
module tanya.random; module tanya.math.random;
import tanya.memory;
import std.digest.sha; import std.digest.sha;
import std.typecons; import std.typecons;
import tanya.memory;
/// Block size of entropy accumulator (SHA-512). /// Block size of entropy accumulator (SHA-512).
enum blockSize = 64; enum blockSize = 64;
@ -148,7 +150,7 @@ version (linux)
/** /**
* Pseudorandom number generator. * Pseudorandom number generator.
* --- * ---
* auto entropy = defaultAllocator.make!Entropy; * auto entropy = defaultAllocator.make!Entropy();
* *
* ubyte[blockSize] output; * ubyte[blockSize] output;
* *

View File

@ -10,98 +10,118 @@
*/ */
module tanya.memory.allocator; module tanya.memory.allocator;
import std.experimental.allocator;
import std.traits;
version (unittest)
{
import tanya.memory : defaultAllocator;
}
/** /**
* Allocator interface. * Abstract class implementing a basic allocator.
*/ */
interface Allocator interface Allocator
{ {
/** /**
* Allocates $(D_PARAM size) bytes of memory. * Returns: Alignment offered.
* */
* Params: @property uint alignment() const shared pure nothrow @safe @nogc;
* size = Amount of memory to allocate.
*
* Returns: The pointer to the new allocated memory.
*/
void[] allocate(size_t size) shared;
/** /**
* Deallocates a memory block. * Allocates $(D_PARAM size) bytes of memory.
* *
* Params: * Params:
* p = A pointer to the memory block to be freed. * size = Amount of memory to allocate.
* *
* Returns: Whether the deallocation was successful. * Returns: Pointer to the new allocated memory.
*/ */
bool deallocate(void[] p) shared; void[] allocate(in size_t size) shared nothrow @nogc;
/** /**
* Increases or decreases the size of a memory block. * Deallocates a memory block.
* *
* Params: * Params:
* p = A pointer to the memory block. * p = A pointer to the memory block to be freed.
* size = Size of the reallocated block. *
* * Returns: Whether the deallocation was successful.
* Returns: Whether the reallocation was successful. */
*/ bool deallocate(void[] p) shared nothrow @nogc;
bool reallocate(ref void[] p, size_t size) shared;
/** /**
* Returns: The alignment offered. * Increases or decreases the size of a memory block.
*/ *
@property immutable(uint) alignment() shared const @safe pure nothrow; * Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: Pointer to the allocated memory.
*/
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
/**
* Expands a memory block in place.
*
* Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool expand(ref void[] p, in size_t size) shared nothrow @nogc;
} }
/** /**
* Params: * The mixin generates common methods for classes and structs using
* T = Element type of the array being created. * allocators. It provides a protected member, constructor and a read-only property,
* allocator = The allocator used for getting memory. * that checks if an allocator was already set and sets it to the default
* array = A reference to the array being changed. * one, if not (useful for structs which don't have a default constructor).
* length = New array length.
*
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter
*/ */
bool resizeArray(T)(shared Allocator allocator, mixin template DefaultAllocator()
ref T[] array,
in size_t length)
{ {
void[] buf = array; /// Allocator.
protected shared Allocator allocator_;
if (!allocator.reallocate(buf, length * T.sizeof)) /**
{ * Params:
return false; * allocator = The allocator should be used.
} */
array = cast(T[]) buf; this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
return true; /**
* This property checks if the allocator was set in the constructor
* and sets it to the default one, if not.
*
* Returns: Used allocator.
*
* Postcondition: $(D_INLINECODE allocator_ !is null)
*/
protected @property shared(Allocator) allocator() nothrow @safe @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
allocator_ = defaultAllocator;
}
return allocator_;
}
/// Ditto.
@property shared(Allocator) allocator() const nothrow @trusted @nogc
out (allocator)
{
assert(allocator !is null);
}
body
{
if (allocator_ is null)
{
return defaultAllocator;
}
return cast(shared Allocator) allocator_;
}
} }
///
unittest
{
int[] p;
defaultAllocator.resizeArray(p, 20);
assert(p.length == 20);
defaultAllocator.resizeArray(p, 30);
assert(p.length == 30);
defaultAllocator.resizeArray(p, 10);
assert(p.length == 10);
defaultAllocator.resizeArray(p, 0);
assert(p is null);
}
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|| hasElaborateDestructor!T || isDynamicArray!T;

View File

@ -1,173 +0,0 @@
/* 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.mallocator;
import tanya.memory.allocator;
import core.exception;
import core.stdc.stdlib;
import std.algorithm.comparison;
/**
* Wrapper for malloc/realloc/free from the C standard library.
*/
class Mallocator : Allocator
{
/**
* 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) shared @nogc nothrow
{
if (!size)
{
return null;
}
auto p = malloc(size + psize);
if (!p)
{
onOutOfMemoryError();
}
return p[psize.. psize + size];
}
///
@nogc nothrow unittest
{
auto p = Mallocator.instance.allocate(20);
assert(p.length == 20);
Mallocator.instance.deallocate(p);
}
/**
* 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) shared @nogc nothrow
{
if (p !is null)
{
free(p.ptr - psize);
}
return true;
}
///
@nogc nothrow unittest
{
void[] p;
assert(Mallocator.instance.deallocate(p));
p = Mallocator.instance.allocate(10);
assert(Mallocator.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) shared @nogc nothrow
{
if (!size)
{
deallocate(p);
p = null;
return true;
}
else if (p is null)
{
p = allocate(size);
return true;
}
auto r = realloc(p.ptr - psize, size + psize);
if (!r)
{
onOutOfMemoryError();
}
p = r[psize.. psize + size];
return true;
}
///
@nogc nothrow unittest
{
void[] p;
Mallocator.instance.reallocate(p, 20);
assert(p.length == 20);
Mallocator.instance.reallocate(p, 30);
assert(p.length == 30);
Mallocator.instance.reallocate(p, 10);
assert(p.length == 10);
Mallocator.instance.reallocate(p, 0);
assert(p is null);
}
/**
* Returns: The alignment offered.
*/
@property immutable(uint) alignment() shared const @safe pure nothrow
{
return cast(uint) max(double.alignof, real.alignof);
}
/**
* Static allocator instance and initializer.
*
* Returns: The global $(D_PSYMBOL Allocator) instance.
*/
static @property ref shared(Mallocator) instance() @nogc nothrow
{
if (instance_ is null)
{
immutable size = __traits(classInstanceSize, Mallocator) + psize;
void* p = malloc(size);
if (p is null)
{
onOutOfMemoryError();
}
p[psize..size] = typeid(Mallocator).initializer[];
instance_ = cast(shared Mallocator) p[psize..size].ptr;
}
return instance_;
}
///
@nogc nothrow unittest
{
assert(instance is instance);
}
private enum psize = 8;
private shared static Mallocator instance_;
}

View File

@ -10,24 +10,24 @@
*/ */
module tanya.memory.mmappool; module tanya.memory.mmappool;
import core.stdc.string;
import std.algorithm.comparison;
import tanya.memory.allocator; import tanya.memory.allocator;
import core.atomic;
import core.exception;
version (Posix) version (Posix)
{ {
import core.stdc.errno; import core.stdc.errno;
import core.sys.posix.sys.mman; import core.sys.posix.sys.mman;
import core.sys.posix.unistd; import core.sys.posix.unistd;
} }
else version (Windows) else version (Windows)
{ {
import core.sys.windows.winbase; import core.sys.windows.winbase;
import core.sys.windows.windows; import core.sys.windows.windows;
} }
/** /**
* This allocator allocates memory in regions (multiple of 4 KB for example). * This allocator allocates memory in regions (multiple of 64 KB for example).
* Each region is then splitted in blocks. So it doesn't request the memory * 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 * from the operating system on each call, but only if there are no large
* enough free blocks in the available regions. * enough free blocks in the available regions.
@ -47,440 +47,530 @@ else version (Windows)
* | N | -----------> next| || N | | | * | N | -----------> next| || N | | |
* | | | | | || | | | * | | | | | || | | |
* --------------------------------------------------- ------------------------ * --------------------------------------------------- ------------------------
*
* TODO:
* $(UL
* $(LI Thread safety (core.atomic.cas))
* $(LI If two neighbour blocks are free, they can be merged)
* $(LI Reallocation shoud check if there is enough free space in the
* next block instead of always moving the memory)
* $(LI Make 64 KB regions mininmal region size on Linux)
* )
*/ */
class MmapPool : Allocator final class MmapPool : Allocator
{ {
@disable this(); /**
* Allocates $(D_PARAM size) bytes of memory.
*
* Params:
* size = Amount of memory to allocate.
*
* Returns: Pointer to the new allocated memory.
*/
void[] allocate(in size_t size) shared nothrow @nogc
{
if (!size)
{
return null;
}
immutable dataSize = addAlignment(size);
shared static this() void* data = findBlock(dataSize);
{ if (data is null)
version (Posix) {
{ data = initializeRegion(dataSize);
pageSize = sysconf(_SC_PAGE_SIZE); }
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
}
/** return data is null ? null : data[0 .. 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) shared @nogc @trusted nothrow
{
if (!size)
{
return null;
}
immutable dataSize = addAlignment(size);
void* data = findBlock(dataSize); ///
if (data is null) nothrow unittest
{ {
data = initializeRegion(dataSize); auto p = MmapPool.instance.allocate(20);
}
return data is null ? null : data[0..size]; assert(p);
}
/// MmapPool.instance.deallocate(p);
@nogc @safe nothrow unittest }
{
auto p = MmapPool.instance.allocate(20);
assert(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 (aligned).
*
* Returns: Data the block points to or $(D_KEYWORD null).
*/
private void* findBlock(in ref size_t size) shared nothrow @nogc
{
Block block1;
RegionLoop: for (auto r = head; r !is null; r = r.next)
{
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
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_ + BlockEntry.sizeof)
{ // Split the block if needed
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
block2.prev = block1;
block2.next = block1.next;
block2.free = true;
block2.size = block1.size - BlockEntry.sizeof - size;
block2.region = block1.region;
MmapPool.instance.deallocate(p); if (block1.next !is null)
} {
block1.next.prev = block2;
}
block1.next = block2;
block1.size = size;
}
block1.free = false;
block1.region.blocks = block1.region.blocks + 1;
/** return cast(void*) block1 + BlockEntry.sizeof;
* 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) shared @nogc 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; // Merge block with the next one.
block2.free = true; private void mergeNext(Block block) shared const pure nothrow @safe @nogc
{
block.size = block.size + BlockEntry.sizeof + block.next.size;
if (block.next.next !is null)
{
block.next.next.prev = block;
}
block.next = block.next.next;
}
block2.size = block1.size - blockEntrySize - size; /**
block1.size = size; * 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) shared nothrow @nogc
{
if (p is null)
{
return true;
}
block2.region = block1.region; Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
atomicOp!"+="(block1.region.blocks, 1); if (block.region.blocks <= 1)
} {
else if (block.region.prev !is null)
{ {
block1.free = false; block.region.prev.next = block.region.next;
atomicOp!"+="(block1.region.blocks, 1); }
} else // Replace the list head. It is being deallocated
return cast(void*) block1 + blockEntrySize; {
} head = block.region.next;
}
if (block.region.next !is null)
{
block.region.next.prev = block.region.prev;
}
version (Posix)
{
return munmap(cast(void*) block.region, block.region.size) == 0;
}
version (Windows)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
}
}
// Merge blocks if neigbours are free.
if (block.next !is null && block.next.free)
{
mergeNext(block);
}
if (block.prev !is null && block.prev.free)
{
block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
if (block.next !is null)
{
block.next.prev = block.prev;
}
block.prev.next = block.next;
}
else
{
block.free = true;
}
block.region.blocks = block.region.blocks - 1;
return true;
}
/** ///
* Deallocates a memory block. nothrow unittest
* {
* Params: auto p = MmapPool.instance.allocate(20);
* p = A pointer to the memory block to be freed.
*
* Returns: Whether the deallocation was successful.
*/
bool deallocate(void[] p) shared @nogc @trusted nothrow
{
if (p is null)
{
return true;
}
Block block = cast(Block) (p.ptr - blockEntrySize); assert(MmapPool.instance.deallocate(p));
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;
}
version (Posix)
{
return munmap(cast(void*) block.region, block.region.size) == 0;
}
version (Windows)
{
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
}
}
else
{
block.free = true;
atomicOp!"-="(block.region.blocks, 1);
return true;
}
}
/// /**
@nogc @safe nothrow unittest * Expands a memory block in place.
{ *
auto p = MmapPool.instance.allocate(20); * Params:
* p = A pointer to the memory block.
* size = Size of the reallocated block.
*
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
*/
bool expand(ref void[] p, in size_t size) shared nothrow @nogc
{
if (size <= p.length)
{
return true;
}
if (p is null)
{
return false;
}
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
assert(MmapPool.instance.deallocate(p)); if (block1.size >= size)
} {
// Enough space in the current block. Can happen because of the alignment.
p = p.ptr[0 .. size];
return true;
}
immutable dataSize = addAlignment(size);
immutable delta = dataSize - p.length;
/** if (block1.next is null
* Increases or decreases the size of a memory block. || !block1.next.free
* || block1.next.size + BlockEntry.sizeof < delta)
* Params: {
* p = A pointer to the memory block. // It is the last block in the region or the next block is too small or not
* size = Size of the reallocated block. // free.
* return false;
* Returns: Whether the reallocation was successful. }
*/ if (block1.next.size >= delta + alignment_)
bool reallocate(ref void[] p, size_t size) shared @nogc @trusted nothrow {
{ // We should move the start position of the next block. The order may be
void[] reallocP; // important because the old block and the new one can overlap.
auto block2 = cast(Block) (p.ptr + dataSize);
block2.size = block1.next.size - delta;
block2.free = true;
block2.region = block1.region;
block2.next = block1.next.next;
block2.prev = block1;
if (size == p.length) block1.size = block1.size + delta;
{
return true;
}
else if (size > 0)
{
reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
}
if (p !is null) if (block1.next.next !is null)
{ {
if (size > p.length) block1.next.next.prev = block2;
{ }
reallocP[0..p.length] = p[0..$]; block1.next = block2;
} }
else if (size > 0) else
{ {
reallocP[0..size] = p[0..size]; // The next block has enough space, but is too small for further
} // allocations. Merge it with the current block.
deallocate(p); mergeNext(block1);
} }
p = reallocP;
return true; p = p.ptr[0 .. size];
} return true;
}
/// ///
@nogc @safe nothrow unittest nothrow unittest
{ {
void[] p; void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof); assert(!MmapPool.instance.expand(p, 5));
(cast(int[]) p)[7] = 123; assert(p is null);
assert(p.length == 40); p = MmapPool.instance.allocate(1);
auto orig = p.ptr;
MmapPool.instance.reallocate(p, 8 * int.sizeof); assert(MmapPool.instance.expand(p, 2));
assert(p.length == 2);
assert(p.ptr == orig);
assert(p.length == 32); assert(MmapPool.instance.expand(p, 4));
assert((cast(int[]) p)[7] == 123); assert(p.length == 4);
assert(p.ptr == orig);
MmapPool.instance.reallocate(p, 20 * int.sizeof); assert(MmapPool.instance.expand(p, 2));
(cast(int[]) p)[15] = 8; assert(p.length == 4);
assert(p.ptr == orig);
assert(p.length == 80); MmapPool.instance.deallocate(p);
assert((cast(int[]) p)[15] == 8); }
assert((cast(int[]) p)[7] == 123);
MmapPool.instance.reallocate(p, 8 * int.sizeof); /**
* 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, in size_t size) shared nothrow @nogc
{
if (size == 0)
{
if (deallocate(p))
{
p = null;
return true;
}
return false;
}
else if (size <= p.length)
{
// Leave the block as is.
p = p.ptr[0 .. size];
return true;
}
else if (expand(p, size))
{
return true;
}
// Can't extend, allocate a new block, copy and delete the previous.
void[] reallocP = allocate(size);
if (reallocP is null)
{
return false;
}
if (p !is null)
{
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
deallocate(p);
}
p = reallocP;
assert(p.length == 32); return true;
assert((cast(int[]) p)[7] == 123); }
MmapPool.instance.deallocate(p); ///
} nothrow unittest
{
void[] p;
MmapPool.instance.reallocate(p, 10 * int.sizeof);
(cast(int[]) p)[7] = 123;
/** assert(p.length == 40);
* Static allocator instance and initializer.
*
* Returns: Global $(D_PSYMBOL MmapPool) instance.
*/
static @property ref shared(MmapPool) instance() @nogc @trusted nothrow
{
if (instance_ is null)
{
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
Region head; // Will become soon our region list head MmapPool.instance.reallocate(p, 8 * int.sizeof);
void* data = initializeRegion(instanceSize, head);
if (data !is null)
{
data[0..instanceSize] = typeid(MmapPool).initializer[];
instance_ = cast(shared MmapPool) data;
instance_.head = head;
}
}
return instance_;
}
/// assert(p.length == 32);
@nogc @safe nothrow unittest assert((cast(int[]) p)[7] == 123);
{
assert(instance is instance);
}
/** MmapPool.instance.reallocate(p, 20 * int.sizeof);
* Initializes a region for one element. (cast(int[]) p)[15] = 8;
*
* 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) @nogc nothrow
{
immutable regionSize = calculateRegionSize(size);
version (Posix)
{
void* p = mmap(null,
regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{
if (errno == ENOMEM)
{
onOutOfMemoryError();
}
return null;
}
}
else version (Windows)
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
if (p is null)
{
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
{
onOutOfMemoryError();
}
return null;
}
}
Region region = cast(Region) p; assert(p.length == 80);
region.blocks = 1; assert((cast(int[]) p)[15] == 8);
region.size = regionSize; assert((cast(int[]) p)[7] == 123);
// Set the pointer to the head of the region list MmapPool.instance.reallocate(p, 8 * int.sizeof);
if (head !is null)
{
head.prev = region;
}
region.next = head;
region.prev = null;
head = region;
// Initialize the data block assert(p.length == 32);
void* memoryPointer = p + regionEntrySize; assert((cast(int[]) p)[7] == 123);
Block block1 = cast(Block) memoryPointer;
block1.size = size;
block1.free = false;
// It is what we want to return MmapPool.instance.deallocate(p);
void* data = memoryPointer + blockEntrySize; }
// Free block after data /**
memoryPointer = data + size; * Static allocator instance and initializer.
Block block2 = cast(Block) memoryPointer; *
block1.prev = block2.next = null; * Returns: Global $(D_PSYMBOL MmapPool) instance.
block1.next = block2; */
block2.prev = block1; static @property ref shared(MmapPool) instance() nothrow @nogc
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2; {
block2.free = true; if (instance_ is null)
block1.region = block2.region = region; {
// Get system dependend page size.
version (Posix)
{
pageSize = sysconf(_SC_PAGE_SIZE);
if (pageSize < 65536)
{
pageSize = pageSize * 65536 / pageSize;
}
}
else version (Windows)
{
SYSTEM_INFO si;
GetSystemInfo(&si);
pageSize = si.dwPageSize;
}
return data; immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
}
/// Ditto. Region head; // Will become soon our region list head
private void* initializeRegion(size_t size) shared @nogc nothrow void* data = initializeRegion(instanceSize, head);
{ if (data !is null)
return initializeRegion(size, head); {
} memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
instance_ = cast(shared MmapPool) data;
instance_.head = head;
}
}
return instance_;
}
/** ///
* Params: nothrow unittest
* x = Space to be aligned. {
* assert(instance is instance);
* Returns: Aligned size of $(D_PARAM x). }
*/
pragma(inline)
private static immutable(size_t) addAlignment(size_t x)
@nogc @safe pure nothrow
out (result)
{
assert(result > 0);
}
body
{
return (x - 1) / alignment_ * alignment_ + alignment_;
}
/** /*
* Params: * Initializes a region for one element.
* x = Required space. *
* * Params:
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)). * size = Aligned size of the first data block in the region.
*/ * head = Region list head.
pragma(inline) *
private static immutable(size_t) calculateRegionSize(size_t x) * Returns: A pointer to the data.
@nogc @safe pure nothrow */
out (result) private static void* initializeRegion(size_t size, ref Region head)
{ nothrow @nogc
assert(result > 0); {
} immutable regionSize = calculateRegionSize(size);
body
{ version (Posix)
x += regionEntrySize + blockEntrySize * 2; {
return x / pageSize * pageSize + pageSize; void* p = mmap(null,
} regionSize,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0);
if (p is MAP_FAILED)
{
return null;
}
}
else version (Windows)
{
void* p = VirtualAlloc(null,
regionSize,
MEM_COMMIT,
PAGE_READWRITE);
if (p is null)
{
return null;
}
}
@property immutable(uint) alignment() shared const @nogc @safe pure nothrow Region region = cast(Region) p;
{ region.blocks = 1;
return alignment_; region.size = regionSize;
}
private enum alignment_ = 8;
private shared static MmapPool instance_; // 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;
private shared static immutable size_t pageSize; // Initialize the data block
void* memoryPointer = p + RegionEntry.sizeof;
Block block1 = cast(Block) memoryPointer;
block1.size = size;
block1.free = false;
private shared struct RegionEntry // It is what we want to return
{ void* data = memoryPointer + BlockEntry.sizeof;
Region prev;
Region next;
uint blocks;
size_t size;
}
private alias Region = shared RegionEntry*;
private enum regionEntrySize = 32;
private shared Region head; // 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 - RegionEntry.sizeof - BlockEntry.sizeof * 2;
block2.free = true;
block1.region = block2.region = region;
private shared struct BlockEntry return data;
{ }
Block prev;
Block next; private void* initializeRegion(size_t size) shared nothrow @nogc
bool free; {
size_t size; return initializeRegion(size, head);
Region region; }
}
private alias Block = shared BlockEntry*; /*
private enum blockEntrySize = 40; * Params:
* x = Space to be aligned.
*
* Returns: Aligned size of $(D_PARAM x).
*/
private static immutable(size_t) addAlignment(size_t x)
pure nothrow @safe @nogc
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)).
*/
private static immutable(size_t) calculateRegionSize(size_t x)
nothrow @safe @nogc
out (result)
{
assert(result > 0);
}
body
{
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
return x / pageSize * pageSize + pageSize;
}
/**
* Returns: Alignment offered.
*/
@property uint alignment() shared const pure nothrow @safe @nogc
{
return alignment_;
}
private enum alignment_ = 8;
private shared static MmapPool instance_;
private shared static size_t pageSize;
private shared struct RegionEntry
{
Region prev;
Region next;
uint blocks;
size_t size;
}
private alias Region = shared RegionEntry*;
private shared Region head;
private shared struct BlockEntry
{
Block prev;
Block next;
Region region;
size_t size;
bool free;
}
private alias Block = shared BlockEntry*;
} }

View File

@ -10,20 +10,225 @@
*/ */
module tanya.memory; module tanya.memory;
public import core.exception;
{ public import std.experimental.allocator : make, makeArray;
import tanya.memory.allocator; import std.traits;
import std.experimental.allocator : make, dispose, shrinkArray, expandArray, makeArray, dispose; public import tanya.memory.allocator;
} public import tanya.memory.types;
// From druntime
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
shared Allocator allocator; shared Allocator allocator;
@property ref shared(Allocator) defaultAllocator() shared static this() nothrow @trusted @nogc
{ {
import tanya.memory.mallocator; import tanya.memory.mmappool;
if (allocator is null) allocator = MmapPool.instance;
{ }
allocator = Mallocator.instance;
} @property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
return allocator; {
return allocator;
}
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
{
.allocator = allocator;
}
/**
* Returns the size in bytes of the state that needs to be allocated to hold an
* object of type $(D_PARAM T).
*
* Params:
* T = Object type.
*/
template stateSize(T)
{
static if (is(T == class) || is(T == interface))
{
enum stateSize = __traits(classInstanceSize, T);
}
else
{
enum stateSize = T.sizeof;
}
}
/**
* Params:
* size = Raw size.
* alignment = Alignment.
*
* Returns: Aligned size.
*/
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
{
return (size - 1) / alignment * alignment + alignment;
}
/**
* Internal function used to create, resize or destroy a dynamic array. It
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
* allocated part of the array is initialized only if $(D_PARAM Init)
* is set. This function can be trusted only in the data structures that
* can ensure that the array is allocated/rellocated/deallocated with the
* same allocator.
*
* Params:
* T = Element type of the array being created.
* Init = If should be initialized.
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
* allocator = The allocator used for getting memory.
* array = A reference to the array being changed.
* length = New array length.
*
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
* not be reallocated. In the latter
*/
package(tanya) bool resize(T,
bool Init = true,
bool Throws = true)
(shared Allocator allocator,
ref T[] array,
in size_t length) @trusted
{
void[] buf = array;
static if (Init)
{
immutable oldLength = array.length;
}
if (!allocator.reallocate(buf, length * T.sizeof))
{
static if (Throws)
{
onOutOfMemoryError;
}
return false;
}
// Casting from void[] is unsafe, but we know we cast to the original type.
array = cast(T[]) buf;
static if (Init)
{
if (oldLength < length)
{
array[oldLength .. $] = T.init;
}
}
return true;
}
package(tanya) alias resizeArray = resize;
///
unittest
{
int[] p;
defaultAllocator.resizeArray(p, 20);
assert(p.length == 20);
defaultAllocator.resizeArray(p, 30);
assert(p.length == 30);
defaultAllocator.resizeArray(p, 10);
assert(p.length == 10);
defaultAllocator.resizeArray(p, 0);
assert(p is null);
}
/**
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
* It is assumed the respective entities had been allocated with the same
* allocator.
*
* Params:
* T = Type of $(D_PARAM p).
* allocator = Allocator the $(D_PARAM p) was allocated with.
* p = Object or array to be destroyed.
*/
void dispose(T)(shared Allocator allocator, auto ref T* p)
{
static if (hasElaborateDestructor!T)
{
destroy(*p);
}
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
p = null;
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T p)
if (is(T == class) || is(T == interface))
{
if (p is null)
{
return;
}
static if (is(T == interface))
{
version(Windows)
{
import core.sys.windows.unknwn : IUnknown;
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
~ __PRETTY_FUNCTION__);
}
auto ob = cast(Object) p;
}
else
{
alias ob = p;
}
auto ptr = cast(void *) ob;
auto support = ptr[0 .. typeid(ob).initializer.length];
scope (success)
{
() @trusted { allocator.deallocate(support); }();
p = null;
}
auto ppv = cast(void**) ptr;
if (!*ppv)
{
return;
}
auto pc = cast(ClassInfo*) *ppv;
scope (exit)
{
*ppv = null;
}
auto c = *pc;
do
{
// Assume the destructor is @nogc. Leave it nothrow since the destructor
// shouldn't throw and if it does, it is an error anyway.
if (c.destructor)
{
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
}
}
while ((c = c.base) !is null);
if (ppv[1]) // if monitor is not null
{
_d_monitordelete(cast(Object) ptr, true);
}
}
/// Ditto.
void dispose(T)(shared Allocator allocator, auto ref T[] array)
{
static if (hasElaborateDestructor!(typeof(array[0])))
{
foreach (ref e; array)
{
destroy(e);
}
}
() @trusted { allocator.deallocate(array); }();
array = null;
} }

457
source/tanya/memory/types.d Normal file
View File

@ -0,0 +1,457 @@
/* 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.memory.types;
import core.exception;
import std.algorithm.comparison;
import std.algorithm.mutation;
import std.conv;
import std.traits;
import tanya.memory;
/**
* Reference-counted object containing a $(D_PARAM T) value as payload.
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
* when the reference count goes down to zero, frees the underlying store.
*
* Params:
* T = Type of the reference-counted value.
*/
struct RefCounted(T)
{
static if (is(T == class) || is(T == interface))
{
private alias Payload = T;
}
else
{
private alias Payload = T*;
}
private class Storage
{
private Payload payload;
private size_t counter = 1;
private final size_t opUnary(string op)() pure nothrow @safe @nogc
if (op == "--" || op == "++")
in
{
assert(counter > 0);
}
body
{
mixin("return " ~ op ~ "counter;");
}
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
{
if (this.counter > counter)
{
return 1;
}
else if (this.counter < counter)
{
return -1;
}
else
{
return 0;
}
}
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
{
return this.counter == counter;
}
}
private final class RefCountedStorage : Storage
{
private shared Allocator allocator;
this(shared Allocator allocator) pure nothrow @safe @nogc
in
{
assert(allocator !is null);
}
body
{
this.allocator = allocator;
}
~this() nothrow @nogc
{
allocator.dispose(payload);
}
}
private Storage storage;
invariant
{
assert(storage is null || allocator_ !is null);
}
/**
* Takes ownership over $(D_PARAM value), setting the counter to 1.
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
*
* Params:
* value = Value whose ownership is taken over.
* allocator = Allocator used to destroy the $(D_PARAM value) and to
* allocate/deallocate internal storage.
*
* Precondition: $(D_INLINECODE allocator !is null)
*/
this(Payload value, shared Allocator allocator = defaultAllocator)
{
this(allocator);
storage = allocator.make!RefCountedStorage(allocator);
move(value, storage.payload);
}
/// Ditto.
this(shared Allocator allocator)
in
{
assert(allocator !is null);
}
body
{
this.allocator_ = allocator;
}
/**
* Increases the reference counter by one.
*/
this(this)
{
if (count != 0)
{
++storage;
}
}
/**
* Decreases the reference counter by one.
*
* If the counter reaches 0, destroys the owned object.
*/
~this()
{
if (storage !is null && !(storage.counter && --storage))
{
allocator_.dispose(storage);
}
}
/**
* Takes ownership over $(D_PARAM rhs). Initializes this
* $(D_PSYMBOL RefCounted) if needed.
*
* If it is the last reference of the previously owned object,
* it will be destroyed.
*
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
*
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
* be used. If you need a different allocator, create a new
* $(D_PSYMBOL RefCounted) and assign it.
*
* Params:
* rhs = $(D_KEYWORD this).
*/
ref typeof(this) opAssign(Payload rhs)
{
if (storage is null)
{
storage = allocator.make!RefCountedStorage(allocator);
}
else if (storage > 1)
{
--storage;
storage = allocator.make!RefCountedStorage(allocator);
}
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
storage = allocator.make!RefCountedStorage(allocator);
}
else
{
allocator.dispose(storage.payload);
}
move(rhs, storage.payload);
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(null))
{
if (storage is null)
{
return this;
}
else if (storage > 1)
{
--storage;
storage = null;
}
else if (cast(RefCountedStorage) storage is null)
{
// Created with refCounted. Always destroyed togethter with the pointer.
assert(storage.counter != 0);
allocator.dispose(storage);
return this;
}
else
{
allocator.dispose(storage.payload);
}
return this;
}
/// Ditto.
ref typeof(this) opAssign(typeof(this) rhs)
{
swap(allocator_, rhs.allocator_);
swap(storage, rhs.storage);
return this;
}
/**
* Returns: Reference to the owned object.
*/
inout(Payload) get() inout pure nothrow @safe @nogc
in
{
assert(count > 0, "Attempted to access an uninitialized reference.");
}
body
{
return storage.payload;
}
static if (isPointer!Payload)
{
/**
* Params:
* op = Operation.
*
* Dereferences the pointer. It is defined only for pointers, not for
* reference types like classes, that can be accessed directly.
*
* Returns: Reference to the pointed value.
*/
ref T opUnary(string op)()
if (op == "*")
{
return *storage.payload;
}
}
/**
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
* storage.
*/
@property bool isInitialized() const
{
return storage !is null;
}
/**
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
* ownership over the same pointer (including $(D_KEYWORD this)).
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
*/
@property size_t count() const
{
return storage is null ? 0 : storage.counter;
}
mixin DefaultAllocator;
alias get this;
}
///
unittest
{
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
auto val = rc.get;
*val = 8;
assert(*rc.storage.payload == 8);
val = null;
assert(rc.storage.payload !is null);
assert(*rc.storage.payload == 8);
*rc = 9;
assert(*rc.storage.payload == 9);
}
version (unittest)
{
private class A
{
uint *destroyed;
this(ref uint destroyed) @nogc
{
this.destroyed = &destroyed;
}
~this() @nogc
{
++(*destroyed);
}
}
private struct B
{
int prop;
@disable this();
this(int param1) @nogc
{
prop = param1;
}
}
}
private unittest
{
uint destroyed;
auto a = defaultAllocator.make!A(destroyed);
assert(destroyed == 0);
{
auto rc = RefCounted!A(a, defaultAllocator);
assert(rc.count == 1);
void func(RefCounted!A rc)
{
assert(rc.count == 2);
}
func(rc);
assert(rc.count == 1);
}
assert(destroyed == 1);
RefCounted!int rc;
assert(rc.count == 0);
rc = defaultAllocator.make!int(8);
assert(rc.count == 1);
}
private unittest
{
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
static assert(is(typeof(RefCounted!A.storage.payload) == A));
static assert(is(RefCounted!B));
static assert(is(RefCounted!A));
}
/**
* Constructs a new object of type $(D_PARAM T) and wraps it in a
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
* the constructor of $(D_PARAM T).
*
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
* directly, since it allocates only ones (the internal storage and the
* object).
*
* Params:
* T = Type of the constructed object.
* A = Types of the arguments to the constructor of $(D_PARAM T).
* args = Constructor arguments of $(D_PARAM T).
*
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
*/
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
if (!is(T == interface) && !isAbstractClass!T
&& !isArray!T && !isAssociativeArray!T)
{
auto rc = typeof(return)(allocator);
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
immutable size = alignedSize(stateSize!T + storageSize);
auto mem = (() @trusted => allocator.allocate(size))();
if (mem is null)
{
onOutOfMemoryError();
}
scope (failure)
{
() @trusted { allocator.deallocate(mem); }();
}
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
static if (is(T == class))
{
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
}
else
{
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
rc.storage.payload = emplace!T(ptr, args);
}
return rc;
}
///
unittest
{
auto rc = defaultAllocator.refCounted!int(5);
assert(rc.count == 1);
void func(RefCounted!int param)
{
if (param.count == 2)
{
func(param);
}
else
{
assert(param.count == 3);
}
}
func(rc);
assert(rc.count == 1);
}
private @nogc unittest
{
struct E
{
}
auto b = defaultAllocator.refCounted!B(15);
static assert(is(typeof(b.storage.payload) == B*));
static assert(is(typeof(b.prop) == int));
static assert(!is(typeof(defaultAllocator.refCounted!B())));
static assert(is(typeof(defaultAllocator.refCounted!E())));
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
{
auto rc = defaultAllocator.refCounted!B(3);
assert(rc.get.prop == 3);
}
{
auto rc = defaultAllocator.refCounted!E();
assert(rc.count);
}
}

49
source/tanya/meta/gen.d Normal file
View File

@ -0,0 +1,49 @@
/* 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/. */
/**
* Templates that generate values.
*
* 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.meta.gen;
import std.traits;
/**
* Initializer list.
*
* Generates a static array with elements from $(D_PARAM args). All elements
* should have the same type. It can be used in constructors which accept a
* list of the elements of the same type in the situations where variadic
* functions and templates can't be used.
*
* Params:
* Args = Argument type.
* args = Arguments.
*/
enum IL(Args...)(Args args)
if (Args.length > 0)
{
alias BaseType = typeof(args[0]);
BaseType[args.length] result;
foreach (i, a; args)
{
static assert(isImplicitlyConvertible!(typeof(a), BaseType));
result[i] = a;
}
return result;
}
///
unittest
{
static assert(IL(1, 5, 8).length == 3);
static assert(IL(1, 5, 8).sizeof == 3 * int.sizeof);
}

View File

@ -0,0 +1,15 @@
/* 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/. */
/**
* Metaprogramming.
*
* 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.meta;
public import tanya.meta.gen;

File diff suppressed because it is too large Load Diff

1191
source/tanya/network/url.d Normal file

File diff suppressed because it is too large Load Diff