Compare commits
10 Commits
d9fda61fe1
...
90797a48be
Author | SHA1 | Date | |
---|---|---|---|
90797a48be | |||
5453c646f6 | |||
7dd4c44140 | |||
81b4fb88f5 | |||
db607f7602 | |||
cfcb1e727a | |||
c7bfbe0657 | |||
0155039071 | |||
c15a8993ec | |||
be8fcb3e1c |
@ -13,14 +13,13 @@ Garbage Collector heap. Everything in the library is usable in @nogc code.
|
|||||||
Tanya provides data structures and utilities to facilitate painless systems
|
Tanya provides data structures and utilities to facilitate painless systems
|
||||||
programming in D.
|
programming in D.
|
||||||
|
|
||||||
- [API Documentation](https://docs.caraus.io/tanya)
|
- [API Documentation](https://docs.caraus.tech/tanya)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Tanya consists of the following packages and (top-level) modules:
|
Tanya consists of the following packages and (top-level) modules:
|
||||||
|
|
||||||
* `algorithm`: Collection of generic algorithms.
|
* `algorithm`: Collection of generic algorithms.
|
||||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
|
||||||
* `bitmanip`: Bit manipulation.
|
* `bitmanip`: Bit manipulation.
|
||||||
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
|
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
|
||||||
string, Set, Hash table.
|
string, Set, Hash table.
|
||||||
@ -35,9 +34,6 @@ type information at compile-time, to transform from one type to another. It has
|
|||||||
also different algorithms for iterating, searching and modifying template
|
also different algorithms for iterating, searching and modifying template
|
||||||
arguments.
|
arguments.
|
||||||
* `net`: URL-Parsing, network programming.
|
* `net`: URL-Parsing, network programming.
|
||||||
* `network`: Socket implementation. `network` is currently under rework.
|
|
||||||
After finishing the new socket implementation will land in the `net` package and
|
|
||||||
`network` will be deprecated.
|
|
||||||
* `os`: Platform-independent interfaces to operating system functionality.
|
* `os`: Platform-independent interfaces to operating system functionality.
|
||||||
* `range`: Generic functions and templates for D ranges.
|
* `range`: Generic functions and templates for D ranges.
|
||||||
* `test`: Test suite for unittest-blocks.
|
* `test`: Test suite for unittest-blocks.
|
||||||
@ -167,7 +163,7 @@ parameter is used)
|
|||||||
|
|
||||||
| DMD | GCC |
|
| DMD | GCC |
|
||||||
|:-------:|:---------:|
|
|:-------:|:---------:|
|
||||||
| 2.091.1 | gdc trunk |
|
| 2.100.0 | 12.1 |
|
||||||
|
|
||||||
## Further characteristics
|
## Further characteristics
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ if (F.length == 1)
|
|||||||
*
|
*
|
||||||
* Returns: Accumulated value.
|
* Returns: Accumulated value.
|
||||||
*/
|
*/
|
||||||
auto foldr(R, T)(R range, auto ref T init)
|
auto foldr(R, T)(scope R range, auto ref T init)
|
||||||
if (isBidirectionalRange!R)
|
if (isBidirectionalRange!R)
|
||||||
{
|
{
|
||||||
if (range.empty)
|
if (range.empty)
|
||||||
|
@ -177,9 +177,7 @@ if (isInputRange!Range && hasLvalueElements!Range)
|
|||||||
import tanya.memory.op : copy, fill;
|
import tanya.memory.op : copy, fill;
|
||||||
alias T = ElementType!Range;
|
alias T = ElementType!Range;
|
||||||
|
|
||||||
static if (__VERSION__ >= 2083
|
static if (isDynamicArray!Range && __traits(isZeroInit, T))
|
||||||
&& isDynamicArray!Range
|
|
||||||
&& __traits(isZeroInit, T))
|
|
||||||
{
|
{
|
||||||
fill!0(range);
|
fill!0(range);
|
||||||
}
|
}
|
||||||
@ -244,54 +242,3 @@ if (isInputRange!Range && hasLvalueElements!Range)
|
|||||||
|
|
||||||
assert(counter == 2);
|
assert(counter == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Rotates the elements of a union of two ranges.
|
|
||||||
*
|
|
||||||
* Performs a left rotation on the given ranges, as if it would be a signle
|
|
||||||
* range, so that [`front.front`, `back.front`$(RPAREN) is a valid range, that
|
|
||||||
* is $(D_PARAM back) would continue $(D_PARAM front).
|
|
||||||
*
|
|
||||||
* The elements are moved so, that the first element of $(D_PARAM back) becomes
|
|
||||||
* the first element of $(D_PARAM front) without changing the relative order of
|
|
||||||
* their elements.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* Range = Range type.
|
|
||||||
* front = Left half.
|
|
||||||
* back = Right half.
|
|
||||||
*/
|
|
||||||
void rotate(Range)(Range front, Range back)
|
|
||||||
if (isForwardRange!Range && hasSwappableElements!Range)
|
|
||||||
{
|
|
||||||
auto next = back.save();
|
|
||||||
|
|
||||||
while (!front.empty && !next.empty && !sameHead(front, next))
|
|
||||||
{
|
|
||||||
tanya.memory.lifetime.swap(front.front, next.front);
|
|
||||||
front.popFront();
|
|
||||||
next.popFront();
|
|
||||||
|
|
||||||
if (next.empty)
|
|
||||||
{
|
|
||||||
next = back.save();
|
|
||||||
}
|
|
||||||
else if (front.empty)
|
|
||||||
{
|
|
||||||
front = back.save();
|
|
||||||
back = next.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
const int[7] expected = [1, 2, 3, 4, 5, 6, 7];
|
|
||||||
int[7] actual = [5, 6, 3, 4, 1, 2, 7];
|
|
||||||
|
|
||||||
rotate(actual[0 .. 2], actual[4 .. 6]);
|
|
||||||
assert(equal(actual[], expected[]));
|
|
||||||
}
|
|
||||||
|
@ -1,187 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event loop implementation for Linux.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/epoll.d,
|
|
||||||
* tanya/async/event/epoll.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.event.epoll;
|
|
||||||
|
|
||||||
version (D_Ddoc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (linux):
|
|
||||||
|
|
||||||
import core.stdc.errno;
|
|
||||||
public import core.sys.linux.epoll;
|
|
||||||
import core.sys.posix.unistd;
|
|
||||||
import core.time;
|
|
||||||
import std.algorithm.comparison;
|
|
||||||
import tanya.async.event.selector;
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.protocol;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.container.array;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class EpollLoop : SelectorLoop
|
|
||||||
{
|
|
||||||
protected int fd;
|
|
||||||
private Array!epoll_event events;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the loop.
|
|
||||||
*/
|
|
||||||
this() @nogc
|
|
||||||
{
|
|
||||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
|
||||||
{
|
|
||||||
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
|
||||||
}
|
|
||||||
super();
|
|
||||||
events = Array!epoll_event(maxEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees loop internals.
|
|
||||||
*/
|
|
||||||
~this() @nogc
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
protected override bool reify(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc
|
|
||||||
{
|
|
||||||
int op = EPOLL_CTL_DEL;
|
|
||||||
epoll_event ev;
|
|
||||||
|
|
||||||
if (events == oldEvents)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (events && oldEvents)
|
|
||||||
{
|
|
||||||
op = EPOLL_CTL_MOD;
|
|
||||||
}
|
|
||||||
else if (events && !oldEvents)
|
|
||||||
{
|
|
||||||
op = EPOLL_CTL_ADD;
|
|
||||||
}
|
|
||||||
|
|
||||||
ev.data.fd = watcher.socket.handle;
|
|
||||||
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
|
||||||
| (events & Event.write ? EPOLLOUT : 0)
|
|
||||||
| EPOLLET;
|
|
||||||
|
|
||||||
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
protected override void poll() @nogc
|
|
||||||
{
|
|
||||||
// Don't block
|
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
|
||||||
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
|
|
||||||
|
|
||||||
if (eventCount < 0)
|
|
||||||
{
|
|
||||||
if (errno != EINTR)
|
|
||||||
{
|
|
||||||
throw defaultAllocator.make!BadLoopException();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto i = 0; i < eventCount; ++i)
|
|
||||||
{
|
|
||||||
auto transport = cast(StreamTransport) connections[events[i].data.fd];
|
|
||||||
|
|
||||||
if (transport is null)
|
|
||||||
{
|
|
||||||
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
|
|
||||||
assert(connection !is null);
|
|
||||||
|
|
||||||
acceptConnections(connection);
|
|
||||||
}
|
|
||||||
else if (events[i].events & EPOLLERR)
|
|
||||||
{
|
|
||||||
kill(transport);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
|
||||||
{
|
|
||||||
SocketException exception;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ptrdiff_t received;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
received = transport.socket.receive(transport.output[]);
|
|
||||||
transport.output += received;
|
|
||||||
}
|
|
||||||
while (received);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
if (transport.socket.disconnected)
|
|
||||||
{
|
|
||||||
kill(transport, exception);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (transport.output.length)
|
|
||||||
{
|
|
||||||
pendings.insertBack(transport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (events[i].events & EPOLLOUT)
|
|
||||||
{
|
|
||||||
transport.writeReady = true;
|
|
||||||
if (transport.input.length)
|
|
||||||
{
|
|
||||||
feed(transport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: The blocking time.
|
|
||||||
*/
|
|
||||||
override protected @property inout(Duration) blockTime()
|
|
||||||
inout @safe pure nothrow
|
|
||||||
{
|
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,390 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event loop implementation for Windows.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/iocp.d,
|
|
||||||
* tanya/async/event/iocp.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.event.iocp;
|
|
||||||
|
|
||||||
version (D_Ddoc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (Windows):
|
|
||||||
|
|
||||||
import core.sys.windows.mswsock;
|
|
||||||
import core.sys.windows.winsock2;
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.protocol;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
import tanya.sys.windows.winbase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transport for stream sockets.
|
|
||||||
*/
|
|
||||||
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
|
||||||
{
|
|
||||||
private SocketException exception;
|
|
||||||
|
|
||||||
private ReadBuffer!ubyte output;
|
|
||||||
|
|
||||||
private WriteBuffer!ubyte input;
|
|
||||||
|
|
||||||
private Protocol protocol_;
|
|
||||||
|
|
||||||
private bool closing;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates new completion port transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* socket = Socket.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE socket !is null)
|
|
||||||
*/
|
|
||||||
this(OverlappedConnectedSocket socket) @nogc
|
|
||||||
{
|
|
||||||
super(socket);
|
|
||||||
output = ReadBuffer!ubyte(8192, 1024);
|
|
||||||
input = WriteBuffer!ubyte(8192);
|
|
||||||
active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*
|
|
||||||
* Postcondition: $(D_INLINECODE socket !is null)
|
|
||||||
*/
|
|
||||||
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
|
||||||
out (socket)
|
|
||||||
{
|
|
||||||
assert(socket !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
return cast(OverlappedConnectedSocket) socket_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
|
||||||
*/
|
|
||||||
bool isClosing() const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return closing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the transport.
|
|
||||||
*
|
|
||||||
* Buffered data will be flushed. No more data will be received.
|
|
||||||
*/
|
|
||||||
void close() pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
closing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write some data to the transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* data = Data to send.
|
|
||||||
*/
|
|
||||||
void write(ubyte[] data) @nogc
|
|
||||||
{
|
|
||||||
input ~= data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol.
|
|
||||||
*/
|
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return protocol_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the protocol.
|
|
||||||
*
|
|
||||||
* The protocol is deallocated by the event loop.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocol = Application protocol.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
|
||||||
*/
|
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(protocol !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
protocol_ = protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the watcher callback.
|
|
||||||
*/
|
|
||||||
override void invoke() @nogc
|
|
||||||
{
|
|
||||||
if (output.length)
|
|
||||||
{
|
|
||||||
immutable empty = input.length == 0;
|
|
||||||
protocol.received(output[0 .. $]);
|
|
||||||
output.clear();
|
|
||||||
if (empty)
|
|
||||||
{
|
|
||||||
SocketState overlapped;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
overlapped = defaultAllocator.make!SocketState;
|
|
||||||
socket.beginSend(input[], overlapped);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(overlapped);
|
|
||||||
defaultAllocator.dispose(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
protocol.disconnected(exception);
|
|
||||||
defaultAllocator.dispose(protocol_);
|
|
||||||
defaultAllocator.dispose(exception);
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class IOCPLoop : Loop
|
|
||||||
{
|
|
||||||
protected HANDLE completionPort;
|
|
||||||
|
|
||||||
protected OVERLAPPED overlap;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the loop.
|
|
||||||
*/
|
|
||||||
this() @nogc
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
|
|
||||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, null, 0, 0);
|
|
||||||
if (!completionPort)
|
|
||||||
{
|
|
||||||
throw make!BadLoopException(defaultAllocator,
|
|
||||||
"Creating completion port failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
override protected bool reify(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc
|
|
||||||
{
|
|
||||||
SocketState overlapped;
|
|
||||||
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
|
||||||
{
|
|
||||||
auto socket = cast(OverlappedStreamSocket) watcher.socket;
|
|
||||||
assert(socket !is null);
|
|
||||||
|
|
||||||
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
|
||||||
completionPort,
|
|
||||||
cast(size_t) (cast(void*) watcher),
|
|
||||||
0) !is completionPort)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
overlapped = defaultAllocator.make!SocketState;
|
|
||||||
socket.beginAccept(overlapped);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(overlapped);
|
|
||||||
defaultAllocator.dispose(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((!(oldEvents & Event.read) && (events & Event.read))
|
|
||||||
|| (!(oldEvents & Event.write) && (events & Event.write)))
|
|
||||||
{
|
|
||||||
auto transport = cast(StreamTransport) watcher;
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
|
||||||
completionPort,
|
|
||||||
cast(size_t) (cast(void*) watcher),
|
|
||||||
0) !is completionPort)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin to read
|
|
||||||
if (!(oldEvents & Event.read) && (events & Event.read))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
overlapped = defaultAllocator.make!SocketState;
|
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(overlapped);
|
|
||||||
defaultAllocator.dispose(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void kill(StreamTransport transport,
|
|
||||||
SocketException exception = null) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(transport !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
transport.socket.shutdown();
|
|
||||||
defaultAllocator.dispose(transport.socket);
|
|
||||||
transport.exception = exception;
|
|
||||||
pendings.insertBack(transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
override protected void poll() @nogc
|
|
||||||
{
|
|
||||||
DWORD lpNumberOfBytes;
|
|
||||||
size_t key;
|
|
||||||
OVERLAPPED* overlap;
|
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
|
||||||
|
|
||||||
auto result = GetQueuedCompletionStatus(completionPort,
|
|
||||||
&lpNumberOfBytes,
|
|
||||||
&key,
|
|
||||||
&overlap,
|
|
||||||
timeout);
|
|
||||||
if (result == FALSE && overlap is null)
|
|
||||||
{
|
|
||||||
return; // Timeout
|
|
||||||
}
|
|
||||||
|
|
||||||
enum size_t offset = size_t.sizeof * 2;
|
|
||||||
auto overlapped = cast(SocketState) ((cast(void*) overlap) - offset);
|
|
||||||
assert(overlapped !is null);
|
|
||||||
scope (failure)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(overlapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (overlapped.event)
|
|
||||||
{
|
|
||||||
case OverlappedSocketEvent.accept:
|
|
||||||
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
|
||||||
assert(connection !is null);
|
|
||||||
|
|
||||||
auto listener = cast(OverlappedStreamSocket) connection.socket;
|
|
||||||
assert(listener !is null);
|
|
||||||
|
|
||||||
auto socket = listener.endAccept(overlapped);
|
|
||||||
auto transport = defaultAllocator.make!StreamTransport(socket);
|
|
||||||
|
|
||||||
connection.incoming.insertBack(transport);
|
|
||||||
|
|
||||||
reify(transport,
|
|
||||||
EventMask(Event.none),
|
|
||||||
EventMask(Event.read | Event.write));
|
|
||||||
|
|
||||||
pendings.insertBack(connection);
|
|
||||||
listener.beginAccept(overlapped);
|
|
||||||
break;
|
|
||||||
case OverlappedSocketEvent.read:
|
|
||||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
if (!transport.active)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(transport);
|
|
||||||
defaultAllocator.dispose(overlapped);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int received;
|
|
||||||
SocketException exception;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
received = transport.socket.endReceive(overlapped);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
if (transport.socket.disconnected)
|
|
||||||
{
|
|
||||||
// We want to get one last notification to destroy the watcher.
|
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
|
||||||
kill(transport, exception);
|
|
||||||
}
|
|
||||||
else if (received > 0)
|
|
||||||
{
|
|
||||||
immutable full = transport.output.free == received;
|
|
||||||
|
|
||||||
transport.output += received;
|
|
||||||
// Receive was interrupted because the buffer is full. We have to continue.
|
|
||||||
if (full)
|
|
||||||
{
|
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
|
||||||
}
|
|
||||||
pendings.insertBack(transport);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case OverlappedSocketEvent.write:
|
|
||||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
|
||||||
assert(transport !is null);
|
|
||||||
|
|
||||||
transport.input += transport.socket.endSend(overlapped);
|
|
||||||
if (transport.input.length > 0)
|
|
||||||
{
|
|
||||||
transport.socket.beginSend(transport.input[], overlapped);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
|
||||||
if (transport.isClosing())
|
|
||||||
{
|
|
||||||
kill(transport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
assert(false, "Unknown event");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,331 +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/. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Event loop implementation for *BSD.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/kqueue.d,
|
|
||||||
* tanya/async/event/kqueue.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.event.kqueue;
|
|
||||||
|
|
||||||
version (D_Ddoc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (OSX)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (iOS)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (TVOS)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (WatchOS)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (FreeBSD)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (OpenBSD)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
else version (DragonFlyBSD)
|
|
||||||
{
|
|
||||||
version = MacBSD;
|
|
||||||
}
|
|
||||||
|
|
||||||
version (MacBSD):
|
|
||||||
|
|
||||||
import core.stdc.errno;
|
|
||||||
import core.sys.posix.time; // timespec
|
|
||||||
import core.sys.posix.unistd;
|
|
||||||
import core.time;
|
|
||||||
import tanya.algorithm.comparison;
|
|
||||||
import tanya.async.event.selector;
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.container.array;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
|
||||||
{
|
|
||||||
*kevp = kevent_t(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
final class KqueueLoop : SelectorLoop
|
|
||||||
{
|
|
||||||
protected int fd;
|
|
||||||
private Array!kevent_t events;
|
|
||||||
private Array!kevent_t changes;
|
|
||||||
private size_t changeCount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Maximal event count can be got at a time
|
|
||||||
* (should be supported by the backend).
|
|
||||||
*/
|
|
||||||
override protected @property uint maxEvents()
|
|
||||||
const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return cast(uint) events.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
this() @nogc
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
|
|
||||||
if ((fd = kqueue()) == -1)
|
|
||||||
{
|
|
||||||
throw make!BadLoopException(defaultAllocator,
|
|
||||||
"kqueue initialization failed");
|
|
||||||
}
|
|
||||||
events = Array!kevent_t(64);
|
|
||||||
changes = Array!kevent_t(64);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees loop internals.
|
|
||||||
*/
|
|
||||||
~this() @nogc
|
|
||||||
{
|
|
||||||
close(fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void set(SocketType socket, short filter, ushort flags) @nogc
|
|
||||||
{
|
|
||||||
if (changes.length <= changeCount)
|
|
||||||
{
|
|
||||||
changes.length = changeCount + maxEvents;
|
|
||||||
}
|
|
||||||
EV_SET(&changes[changeCount],
|
|
||||||
cast(ulong) socket,
|
|
||||||
filter,
|
|
||||||
flags,
|
|
||||||
0U,
|
|
||||||
0,
|
|
||||||
null);
|
|
||||||
++changeCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
override protected bool reify(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc
|
|
||||||
{
|
|
||||||
if (events != oldEvents)
|
|
||||||
{
|
|
||||||
if (oldEvents & Event.read || oldEvents & Event.accept)
|
|
||||||
{
|
|
||||||
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
|
|
||||||
}
|
|
||||||
if (oldEvents & Event.write)
|
|
||||||
{
|
|
||||||
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (events & (Event.read | events & Event.accept))
|
|
||||||
{
|
|
||||||
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
|
|
||||||
}
|
|
||||||
if (events & Event.write)
|
|
||||||
{
|
|
||||||
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
protected override void poll() @nogc
|
|
||||||
{
|
|
||||||
timespec ts;
|
|
||||||
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
|
||||||
|
|
||||||
if (changeCount > maxEvents)
|
|
||||||
{
|
|
||||||
events.length = changes.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto eventCount = kevent(fd,
|
|
||||||
changes.get().ptr,
|
|
||||||
cast(int) changeCount,
|
|
||||||
events.get().ptr,
|
|
||||||
maxEvents,
|
|
||||||
&ts);
|
|
||||||
changeCount = 0;
|
|
||||||
|
|
||||||
if (eventCount < 0)
|
|
||||||
{
|
|
||||||
if (errno != EINTR)
|
|
||||||
{
|
|
||||||
throw defaultAllocator.make!BadLoopException();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i; i < eventCount; ++i)
|
|
||||||
{
|
|
||||||
assert(connections.length > events[i].ident);
|
|
||||||
|
|
||||||
auto transport = cast(StreamTransport) connections[events[i].ident];
|
|
||||||
// If it is a ConnectionWatcher. Accept connections.
|
|
||||||
if (transport is null)
|
|
||||||
{
|
|
||||||
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
|
|
||||||
assert(connection !is null);
|
|
||||||
|
|
||||||
acceptConnections(connection);
|
|
||||||
}
|
|
||||||
else if (events[i].flags & EV_ERROR)
|
|
||||||
{
|
|
||||||
kill(transport);
|
|
||||||
}
|
|
||||||
else if (events[i].filter == EVFILT_READ)
|
|
||||||
{
|
|
||||||
SocketException exception;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ptrdiff_t received;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
received = transport.socket.receive(transport.output[]);
|
|
||||||
transport.output += received;
|
|
||||||
}
|
|
||||||
while (received);
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
if (transport.socket.disconnected)
|
|
||||||
{
|
|
||||||
kill(transport, exception);
|
|
||||||
}
|
|
||||||
else if (transport.output.length)
|
|
||||||
{
|
|
||||||
pendings.insertBack(transport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (events[i].filter == EVFILT_WRITE)
|
|
||||||
{
|
|
||||||
transport.writeReady = true;
|
|
||||||
if (transport.input.length)
|
|
||||||
{
|
|
||||||
feed(transport);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: The blocking time.
|
|
||||||
*/
|
|
||||||
override protected @property inout(Duration) blockTime()
|
|
||||||
inout @nogc @safe pure nothrow
|
|
||||||
{
|
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the transport couldn't send the data, the further sending should
|
|
||||||
* be handled by the event loop.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* transport = Transport.
|
|
||||||
* exception = Exception thrown on sending.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
|
||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
|
||||||
* transport will be destroyed then).
|
|
||||||
*/
|
|
||||||
protected override bool feed(StreamTransport transport,
|
|
||||||
SocketException exception = null) @nogc
|
|
||||||
{
|
|
||||||
if (!super.feed(transport, exception))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!transport.writeReady)
|
|
||||||
{
|
|
||||||
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,407 +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/. */
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This module contains base implementations for reactor event loops.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/event/selector.d,
|
|
||||||
* tanya/async/event/selector.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.event.selector;
|
|
||||||
|
|
||||||
version (D_Ddoc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (Posix):
|
|
||||||
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.protocol;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.container.array;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transport for stream sockets.
|
|
||||||
*/
|
|
||||||
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
|
||||||
{
|
|
||||||
private SelectorLoop loop;
|
|
||||||
|
|
||||||
private SocketException exception;
|
|
||||||
|
|
||||||
package ReadBuffer!ubyte output;
|
|
||||||
|
|
||||||
package WriteBuffer!ubyte input;
|
|
||||||
|
|
||||||
private Protocol protocol_;
|
|
||||||
|
|
||||||
private bool closing;
|
|
||||||
|
|
||||||
/// Received notification that the underlying socket is write-ready.
|
|
||||||
package bool writeReady;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* loop = Event loop.
|
|
||||||
* socket = Socket.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
|
|
||||||
*/
|
|
||||||
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(loop !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
super(socket);
|
|
||||||
this.loop = loop;
|
|
||||||
output = ReadBuffer!ubyte(8192, 1024);
|
|
||||||
input = WriteBuffer!ubyte(8192);
|
|
||||||
active = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*
|
|
||||||
* Postcondition: $(D_INLINECODE socket !is null)
|
|
||||||
*/
|
|
||||||
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
|
||||||
out (socket)
|
|
||||||
{
|
|
||||||
assert(socket !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
return cast(ConnectedSocket) socket_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private @property void socket(ConnectedSocket socket)
|
|
||||||
pure nothrow @safe @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(socket !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
socket_ = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol.
|
|
||||||
*/
|
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return protocol_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the protocol.
|
|
||||||
*
|
|
||||||
* The protocol is deallocated by the event loop.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocol = Application protocol.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
|
||||||
*/
|
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(protocol !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
protocol_ = protocol;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
|
||||||
*/
|
|
||||||
bool isClosing() const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return closing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the transport.
|
|
||||||
*
|
|
||||||
* Buffered data will be flushed. No more data will be received.
|
|
||||||
*/
|
|
||||||
void close() @nogc
|
|
||||||
{
|
|
||||||
closing = true;
|
|
||||||
loop.reify(this,
|
|
||||||
EventMask(Event.read | Event.write),
|
|
||||||
EventMask(Event.write));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the watcher callback.
|
|
||||||
*/
|
|
||||||
override void invoke() @nogc
|
|
||||||
{
|
|
||||||
if (output.length)
|
|
||||||
{
|
|
||||||
protocol.received(output[0 .. $]);
|
|
||||||
output.clear();
|
|
||||||
if (isClosing() && input.length == 0)
|
|
||||||
{
|
|
||||||
loop.kill(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
protocol.disconnected(exception);
|
|
||||||
defaultAllocator.dispose(protocol_);
|
|
||||||
defaultAllocator.dispose(exception);
|
|
||||||
active = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Write some data to the transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* data = Data to send.
|
|
||||||
*/
|
|
||||||
void write(ubyte[] data) @nogc
|
|
||||||
{
|
|
||||||
if (!data.length)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Try to write if the socket is write ready.
|
|
||||||
if (writeReady)
|
|
||||||
{
|
|
||||||
ptrdiff_t sent;
|
|
||||||
SocketException exception;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
sent = socket.send(data);
|
|
||||||
if (sent == 0)
|
|
||||||
{
|
|
||||||
writeReady = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
writeReady = false;
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
if (sent < data.length)
|
|
||||||
{
|
|
||||||
input ~= data[sent..$];
|
|
||||||
loop.feed(this, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
input ~= data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class SelectorLoop : Loop
|
|
||||||
{
|
|
||||||
/// Pending connections.
|
|
||||||
protected Array!SocketWatcher connections;
|
|
||||||
|
|
||||||
this() @nogc
|
|
||||||
{
|
|
||||||
super();
|
|
||||||
this.connections = Array!SocketWatcher(maxEvents);
|
|
||||||
}
|
|
||||||
|
|
||||||
~this() @nogc
|
|
||||||
{
|
|
||||||
foreach (ref connection; this.connections[])
|
|
||||||
{
|
|
||||||
// We want to free only the transports. ConnectionWatcher are
|
|
||||||
// created by the user and should be freed by himself.
|
|
||||||
if (cast(StreamTransport) connection !is null)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
override abstract protected bool reify(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kills the watcher and closes the connection.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* transport = Transport.
|
|
||||||
* exception = Occurred exception.
|
|
||||||
*/
|
|
||||||
protected void kill(StreamTransport transport,
|
|
||||||
SocketException exception = null) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(transport !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
transport.socket.shutdown();
|
|
||||||
defaultAllocator.dispose(transport.socket);
|
|
||||||
transport.exception = exception;
|
|
||||||
pendings.insertBack(transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the transport couldn't send the data, the further sending should
|
|
||||||
* be handled by the event loop.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* transport = Transport.
|
|
||||||
* exception = Exception thrown on sending.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
|
||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
|
||||||
* transport will be destroyed then).
|
|
||||||
*/
|
|
||||||
protected bool feed(StreamTransport transport,
|
|
||||||
SocketException exception = null) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(transport !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
while (transport.input.length && transport.writeReady)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ptrdiff_t sent = transport.socket.send(transport.input[]);
|
|
||||||
if (sent == 0)
|
|
||||||
{
|
|
||||||
transport.writeReady = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport.input += sent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
exception = e;
|
|
||||||
transport.writeReady = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (exception !is null)
|
|
||||||
{
|
|
||||||
kill(transport, exception);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (transport.input.length == 0 && transport.isClosing())
|
|
||||||
{
|
|
||||||
kill(transport);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start watching.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* watcher = Watcher.
|
|
||||||
*/
|
|
||||||
override void start(ConnectionWatcher watcher) @nogc
|
|
||||||
{
|
|
||||||
if (watcher.active)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connections.length <= watcher.socket)
|
|
||||||
{
|
|
||||||
connections.length = watcher.socket.handle + maxEvents / 2;
|
|
||||||
}
|
|
||||||
connections[watcher.socket.handle] = watcher;
|
|
||||||
|
|
||||||
super.start(watcher);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept incoming connections.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* connection = Connection watcher ready to accept.
|
|
||||||
*/
|
|
||||||
package void acceptConnections(ConnectionWatcher connection) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(connection !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
ConnectedSocket client;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client = (cast(StreamSocket) connection.socket).accept();
|
|
||||||
}
|
|
||||||
catch (SocketException e)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (client is null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamTransport transport;
|
|
||||||
|
|
||||||
if (connections.length > client.handle)
|
|
||||||
{
|
|
||||||
transport = cast(StreamTransport) connections[client.handle];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
connections.length = client.handle + maxEvents / 2;
|
|
||||||
}
|
|
||||||
if (transport is null)
|
|
||||||
{
|
|
||||||
transport = defaultAllocator.make!StreamTransport(this, client);
|
|
||||||
connections[client.handle] = transport;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
transport.socket = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
reify(transport,
|
|
||||||
EventMask(Event.none),
|
|
||||||
EventMask(Event.read | Event.write));
|
|
||||||
connection.incoming.insertBack(transport);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection.incoming.empty)
|
|
||||||
{
|
|
||||||
pendings.insertBack(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,56 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This module provides API for Windows I/O Completion Ports.
|
|
||||||
*
|
|
||||||
* Note: Available only on Windows.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/iocp.d,
|
|
||||||
* tanya/async/iocp.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.iocp;
|
|
||||||
|
|
||||||
version (Windows)
|
|
||||||
{
|
|
||||||
version = WindowsDoc;
|
|
||||||
}
|
|
||||||
else version (D_Ddoc)
|
|
||||||
{
|
|
||||||
version = WindowsDoc;
|
|
||||||
version (Windows)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
private struct OVERLAPPED
|
|
||||||
{
|
|
||||||
}
|
|
||||||
private alias HANDLE = void*;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
version (WindowsDoc):
|
|
||||||
|
|
||||||
import core.sys.windows.winbase;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides an extendable representation of a Win32 $(D_PSYMBOL OVERLAPPED)
|
|
||||||
* structure.
|
|
||||||
*/
|
|
||||||
class State
|
|
||||||
{
|
|
||||||
/// For internal use by Windows API.
|
|
||||||
align(1) OVERLAPPED overlapped;
|
|
||||||
|
|
||||||
/// File/socket handle.
|
|
||||||
HANDLE handle;
|
|
||||||
|
|
||||||
/// For keeping events or event masks.
|
|
||||||
int event;
|
|
||||||
}
|
|
@ -1,352 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for the event loop implementations and the default event loop
|
|
||||||
* chooser.
|
|
||||||
*
|
|
||||||
* ---
|
|
||||||
* import tanya.async;
|
|
||||||
* import tanya.memory;
|
|
||||||
* import tanya.network.socket;
|
|
||||||
*
|
|
||||||
* class EchoProtocol : TransmissionControlProtocol
|
|
||||||
* {
|
|
||||||
* private DuplexTransport transport;
|
|
||||||
*
|
|
||||||
* void received(in ubyte[] data) @nogc
|
|
||||||
* {
|
|
||||||
* ubyte[512] buffer;
|
|
||||||
* buffer[0 .. data.length] = data;
|
|
||||||
* transport.write(buffer[]);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void connected(DuplexTransport transport) @nogc
|
|
||||||
* {
|
|
||||||
* this.transport = transport;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void disconnected(SocketException e) @nogc
|
|
||||||
* {
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void main()
|
|
||||||
* {
|
|
||||||
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
|
|
||||||
*
|
|
||||||
* version (Windows)
|
|
||||||
* {
|
|
||||||
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
|
|
||||||
* }
|
|
||||||
* else
|
|
||||||
* {
|
|
||||||
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
|
|
||||||
* sock.blocking = false;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* sock.bind(address);
|
|
||||||
* sock.listen(5);
|
|
||||||
*
|
|
||||||
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
|
||||||
* io.setProtocol!EchoProtocol;
|
|
||||||
*
|
|
||||||
* defaultLoop.start(io);
|
|
||||||
* defaultLoop.run();
|
|
||||||
*
|
|
||||||
* sock.shutdown();
|
|
||||||
* defaultAllocator.dispose(io);
|
|
||||||
* defaultAllocator.dispose(sock);
|
|
||||||
* defaultAllocator.dispose(address);
|
|
||||||
* }
|
|
||||||
* ---
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/loop.d,
|
|
||||||
* tanya/async/loop.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.loop;
|
|
||||||
|
|
||||||
import core.time;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.bitmanip;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.container.list;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
version (DisableBackends)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (D_Ddoc)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
else version (linux)
|
|
||||||
{
|
|
||||||
import tanya.async.event.epoll;
|
|
||||||
version = Epoll;
|
|
||||||
}
|
|
||||||
else version (Windows)
|
|
||||||
{
|
|
||||||
import tanya.async.event.iocp;
|
|
||||||
version = IOCP;
|
|
||||||
}
|
|
||||||
else version (OSX)
|
|
||||||
{
|
|
||||||
version = Kqueue;
|
|
||||||
}
|
|
||||||
else version (iOS)
|
|
||||||
{
|
|
||||||
version = Kqueue;
|
|
||||||
}
|
|
||||||
else version (FreeBSD)
|
|
||||||
{
|
|
||||||
version = Kqueue;
|
|
||||||
}
|
|
||||||
else version (OpenBSD)
|
|
||||||
{
|
|
||||||
version = Kqueue;
|
|
||||||
}
|
|
||||||
else version (DragonFlyBSD)
|
|
||||||
{
|
|
||||||
version = Kqueue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events.
|
|
||||||
*/
|
|
||||||
enum Event : uint
|
|
||||||
{
|
|
||||||
none = 0x00, /// No events.
|
|
||||||
read = 0x01, /// Non-blocking read call.
|
|
||||||
write = 0x02, /// Non-blocking write call.
|
|
||||||
accept = 0x04, /// Connection made.
|
|
||||||
error = 0x80000000, /// Sent when an error occurs.
|
|
||||||
}
|
|
||||||
|
|
||||||
alias EventMask = BitFlags!Event;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event loop.
|
|
||||||
*/
|
|
||||||
abstract class Loop
|
|
||||||
{
|
|
||||||
protected bool done = true;
|
|
||||||
|
|
||||||
/// Pending watchers.
|
|
||||||
protected DList!Watcher pendings;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Maximal event count can be got at a time
|
|
||||||
* (should be supported by the backend).
|
|
||||||
*/
|
|
||||||
protected @property uint maxEvents()
|
|
||||||
const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return 128U;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the loop.
|
|
||||||
*/
|
|
||||||
this() @nogc
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Frees loop internals.
|
|
||||||
*/
|
|
||||||
~this() @nogc
|
|
||||||
{
|
|
||||||
for (; !this.pendings.empty; this.pendings.removeFront())
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(this.pendings.front);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the loop.
|
|
||||||
*/
|
|
||||||
void run() @nogc
|
|
||||||
{
|
|
||||||
this.done = false;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
poll();
|
|
||||||
|
|
||||||
// Invoke pendings
|
|
||||||
for (; !this.pendings.empty; this.pendings.removeFront())
|
|
||||||
{
|
|
||||||
this.pendings.front.invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!this.done);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Break out of the loop.
|
|
||||||
*/
|
|
||||||
void unloop() @safe pure nothrow @nogc
|
|
||||||
{
|
|
||||||
this.done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start watching.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* watcher = Watcher.
|
|
||||||
*/
|
|
||||||
void start(ConnectionWatcher watcher) @nogc
|
|
||||||
{
|
|
||||||
if (watcher.active)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
watcher.active = true;
|
|
||||||
|
|
||||||
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: The blocking time.
|
|
||||||
*/
|
|
||||||
protected @property inout(Duration) blockTime()
|
|
||||||
inout @safe pure nothrow @nogc
|
|
||||||
{
|
|
||||||
// Don't block if we have to do.
|
|
||||||
return pendings.empty ? blockTime_ : Duration.zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the blocking time for IO watchers.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* blockTime = The blocking time. Cannot be larger than
|
|
||||||
* $(D_PSYMBOL maxBlockTime).
|
|
||||||
*/
|
|
||||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
|
||||||
assert(!blockTime.isNegative);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
blockTime_ = blockTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Does the actual polling.
|
|
||||||
*/
|
|
||||||
abstract protected void poll() @nogc;
|
|
||||||
|
|
||||||
/// Maximal block time.
|
|
||||||
protected Duration blockTime_ = 1.dur!"minutes";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception thrown on errors in the event loop.
|
|
||||||
*/
|
|
||||||
class BadLoopException : Exception
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* file = The file where the exception occurred.
|
|
||||||
* line = The line number where the exception occurred.
|
|
||||||
* next = The previous exception in the chain of exceptions, if any.
|
|
||||||
*/
|
|
||||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
|
||||||
pure nothrow const @safe @nogc
|
|
||||||
{
|
|
||||||
super("Event loop cannot be initialized.", file, line, next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the event loop used by default. If an event loop wasn't set with
|
|
||||||
* $(D_PSYMBOL defaultLoop) before, $(D_PSYMBOL defaultLoop) will try to
|
|
||||||
* choose an event loop supported on the system.
|
|
||||||
*
|
|
||||||
* Returns: The default event loop.
|
|
||||||
*/
|
|
||||||
@property Loop defaultLoop() @nogc
|
|
||||||
{
|
|
||||||
if (defaultLoop_ !is null)
|
|
||||||
{
|
|
||||||
return defaultLoop_;
|
|
||||||
}
|
|
||||||
version (Epoll)
|
|
||||||
{
|
|
||||||
defaultLoop_ = defaultAllocator.make!EpollLoop;
|
|
||||||
}
|
|
||||||
else version (IOCP)
|
|
||||||
{
|
|
||||||
defaultLoop_ = defaultAllocator.make!IOCPLoop;
|
|
||||||
}
|
|
||||||
else version (Kqueue)
|
|
||||||
{
|
|
||||||
import tanya.async.event.kqueue;
|
|
||||||
defaultLoop_ = defaultAllocator.make!KqueueLoop;
|
|
||||||
}
|
|
||||||
return defaultLoop_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default event loop.
|
|
||||||
*
|
|
||||||
* This property makes it possible to implement your own backends or event
|
|
||||||
* loops, for example, if the system is not supported or if you want to
|
|
||||||
* extend the supported implementation. Just extend $(D_PSYMBOL Loop) and pass
|
|
||||||
* your implementation to this property.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* loop = The event loop.
|
|
||||||
*/
|
|
||||||
@property void defaultLoop(Loop loop) @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(loop !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
defaultLoop_ = loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Loop defaultLoop_;
|
|
@ -1,20 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This package provides asynchronous capabilities.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/package.d,
|
|
||||||
* tanya/async/package.d)
|
|
||||||
*/
|
|
||||||
module tanya.async;
|
|
||||||
|
|
||||||
public import tanya.async.loop;
|
|
||||||
public import tanya.async.protocol;
|
|
||||||
public import tanya.async.transport;
|
|
||||||
public import tanya.async.watcher;
|
|
@ -1,58 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This module contains protocol which handle data in asynchronous
|
|
||||||
* applications.
|
|
||||||
*
|
|
||||||
* When an event from the network arrives, a protocol method gets
|
|
||||||
* called and can respond to the event.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/protocol.d,
|
|
||||||
* tanya/async/protocol.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.protocol;
|
|
||||||
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common protocol interface.
|
|
||||||
*/
|
|
||||||
interface Protocol
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* data = Read data.
|
|
||||||
*/
|
|
||||||
void received(in ubyte[] data) @nogc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a connection is made.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* transport = Protocol transport.
|
|
||||||
*/
|
|
||||||
void connected(DuplexTransport transport) @nogc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a connection is lost.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* exception = $(D_PSYMBOL Exception) if an error caused
|
|
||||||
* the disconnect, $(D_KEYWORD null) otherwise.
|
|
||||||
*/
|
|
||||||
void disconnected(SocketException exception) @nogc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for TCP.
|
|
||||||
*/
|
|
||||||
interface TransmissionControlProtocol : Protocol
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,104 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This module contains transports which are responsible for data dilvery
|
|
||||||
* between two parties of an asynchronous communication.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/transport.d,
|
|
||||||
* tanya/async/transport.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.transport;
|
|
||||||
|
|
||||||
import tanya.async.protocol;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base transport interface.
|
|
||||||
*/
|
|
||||||
interface Transport
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for read-only transports.
|
|
||||||
*/
|
|
||||||
interface ReadTransport : Transport
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for write-only transports.
|
|
||||||
*/
|
|
||||||
interface WriteTransport : Transport
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Write some data to the transport.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* data = Data to send.
|
|
||||||
*/
|
|
||||||
void write(ubyte[] data) @nogc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a bidirectional transport.
|
|
||||||
*/
|
|
||||||
interface DuplexTransport : ReadTransport, WriteTransport
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns: Application protocol.
|
|
||||||
*
|
|
||||||
* Postcondition: $(D_INLINECODE protocol !is null)
|
|
||||||
*/
|
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
|
||||||
out (protocol)
|
|
||||||
{
|
|
||||||
assert(protocol !is null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Switches the protocol.
|
|
||||||
*
|
|
||||||
* The protocol is deallocated by the event loop.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* protocol = Application protocol.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
|
||||||
*/
|
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(protocol !is null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
|
||||||
*/
|
|
||||||
bool isClosing() const pure nothrow @safe @nogc;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the transport.
|
|
||||||
*
|
|
||||||
* Buffered data will be flushed. No more data will be received.
|
|
||||||
*/
|
|
||||||
void close() @nogc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a socket transport.
|
|
||||||
*/
|
|
||||||
interface SocketTransport : Transport
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*/
|
|
||||||
@property Socket socket() pure nothrow @safe @nogc;
|
|
||||||
}
|
|
@ -1,117 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Watchers register user's interest in some event.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/async/watcher.d,
|
|
||||||
* tanya/async/watcher.d)
|
|
||||||
*/
|
|
||||||
module tanya.async.watcher;
|
|
||||||
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.protocol;
|
|
||||||
import tanya.async.transport;
|
|
||||||
import tanya.container.buffer;
|
|
||||||
import tanya.container.list;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
import tanya.network.socket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A watcher is an opaque structure that you allocate and register to record
|
|
||||||
* your interest in some event.
|
|
||||||
*/
|
|
||||||
abstract class Watcher
|
|
||||||
{
|
|
||||||
/// Whether the watcher is active.
|
|
||||||
bool active;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke some action on event.
|
|
||||||
*/
|
|
||||||
void invoke() @nogc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket watcher.
|
|
||||||
*/
|
|
||||||
abstract class SocketWatcher : Watcher
|
|
||||||
{
|
|
||||||
/// Watched socket.
|
|
||||||
protected Socket socket_;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* socket = Socket.
|
|
||||||
*
|
|
||||||
* Precondition: $(D_INLINECODE socket !is null)
|
|
||||||
*/
|
|
||||||
this(Socket socket) pure nothrow @safe @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(socket !is null);
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
socket_ = socket;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns: Socket.
|
|
||||||
*/
|
|
||||||
@property Socket socket() pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return socket_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Connection watcher.
|
|
||||||
*/
|
|
||||||
class ConnectionWatcher : SocketWatcher
|
|
||||||
{
|
|
||||||
/// Incoming connection queue.
|
|
||||||
DList!DuplexTransport incoming;
|
|
||||||
|
|
||||||
private Protocol delegate() @nogc protocolFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* socket = Socket.
|
|
||||||
*/
|
|
||||||
this(Socket socket) @nogc
|
|
||||||
{
|
|
||||||
super(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Params:
|
|
||||||
* P = Protocol should be used.
|
|
||||||
*/
|
|
||||||
void setProtocol(P : Protocol)() @nogc
|
|
||||||
{
|
|
||||||
this.protocolFactory = () @nogc => cast(Protocol) defaultAllocator.make!P;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes new connection callback.
|
|
||||||
*/
|
|
||||||
override void invoke() @nogc
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
for (; !this.incoming.empty; this.incoming.removeFront())
|
|
||||||
{
|
|
||||||
this.incoming.front.protocol = protocolFactory();
|
|
||||||
this.incoming.front.protocol.connected(this.incoming.front);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,318 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bit manipulation.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2018-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/bitmanip.d,
|
|
||||||
* tanya/bitmanip.d)
|
|
||||||
*/
|
|
||||||
module tanya.bitmanip;
|
|
||||||
|
|
||||||
import tanya.meta.metafunction;
|
|
||||||
import tanya.meta.trait;
|
|
||||||
import tanya.meta.transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether $(D_PARAM E) is a $(D_KEYWORD enum), whose members can be
|
|
||||||
* used as bit flags.
|
|
||||||
*
|
|
||||||
* This is the case if all members of $(D_PARAM E) are integral numbers that
|
|
||||||
* are either 0 or positive integral powers of 2.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* E = Some $(D_KEYWORD enum).
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if $(D_PARAM E) contains only bit flags,
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
template isBitFlagEnum(E)
|
|
||||||
{
|
|
||||||
enum bool isValid(OriginalType!E x) = x == 0
|
|
||||||
|| (x > 0 && ((x & (x - 1)) == 0));
|
|
||||||
static if (isIntegral!E)
|
|
||||||
{
|
|
||||||
enum bool isBitFlagEnum = allSatisfy!(isValid, EnumMembers!E);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
enum bool isBitFlagEnum = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
enum Valid
|
|
||||||
{
|
|
||||||
none = 0,
|
|
||||||
one = 1 << 0,
|
|
||||||
two = 1 << 1,
|
|
||||||
}
|
|
||||||
static assert(isBitFlagEnum!Valid);
|
|
||||||
|
|
||||||
enum Invalid
|
|
||||||
{
|
|
||||||
one,
|
|
||||||
two,
|
|
||||||
three,
|
|
||||||
four,
|
|
||||||
}
|
|
||||||
static assert(!isBitFlagEnum!Invalid);
|
|
||||||
|
|
||||||
enum Negative
|
|
||||||
{
|
|
||||||
one = -1,
|
|
||||||
two = -2,
|
|
||||||
}
|
|
||||||
static assert(!isBitFlagEnum!Negative);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates that $(D_PARAM field) contains only bits from $(D_PARAM E).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* E = Some $(D_KEYWORD enum).
|
|
||||||
* field = Bit field.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if $(D_PARAM field) is valid, $(D_KEYWORD false)
|
|
||||||
* otherwise.
|
|
||||||
*/
|
|
||||||
bool containsBitFlags(E)(E field)
|
|
||||||
if (isBitFlagEnum!E)
|
|
||||||
{
|
|
||||||
OriginalType!E fillField()
|
|
||||||
{
|
|
||||||
typeof(return) full;
|
|
||||||
static foreach (member; EnumMembers!E)
|
|
||||||
{
|
|
||||||
full |= member;
|
|
||||||
}
|
|
||||||
return full;
|
|
||||||
}
|
|
||||||
enum OriginalType!E full = fillField();
|
|
||||||
return (field & ~full) == OriginalType!E.init;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
enum E
|
|
||||||
{
|
|
||||||
one,
|
|
||||||
two,
|
|
||||||
three,
|
|
||||||
}
|
|
||||||
assert(containsBitFlags(E.one | E.two));
|
|
||||||
assert(!containsBitFlags(cast(E) 0x8));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows to use $(D_KEYWORD enum) values as a set of bit flags.
|
|
||||||
*
|
|
||||||
* $(D_PSYMBOL BitFlags) behaves the same as a bit field of type $(D_PARAM E),
|
|
||||||
* but does additional cheks to ensure that the bit field contains only valid
|
|
||||||
* values, this is only values from $(D_PARAM E).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* E = Some $(D_KEYWORD enum).
|
|
||||||
*/
|
|
||||||
struct BitFlags(E)
|
|
||||||
if (isBitFlagEnum!E)
|
|
||||||
{
|
|
||||||
private OriginalType!E field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs $(D_PSYMBOL BitFlags) from $(D_PARAM field).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* field = Bits to be set.
|
|
||||||
*/
|
|
||||||
this(E field)
|
|
||||||
{
|
|
||||||
this.field = field;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts $(D_PSYMBOL BitFlags) to a boolean.
|
|
||||||
*
|
|
||||||
* It is $(D_KEYWORD true) if any bit is set, $(D_KEYWORD false) otherwise.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL BitFlags) contains any
|
|
||||||
* set bits, $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
bool opCast(T : bool)()
|
|
||||||
{
|
|
||||||
return this.field != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts to the original type of $(D_PARAM E) ($(D_KEYWORD int) by
|
|
||||||
* default).
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this) as $(D_INLINECODE OriginalType!T).
|
|
||||||
*/
|
|
||||||
OriginalType!E opCast(T : OriginalType!E)() const
|
|
||||||
{
|
|
||||||
return this.field;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests (&), sets (|) or toggles (^) bits.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* op = Operation.
|
|
||||||
* that = 0 or more bit flags.
|
|
||||||
*
|
|
||||||
* Returns: New $(D_PSYMBOL BitFlags) object.
|
|
||||||
*/
|
|
||||||
BitFlags opBinary(string op)(E that) const
|
|
||||||
if (op == "&" || op == "|" || op == "^")
|
|
||||||
{
|
|
||||||
BitFlags result = this;
|
|
||||||
mixin("return result " ~ op ~ "= that;");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
BitFlags opBinary(string op)(BitFlags that) const
|
|
||||||
if (op == "&" || op == "|" || op == "^")
|
|
||||||
{
|
|
||||||
BitFlags result = this;
|
|
||||||
mixin("return result " ~ op ~ "= that;");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
BitFlags opBinaryRight(string op)(E that) const
|
|
||||||
if (op == "&" || op == "|" || op == "^")
|
|
||||||
{
|
|
||||||
BitFlags result = this;
|
|
||||||
mixin("return result " ~ op ~ "= that;");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests (&), sets (|) or toggles (^) bits.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* op = Operation.
|
|
||||||
* that = 0 or more bit flags.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
ref BitFlags opOpAssign(string op)(E that)
|
|
||||||
if (op == "&" || op == "|" || op == "^")
|
|
||||||
{
|
|
||||||
mixin("this.field " ~ op ~ "= that;");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
ref BitFlags opOpAssign(string op)(BitFlags that)
|
|
||||||
if (op == "&" || op == "|" || op == "^")
|
|
||||||
{
|
|
||||||
mixin("this.field " ~ op ~ "= that.field;");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inverts all bit flags.
|
|
||||||
*
|
|
||||||
* Returns: New $(D_PSYMBOL BitFlags) object with all bits inverted.
|
|
||||||
*/
|
|
||||||
BitFlags opUnary(string op : "~")() const
|
|
||||||
{
|
|
||||||
BitFlags result;
|
|
||||||
result.field = ~this.field;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assigns a bit field.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* that = Bit field of type $(D_PARAM E).
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
ref BitFlags opAssign(E that)
|
|
||||||
{
|
|
||||||
this.field = that;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares this $(D_PSYMBOL BitFlags) object to another bit field.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* that = $(D_PSYMBOL BitFlags) object or a bit field of type
|
|
||||||
* $(D_PARAM E).
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if $(D_KEYWORD this) and $(D_PARAM that)
|
|
||||||
* contain the same bits ,$(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
bool opEquals(E that) const
|
|
||||||
{
|
|
||||||
return this.field == that;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
bool opEquals(BitFlags that) const
|
|
||||||
{
|
|
||||||
return this.field == that.field;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a hash value of this object.
|
|
||||||
*
|
|
||||||
* Returns: Hash value.
|
|
||||||
*/
|
|
||||||
size_t toHash() const
|
|
||||||
{
|
|
||||||
return cast(size_t) this.field;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a $(D_PSYMBOL BitFlags) object initialized with $(D_PARAM field).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* E = Some $(D_KEYWORD enum).
|
|
||||||
* field = Bits to be set.
|
|
||||||
*/
|
|
||||||
BitFlags!E bitFlags(E)(E field)
|
|
||||||
if (isBitFlagEnum!E)
|
|
||||||
{
|
|
||||||
return BitFlags!E(field);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
enum E
|
|
||||||
{
|
|
||||||
one = 1 << 0,
|
|
||||||
two = 1 << 1,
|
|
||||||
three = 1 << 2,
|
|
||||||
}
|
|
||||||
// Construct with E.one and E.two set
|
|
||||||
auto flags = bitFlags(E.one | E.two);
|
|
||||||
|
|
||||||
// Test wheter E.one is set
|
|
||||||
assert(flags & E.one);
|
|
||||||
|
|
||||||
// Toggle E.one
|
|
||||||
flags ^= E.one;
|
|
||||||
assert(!(flags & E.one));
|
|
||||||
|
|
||||||
// Set E.three
|
|
||||||
flags |= E.three;
|
|
||||||
assert(flags & E.three);
|
|
||||||
|
|
||||||
// Clear E.three
|
|
||||||
flags &= ~E.three;
|
|
||||||
assert(!(flags & E.three));
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ module tanya.container.array;
|
|||||||
import core.checkedint;
|
import core.checkedint;
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
import std.algorithm.iteration;
|
import std.algorithm.iteration;
|
||||||
|
import std.algorithm.mutation : bringToFront;
|
||||||
import tanya.algorithm.mutation;
|
import tanya.algorithm.mutation;
|
||||||
import tanya.memory.allocator;
|
import tanya.memory.allocator;
|
||||||
import tanya.memory.lifetime;
|
import tanya.memory.lifetime;
|
||||||
@ -815,7 +816,7 @@ struct Array(T)
|
|||||||
const after = r.end - this.data;
|
const after = r.end - this.data;
|
||||||
const inserted = insertBack(el);
|
const inserted = insertBack(el);
|
||||||
|
|
||||||
rotate(this.data[after .. oldLength], this.data[oldLength .. length]);
|
bringToFront(this.data[after .. oldLength], this.data[oldLength .. length]);
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -854,7 +855,7 @@ struct Array(T)
|
|||||||
{
|
{
|
||||||
moveBack(el);
|
moveBack(el);
|
||||||
}
|
}
|
||||||
rotate(this.data[offset .. oldLen], this.data[oldLen .. length]);
|
bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -910,7 +911,7 @@ struct Array(T)
|
|||||||
{
|
{
|
||||||
moveBack(el);
|
moveBack(el);
|
||||||
}
|
}
|
||||||
rotate(this.data[offset .. oldLen], this.data[oldLen .. length]);
|
bringToFront(this.data[offset .. oldLen], this.data[oldLen .. length]);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
/*
|
/*
|
||||||
* Internal package used by containers that rely on entries/nodes.
|
* Internal package used by containers that rely on entries/nodes.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
* Copyright: Eugene Wissner 2016-2022.
|
||||||
* 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:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
@ -19,7 +19,6 @@ import tanya.memory.allocator;
|
|||||||
import tanya.memory.lifetime;
|
import tanya.memory.lifetime;
|
||||||
import tanya.meta.trait;
|
import tanya.meta.trait;
|
||||||
import tanya.meta.transform;
|
import tanya.meta.transform;
|
||||||
import tanya.typecons;
|
|
||||||
|
|
||||||
package struct SEntry(T)
|
package struct SEntry(T)
|
||||||
{
|
{
|
||||||
@ -54,7 +53,11 @@ package struct Bucket(K, V = void)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
alias KV = Tuple!(K, "key", V, "value");
|
package struct KV
|
||||||
|
{
|
||||||
|
package K key;
|
||||||
|
package V value;
|
||||||
|
}
|
||||||
KV kv;
|
KV kv;
|
||||||
}
|
}
|
||||||
BucketStatus status = BucketStatus.empty;
|
BucketStatus status = BucketStatus.empty;
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
module tanya.container.string;
|
module tanya.container.string;
|
||||||
|
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
|
import std.algorithm.mutation : bringToFront;
|
||||||
import tanya.algorithm.mutation;
|
import tanya.algorithm.mutation;
|
||||||
import tanya.hash.lookup;
|
import tanya.hash.lookup;
|
||||||
import tanya.memory.allocator;
|
import tanya.memory.allocator;
|
||||||
@ -1488,7 +1489,7 @@ struct String
|
|||||||
const after = r.end - this.data;
|
const after = r.end - this.data;
|
||||||
const inserted = insertBack(el);
|
const inserted = insertBack(el);
|
||||||
|
|
||||||
rotate(this.data[after .. oldLength], this.data[oldLength .. length]);
|
bringToFront(this.data[after .. oldLength], this.data[oldLength .. length]);
|
||||||
return inserted;
|
return inserted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
*
|
*
|
||||||
* More advanced formatting is currently not implemented.
|
* More advanced formatting is currently not implemented.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2017-2020.
|
* Copyright: Eugene Wissner 2017-2022.
|
||||||
* 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:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
@ -56,7 +56,6 @@ import tanya.meta.metafunction;
|
|||||||
import tanya.meta.trait;
|
import tanya.meta.trait;
|
||||||
import tanya.meta.transform;
|
import tanya.meta.transform;
|
||||||
import tanya.range;
|
import tanya.range;
|
||||||
import tanya.typecons : Tuple;
|
|
||||||
|
|
||||||
// Returns the last part of buffer with converted number.
|
// Returns the last part of buffer with converted number.
|
||||||
package(tanya) char[] integral2String(T)(T number, return ref char[21] buffer)
|
package(tanya) char[] integral2String(T)(T number, return ref char[21] buffer)
|
||||||
@ -940,6 +939,12 @@ private struct uint128
|
|||||||
{
|
{
|
||||||
ulong[2] data;
|
ulong[2] data;
|
||||||
|
|
||||||
|
private struct DivMod
|
||||||
|
{
|
||||||
|
uint128 quotient;
|
||||||
|
uint128 remainder;
|
||||||
|
}
|
||||||
|
|
||||||
this(ulong upper, ulong lower) @nogc nothrow pure @safe
|
this(ulong upper, ulong lower) @nogc nothrow pure @safe
|
||||||
{
|
{
|
||||||
this.data[0] = upper;
|
this.data[0] = upper;
|
||||||
@ -1174,7 +1179,7 @@ private struct uint128
|
|||||||
return this.data[1];
|
return this.data[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
Tuple!(uint128, uint128) divMod(ulong rhs) const @nogc nothrow pure @safe
|
DivMod divMod(ulong rhs) const @nogc nothrow pure @safe
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(rhs != uint128(), "Division by 0");
|
assert(rhs != uint128(), "Division by 0");
|
||||||
@ -1197,22 +1202,22 @@ private struct uint128
|
|||||||
typeof(return) result;
|
typeof(return) result;
|
||||||
for (ubyte x = this.bits; x > 0; --x)
|
for (ubyte x = this.bits; x > 0; --x)
|
||||||
{
|
{
|
||||||
result[0] = result[0] << 1;
|
result.quotient = result.quotient << 1;
|
||||||
result[1] = result[1] << 1;
|
result.remainder = result.remainder << 1;
|
||||||
|
|
||||||
if ((this >> (x - 1U)) & 1)
|
if ((this >> (x - 1U)) & 1)
|
||||||
{
|
{
|
||||||
++result[1];
|
++result.remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result[1] >= rhs)
|
if (result.remainder >= rhs)
|
||||||
{
|
{
|
||||||
if (result[1].data[1] < rhs)
|
if (result.remainder.data[1] < rhs)
|
||||||
{
|
{
|
||||||
--result[1].data[0];
|
--result.remainder.data[0];
|
||||||
}
|
}
|
||||||
result[1].data[1] -= rhs;
|
result.remainder.data[1] -= rhs;
|
||||||
++result[0];
|
++result.quotient;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -1220,12 +1225,12 @@ private struct uint128
|
|||||||
|
|
||||||
uint128 opBinary(string op : "/")(ulong rhs)
|
uint128 opBinary(string op : "/")(ulong rhs)
|
||||||
{
|
{
|
||||||
return divMod(rhs)[0];
|
return divMod(rhs).quotient;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint128 opBinary(string op : "%")(ulong rhs) const
|
uint128 opBinary(string op : "%")(ulong rhs) const
|
||||||
{
|
{
|
||||||
return divMod(rhs)[1];
|
return divMod(rhs).remainder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1302,12 +1307,12 @@ do
|
|||||||
enum ulong power19 = cast(ulong) 1e19;
|
enum ulong power19 = cast(ulong) 1e19;
|
||||||
|
|
||||||
auto qr = leftBoundary.divMod(power19);
|
auto qr = leftBoundary.divMod(power19);
|
||||||
auto low = cast(ulong) qr[1];
|
auto low = cast(ulong) qr.remainder;
|
||||||
const lowFactor = cast(ulong) (qr[0] % power19);
|
const lowFactor = cast(ulong) (qr.quotient % power19);
|
||||||
|
|
||||||
qr = rightBoundary.divMod(power19);
|
qr = rightBoundary.divMod(power19);
|
||||||
auto high = cast(ulong) qr[1];
|
auto high = cast(ulong) qr.remainder;
|
||||||
const highFactor = cast(ulong) (qr[0] % power19);
|
const highFactor = cast(ulong) (qr.quotient % power19);
|
||||||
size_t digitIndex;
|
size_t digitIndex;
|
||||||
|
|
||||||
if (lowFactor != highFactor)
|
if (lowFactor != highFactor)
|
||||||
|
@ -1,111 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number theory.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2017-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/math/nbtheory.d,
|
|
||||||
* tanya/math/nbtheory.d)
|
|
||||||
*/
|
|
||||||
module tanya.math.nbtheory;
|
|
||||||
|
|
||||||
import tanya.meta.trait;
|
|
||||||
import tanya.meta.transform;
|
|
||||||
|
|
||||||
import core.math : fabs;
|
|
||||||
import std.math : log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the absolute value of a number.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Argument type.
|
|
||||||
* x = Argument.
|
|
||||||
*
|
|
||||||
* Returns: Absolute value of $(D_PARAM x).
|
|
||||||
*/
|
|
||||||
Unqual!T abs(T)(T x)
|
|
||||||
if (isIntegral!T)
|
|
||||||
{
|
|
||||||
static if (isSigned!T)
|
|
||||||
{
|
|
||||||
return x >= 0 ? x : -x;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
int i = -1;
|
|
||||||
assert(i.abs == 1);
|
|
||||||
static assert(is(typeof(i.abs) == int));
|
|
||||||
|
|
||||||
uint u = 1;
|
|
||||||
assert(u.abs == 1);
|
|
||||||
static assert(is(typeof(u.abs) == uint));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
Unqual!T abs(T)(T x)
|
|
||||||
if (isFloatingPoint!T)
|
|
||||||
{
|
|
||||||
return fabs(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
float f = -1.64;
|
|
||||||
assert(f.abs == 1.64F);
|
|
||||||
static assert(is(typeof(f.abs) == float));
|
|
||||||
|
|
||||||
double d = -1.64;
|
|
||||||
assert(d.abs == 1.64);
|
|
||||||
static assert(is(typeof(d.abs) == double));
|
|
||||||
|
|
||||||
real r = -1.64;
|
|
||||||
assert(r.abs == 1.64L);
|
|
||||||
static assert(is(typeof(r.abs) == real));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates natural logarithm of $(D_PARAM x).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Argument type.
|
|
||||||
* x = Argument.
|
|
||||||
*
|
|
||||||
* Returns: Natural logarithm of $(D_PARAM x).
|
|
||||||
*/
|
|
||||||
Unqual!T ln(T)(T x)
|
|
||||||
if (isFloatingPoint!T)
|
|
||||||
{
|
|
||||||
return log(x);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
import tanya.math;
|
|
||||||
|
|
||||||
assert(isNaN(ln(-7.389f)));
|
|
||||||
assert(isNaN(ln(-7.389)));
|
|
||||||
assert(isNaN(ln(-7.389L)));
|
|
||||||
|
|
||||||
assert(isInfinity(ln(0.0f)));
|
|
||||||
assert(isInfinity(ln(0.0)));
|
|
||||||
assert(isInfinity(ln(0.0L)));
|
|
||||||
|
|
||||||
assert(ln(1.0f) == 0.0f);
|
|
||||||
assert(ln(1.0) == 0.0);
|
|
||||||
assert(ln(1.0L) == 0.0L);
|
|
||||||
}
|
|
@ -12,7 +12,7 @@
|
|||||||
* be found in its submodules. $(D_PSYMBOL tanya.math) doesn't import any
|
* be found in its submodules. $(D_PSYMBOL tanya.math) doesn't import any
|
||||||
* submodules publically, they should be imported explicitly.
|
* submodules publically, they should be imported explicitly.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
* Copyright: Eugene Wissner 2016-2022.
|
||||||
* 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:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
@ -21,7 +21,7 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.math;
|
module tanya.math;
|
||||||
|
|
||||||
import tanya.math.nbtheory;
|
import std.math;
|
||||||
import tanya.meta.trait;
|
import tanya.meta.trait;
|
||||||
import tanya.meta.transform;
|
import tanya.meta.transform;
|
||||||
|
|
||||||
@ -543,73 +543,3 @@ if (isFloatingPoint!F)
|
|||||||
assert(signBit(-1.0L));
|
assert(signBit(-1.0L));
|
||||||
assert(!signBit(1.0L));
|
assert(!signBit(1.0L));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
|
||||||
*
|
|
||||||
* 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");
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe 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);
|
|
||||||
}
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
/**
|
/**
|
||||||
* Random number generator.
|
* Random number generator.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
* Copyright: Eugene Wissner 2016-2022.
|
||||||
* 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:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
@ -16,7 +16,6 @@ module tanya.math.random;
|
|||||||
|
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
import tanya.memory.allocator;
|
import tanya.memory.allocator;
|
||||||
import tanya.typecons;
|
|
||||||
|
|
||||||
/// Maximum amount gathered from the entropy sources.
|
/// Maximum amount gathered from the entropy sources.
|
||||||
enum maxGather = 128;
|
enum maxGather = 128;
|
||||||
|
@ -142,3 +142,23 @@ String indexToName(uint index) @nogc nothrow @trusted
|
|||||||
return String(findNullTerminated(buffer));
|
return String(findNullTerminated(buffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
|
||||||
|
* the protocol family which will be used for communication.
|
||||||
|
*/
|
||||||
|
enum AddressFamily : int
|
||||||
|
{
|
||||||
|
unspec = 0, /// Unspecified.
|
||||||
|
local = 1, /// Local to host (pipes and file-domain).
|
||||||
|
unix = local, /// POSIX name for PF_LOCAL.
|
||||||
|
inet = 2, /// IP protocol family.
|
||||||
|
ax25 = 3, /// Amateur Radio AX.25.
|
||||||
|
ipx = 4, /// Novell Internet Protocol.
|
||||||
|
appletalk = 5, /// Appletalk DDP.
|
||||||
|
netrom = 6, /// Amateur radio NetROM.
|
||||||
|
bridge = 7, /// Multiprotocol bridge.
|
||||||
|
atmpvc = 8, /// ATM PVCs.
|
||||||
|
x25 = 9, /// Reserved for X.25 project.
|
||||||
|
inet6 = 10, /// IP version 6.
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ module tanya.net.ip;
|
|||||||
|
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
import std.ascii;
|
import std.ascii;
|
||||||
|
import std.sumtype;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
import tanya.algorithm.iteration;
|
import tanya.algorithm.iteration;
|
||||||
import tanya.algorithm.mutation;
|
import tanya.algorithm.mutation;
|
||||||
@ -28,7 +29,6 @@ import tanya.meta.transform;
|
|||||||
import tanya.net.iface;
|
import tanya.net.iface;
|
||||||
import tanya.net.inet;
|
import tanya.net.inet;
|
||||||
import tanya.range;
|
import tanya.range;
|
||||||
import tanya.typecons;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IPv4 internet address.
|
* IPv4 internet address.
|
||||||
@ -1061,7 +1061,7 @@ if (isInputRange!R && is(Unqual!(ElementType!R) == ubyte))
|
|||||||
*/
|
*/
|
||||||
struct Address
|
struct Address
|
||||||
{
|
{
|
||||||
private Variant!(Address4, Address6) address;
|
private SumType!(Address4, Address6) address;
|
||||||
|
|
||||||
@disable this();
|
@disable this();
|
||||||
|
|
||||||
@ -1095,7 +1095,10 @@ struct Address
|
|||||||
*/
|
*/
|
||||||
bool isV4() const @nogc nothrow pure @safe
|
bool isV4() const @nogc nothrow pure @safe
|
||||||
{
|
{
|
||||||
return this.address.peek!Address4;
|
return this.address.match!(
|
||||||
|
(Address4 address4) => true,
|
||||||
|
(Address6 address6) => false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1112,7 +1115,10 @@ struct Address
|
|||||||
*/
|
*/
|
||||||
bool isV6() const @nogc nothrow pure @safe
|
bool isV6() const @nogc nothrow pure @safe
|
||||||
{
|
{
|
||||||
return this.address.peek!Address6;
|
return this.address.match!(
|
||||||
|
(Address4 address4) => false,
|
||||||
|
(Address6 address6) => true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1131,14 +1137,12 @@ struct Address
|
|||||||
*
|
*
|
||||||
* Precondition: This is an IPv4 address.
|
* Precondition: This is an IPv4 address.
|
||||||
*/
|
*/
|
||||||
ref inout(Address4) toV4() inout @nogc nothrow pure @safe
|
Address4 toV4() inout @nogc nothrow pure @safe
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(this.address.peek!Address4);
|
return this.address.match!(
|
||||||
}
|
(Address4 address4) => address4,
|
||||||
do
|
_ => assert(false, "Not an IPv4 address")
|
||||||
{
|
);
|
||||||
return this.address.get!Address4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1158,14 +1162,12 @@ struct Address
|
|||||||
*
|
*
|
||||||
* Precondition: This is an IPv6 address.
|
* Precondition: This is an IPv6 address.
|
||||||
*/
|
*/
|
||||||
ref inout(Address6) toV6() inout @nogc nothrow pure @safe
|
Address6 toV6() inout @nogc nothrow pure @safe
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(this.address.peek!Address6);
|
return this.address.match!(
|
||||||
}
|
(Address6 address6) => address6,
|
||||||
do
|
_ => assert(false, "Not an IPv6 address")
|
||||||
{
|
);
|
||||||
return this.address.get!Address6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1185,17 +1187,11 @@ struct Address
|
|||||||
* $(D_PSYMBOL Address6.loopback).
|
* $(D_PSYMBOL Address6.loopback).
|
||||||
*/
|
*/
|
||||||
bool isLoopback() const @nogc nothrow pure @safe
|
bool isLoopback() const @nogc nothrow pure @safe
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(this.address.hasValue);
|
return this.address.match!(
|
||||||
}
|
(Address4 address) => address.isLoopback(),
|
||||||
do
|
(Address6 address) => address.isLoopback()
|
||||||
{
|
);
|
||||||
if (this.address.peek!Address4)
|
|
||||||
{
|
|
||||||
return this.address.get!Address4.isLoopback();
|
|
||||||
}
|
|
||||||
return this.address.get!Address6.isLoopback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1215,17 +1211,11 @@ struct Address
|
|||||||
* $(D_PSYMBOL Address6.isMulticast).
|
* $(D_PSYMBOL Address6.isMulticast).
|
||||||
*/
|
*/
|
||||||
bool isMulticast() const @nogc nothrow pure @safe
|
bool isMulticast() const @nogc nothrow pure @safe
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(this.address.hasValue);
|
return this.address.match!(
|
||||||
}
|
(Address4 address) => address.isMulticast(),
|
||||||
do
|
(Address6 address) => address.isMulticast()
|
||||||
{
|
);
|
||||||
if (this.address.peek!Address4)
|
|
||||||
{
|
|
||||||
return this.address.get!Address4.isMulticast();
|
|
||||||
}
|
|
||||||
return this.address.get!Address6.isMulticast();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1244,17 +1234,11 @@ struct Address
|
|||||||
* See_Also: $(D_PSYMBOL Address4.isAny), $(D_PSYMBOL Address6.isAny).
|
* See_Also: $(D_PSYMBOL Address4.isAny), $(D_PSYMBOL Address6.isAny).
|
||||||
*/
|
*/
|
||||||
bool isAny() const @nogc nothrow pure @safe
|
bool isAny() const @nogc nothrow pure @safe
|
||||||
in
|
|
||||||
{
|
{
|
||||||
assert(this.address.hasValue);
|
return this.address.match!(
|
||||||
}
|
(Address4 address) => address.isAny(),
|
||||||
do
|
(Address6 address) => address.isAny()
|
||||||
{
|
);
|
||||||
if (this.address.peek!Address4)
|
|
||||||
{
|
|
||||||
return this.address.get!Address4.isAny();
|
|
||||||
}
|
|
||||||
return this.address.get!Address6.isAny();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1277,14 +1261,22 @@ struct Address
|
|||||||
* otherwise.
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
bool opEquals(T)(T that) const
|
bool opEquals(T)(T that) const
|
||||||
if (is(Unqual!T == Address4) || is(Unqual!T == Address6))
|
if (is(Unqual!T == Address4))
|
||||||
{
|
{
|
||||||
alias AddressType = Unqual!T;
|
return this.address.match!(
|
||||||
if (this.address.peek!AddressType)
|
(Address4 address) => address == that,
|
||||||
{
|
(Address6 address) => false
|
||||||
return this.address.get!AddressType == that;
|
);
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
|
///
|
||||||
|
bool opEquals(T)(T that) const
|
||||||
|
if (is(Unqual!T == Address6))
|
||||||
|
{
|
||||||
|
return this.address.match!(
|
||||||
|
(Address4 address) => false,
|
||||||
|
(Address6 address) => address == that,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1324,3 +1316,98 @@ struct Address
|
|||||||
assert(address == Address4.loopback);
|
assert(address == Address4.loopback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service endpoint specified by a version independent IP address and port.
|
||||||
|
*/
|
||||||
|
struct Endpoint
|
||||||
|
{
|
||||||
|
private AddressFamily family = AddressFamily.unspec;
|
||||||
|
private ubyte[ushort.sizeof] service;
|
||||||
|
private Address4 address4; // Unused sin6_flowinfo if IPv6
|
||||||
|
private Address6 address6; // Unused if IPv4
|
||||||
|
|
||||||
|
static assert(Address4.sizeof == 4);
|
||||||
|
|
||||||
|
/// Allows the system to select a free port.
|
||||||
|
enum ushort anyPort = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an endpoint.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Address type (IPv4 or IPv6).
|
||||||
|
* address = IP address that should be associated with the endpoint.
|
||||||
|
* port = Port number in network byte order.
|
||||||
|
*/
|
||||||
|
this(T)(T address, const ushort port = anyPort)
|
||||||
|
if (is(T == Address) || is(T == Address4) || is(T == Address6))
|
||||||
|
{
|
||||||
|
this.address = address;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Port number in network byte order.
|
||||||
|
*/
|
||||||
|
@property inout(ushort) port() inout const @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
return this.service[].toHostOrder!ushort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* port = Port number in network byte order.
|
||||||
|
*/
|
||||||
|
@property void port(const ushort port) @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
NetworkOrder!(ushort.sizeof)(port).copy(this.service[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: IP address associated with the endpoint.
|
||||||
|
*/
|
||||||
|
@property inout(Address) address() inout @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
if (this.family == AddressFamily.inet)
|
||||||
|
{
|
||||||
|
return Address(this.address4);
|
||||||
|
}
|
||||||
|
else if (this.family == AddressFamily.inet6)
|
||||||
|
{
|
||||||
|
return Address(this.address6);
|
||||||
|
}
|
||||||
|
return Address.init;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* address = IP address associated with the endpoint.
|
||||||
|
*/
|
||||||
|
@property void address(Address address) @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
if (address.isV4())
|
||||||
|
{
|
||||||
|
this.address = address.toV4();
|
||||||
|
}
|
||||||
|
else if (address.isV6())
|
||||||
|
{
|
||||||
|
this.address = address.toV6();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
@property void address(Address4 address) @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
this.family = AddressFamily.inet;
|
||||||
|
this.address4 = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ditto
|
||||||
|
@property void address(Address6 address) @nogc nothrow pure @safe
|
||||||
|
{
|
||||||
|
this.family = AddressFamily.inet6;
|
||||||
|
this.address4 = Address4(0);
|
||||||
|
this.address6 = address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
/**
|
/**
|
||||||
* Network programming.
|
* Network programming.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2017-2020.
|
* Copyright: Eugene Wissner 2017-2022.
|
||||||
* 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:info@caraus.de, Eugene Wissner)
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
@ -1,17 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Network programming.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2016-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/network/package.d,
|
|
||||||
* tanya/network/package.d)
|
|
||||||
*/
|
|
||||||
module tanya.network;
|
|
||||||
|
|
||||||
public import tanya.network.socket;
|
|
File diff suppressed because it is too large
Load Diff
@ -1,427 +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/. */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type constructors.
|
|
||||||
*
|
|
||||||
* This module contains templates that allow to build new types from the
|
|
||||||
* available ones.
|
|
||||||
*
|
|
||||||
* Copyright: Eugene Wissner 2017-2020.
|
|
||||||
* 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)
|
|
||||||
* Source: $(LINK2 https://github.com/caraus-ecms/tanya/blob/master/source/tanya/typecons.d,
|
|
||||||
* tanya/typecons.d)
|
|
||||||
*/
|
|
||||||
module tanya.typecons;
|
|
||||||
|
|
||||||
import tanya.format;
|
|
||||||
import tanya.memory.lifetime;
|
|
||||||
import tanya.meta.metafunction;
|
|
||||||
import tanya.meta.trait;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* $(D_PSYMBOL Tuple) can store two or more heterogeneous objects.
|
|
||||||
*
|
|
||||||
* The objects can by accessed by index as `obj[0]` and `obj[1]` or by optional
|
|
||||||
* names (e.g. `obj.first`).
|
|
||||||
*
|
|
||||||
* $(D_PARAM Specs) contains a list of object types and names. First
|
|
||||||
* comes the object type, then an optional string containing the name.
|
|
||||||
* If you want the object be accessible only by its index (`0` or `1`),
|
|
||||||
* just skip the name.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* Specs = Field types and names.
|
|
||||||
*
|
|
||||||
* See_Also: $(D_PSYMBOL tuple).
|
|
||||||
*/
|
|
||||||
template Tuple(Specs...)
|
|
||||||
{
|
|
||||||
template parseSpecs(size_t fieldCount, Specs...)
|
|
||||||
{
|
|
||||||
static if (Specs.length == 0)
|
|
||||||
{
|
|
||||||
alias parseSpecs = AliasSeq!();
|
|
||||||
}
|
|
||||||
else static if (is(Specs[0]) && fieldCount < 2)
|
|
||||||
{
|
|
||||||
static if (is(typeof(Specs[1]) == string))
|
|
||||||
{
|
|
||||||
alias parseSpecs
|
|
||||||
= AliasSeq!(Pack!(Specs[0], Specs[1]),
|
|
||||||
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
alias parseSpecs
|
|
||||||
= AliasSeq!(Pack!(Specs[0]),
|
|
||||||
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
static assert(false, "Invalid argument: " ~ Specs[0].stringof);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alias ChooseType(alias T) = T.Seq[0];
|
|
||||||
alias ParsedSpecs = parseSpecs!(0, Specs);
|
|
||||||
|
|
||||||
static assert(ParsedSpecs.length > 1, "Invalid argument count");
|
|
||||||
|
|
||||||
private string formatAliases(size_t n, Specs...)()
|
|
||||||
{
|
|
||||||
static if (Specs.length == 0)
|
|
||||||
{
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
string fieldAlias;
|
|
||||||
static if (Specs[0].length == 2)
|
|
||||||
{
|
|
||||||
char[21] buffer;
|
|
||||||
fieldAlias = "alias " ~ Specs[0][1] ~ " = expand["
|
|
||||||
~ integral2String(n, buffer).idup ~ "];";
|
|
||||||
}
|
|
||||||
return fieldAlias ~ formatAliases!(n + 1, Specs[1 .. $])();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Tuple
|
|
||||||
{
|
|
||||||
/// Field types.
|
|
||||||
alias Types = Map!(ChooseType, ParsedSpecs);
|
|
||||||
|
|
||||||
// Create field aliases.
|
|
||||||
mixin(formatAliases!(0, ParsedSpecs[0 .. $])());
|
|
||||||
|
|
||||||
/// Represents the values of the $(D_PSYMBOL Tuple) as a list of values.
|
|
||||||
Types expand;
|
|
||||||
|
|
||||||
alias expand this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
auto pair = Tuple!(int, "first", string, "second")(1, "second");
|
|
||||||
assert(pair.first == 1);
|
|
||||||
assert(pair[0] == 1);
|
|
||||||
assert(pair.second == "second");
|
|
||||||
assert(pair[1] == "second");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new $(D_PSYMBOL Tuple).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* Names = Field names.
|
|
||||||
*
|
|
||||||
* See_Also: $(D_PSYMBOL Tuple).
|
|
||||||
*/
|
|
||||||
template tuple(Names...)
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Creates a new $(D_PSYMBOL Tuple).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* Args = Field types.
|
|
||||||
* args = Field values.
|
|
||||||
*
|
|
||||||
* Returns: Newly created $(D_PSYMBOL Tuple).
|
|
||||||
*/
|
|
||||||
auto tuple(Args...)(auto ref Args args)
|
|
||||||
if (Args.length >= Names.length && isTypeTuple!Args)
|
|
||||||
{
|
|
||||||
alias Zipped = ZipWith!(AliasSeq, Pack!Args, Pack!Names);
|
|
||||||
alias Nameless = Args[Names.length .. $];
|
|
||||||
|
|
||||||
return Tuple!(Zipped, Nameless)(forward!args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
auto t = tuple!("one", "two")(20, 5);
|
|
||||||
assert(t.one == 20);
|
|
||||||
assert(t.two == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type that can hold one of the types listed as its template parameters.
|
|
||||||
*
|
|
||||||
* $(D_PSYMBOL Variant) is a type similar to $(D_KEYWORD union), but
|
|
||||||
* $(D_PSYMBOL Variant) keeps track of the actually used type and throws an
|
|
||||||
* assertion error when trying to access an invalid type at runtime.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* Specs = Types this $(D_SPYBMOL Variant) can hold.
|
|
||||||
*/
|
|
||||||
template Variant(Specs...)
|
|
||||||
if (isTypeTuple!Specs && NoDuplicates!Specs.length == Specs.length)
|
|
||||||
{
|
|
||||||
union AlignedUnion(Args...)
|
|
||||||
{
|
|
||||||
static if (Args.length > 0)
|
|
||||||
{
|
|
||||||
Args[0] value;
|
|
||||||
}
|
|
||||||
static if (Args.length > 1)
|
|
||||||
{
|
|
||||||
AlignedUnion!(Args[1 .. $]) rest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct VariantAccessorInfo
|
|
||||||
{
|
|
||||||
string accessor;
|
|
||||||
ptrdiff_t tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
template accessor(T, Union)
|
|
||||||
{
|
|
||||||
enum VariantAccessorInfo info = accessorImpl!(T, Union, 1);
|
|
||||||
enum accessor = VariantAccessorInfo("this.values" ~ info.accessor, info.tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
template accessorImpl(T, Union, size_t tag)
|
|
||||||
{
|
|
||||||
static if (is(T == typeof(Union.value)))
|
|
||||||
{
|
|
||||||
enum accessorImpl = VariantAccessorInfo(".value", tag);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
enum VariantAccessorInfo info = accessorImpl!(T, typeof(Union.rest), tag + 1);
|
|
||||||
enum accessorImpl = VariantAccessorInfo(".rest" ~ info.accessor, info.tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Variant
|
|
||||||
{
|
|
||||||
/// Types can be present in this $(D_PSYMBOL Variant).
|
|
||||||
alias Types = Specs;
|
|
||||||
|
|
||||||
private ptrdiff_t tag = -1;
|
|
||||||
private AlignedUnion!Types values;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs this $(D_PSYMBOL Variant) with one of the types supported
|
|
||||||
* in it.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Type of the initial value.
|
|
||||||
* value = Initial value.
|
|
||||||
*/
|
|
||||||
this(T)(ref T value)
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
{
|
|
||||||
copyAssign!T(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
this(T)(T value)
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
{
|
|
||||||
moveAssign!T(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
~this()
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
this(this)
|
|
||||||
{
|
|
||||||
alias pred(U) = hasElaborateCopyConstructor!(U.Seq[1]);
|
|
||||||
static foreach (Type; Filter!(pred, Enumerate!Types))
|
|
||||||
{
|
|
||||||
if (this.tag == Type.Seq[0])
|
|
||||||
{
|
|
||||||
get!(Type.Seq[1]).__postblit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells whether this $(D_PSYMBOL Variant) is initialized.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) contains a
|
|
||||||
* value, $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
bool hasValue() const
|
|
||||||
{
|
|
||||||
return this.tag != -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells whether this $(D_PSYMBOL Variant) holds currently a value of
|
|
||||||
* type $(D_PARAM T).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Examined type.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) currently
|
|
||||||
* contains a value of type $(D_PARAM T), $(D_KEYWORD false)
|
|
||||||
* otherwise.
|
|
||||||
*/
|
|
||||||
bool peek(T)() const
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
{
|
|
||||||
return this.tag == staticIndexOf!(T, Types);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying value, assuming it is of the type $(D_PARAM T).
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Type of the value should be returned.
|
|
||||||
*
|
|
||||||
* Returns: The underyling value.
|
|
||||||
*
|
|
||||||
* Precondition: The $(D_PSYMBOL Variant) has a value.
|
|
||||||
*
|
|
||||||
* See_Also: $(D_PSYMBOL peek), $(D_PSYMBOL hasValue).
|
|
||||||
*/
|
|
||||||
ref inout(T) get(T)() inout
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
in
|
|
||||||
{
|
|
||||||
assert(this.tag == staticIndexOf!(T, Types), "Variant isn't initialized");
|
|
||||||
}
|
|
||||||
do
|
|
||||||
{
|
|
||||||
mixin("return " ~ accessor!(T, AlignedUnion!Types).accessor ~ ";");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reassigns the value.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* T = Type of the new value
|
|
||||||
* that = New value.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD this).
|
|
||||||
*/
|
|
||||||
ref typeof(this) opAssign(T)(T that)
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
return moveAssign!T(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ditto
|
|
||||||
ref typeof(this) opAssign(T)(ref T that)
|
|
||||||
if (canFind!(T, Types))
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
return copyAssign!T(that);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ref typeof(this) moveAssign(T)(ref T that) @trusted
|
|
||||||
{
|
|
||||||
this.tag = staticIndexOf!(T, Types);
|
|
||||||
|
|
||||||
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
|
|
||||||
moveEmplace(that, mixin(accessorMixin));
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ref typeof(this) copyAssign(T)(ref T that) return
|
|
||||||
{
|
|
||||||
this.tag = staticIndexOf!(T, Types);
|
|
||||||
|
|
||||||
enum string accessorMixin = accessor!(T, AlignedUnion!Types).accessor;
|
|
||||||
emplace!T((() @trusted => (&mixin(accessorMixin))[0 .. 1])(), that);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reset()
|
|
||||||
{
|
|
||||||
alias pred(U) = hasElaborateDestructor!(U.Seq[1]);
|
|
||||||
static foreach (Type; Filter!(pred, Enumerate!Types))
|
|
||||||
{
|
|
||||||
if (this.tag == Type.Seq[0])
|
|
||||||
{
|
|
||||||
destroy(get!(Type.Seq[1]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns $(D_PSYMBOL TypeInfo) corresponding to the current type.
|
|
||||||
*
|
|
||||||
* If this $(D_PSYMBOL Variant) isn't initialized, returns
|
|
||||||
* $(D_KEYWORD null).
|
|
||||||
*
|
|
||||||
* Returns: $(D_PSYMBOL TypeInfo) of the current type.
|
|
||||||
*/
|
|
||||||
@property TypeInfo type()
|
|
||||||
{
|
|
||||||
static foreach (i, Type; Types)
|
|
||||||
{
|
|
||||||
if (this.tag == i)
|
|
||||||
{
|
|
||||||
return typeid(Type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares this $(D_PSYMBOL Variant) with another one with the same
|
|
||||||
* specification for equality.
|
|
||||||
*
|
|
||||||
* $(UL
|
|
||||||
* $(LI If both hold values of the same type, these values are
|
|
||||||
* compared.)
|
|
||||||
* $(LI If they hold values of different types, then the
|
|
||||||
* $(D_PSYMBOL Variant)s aren't equal.)
|
|
||||||
* $(LI If only one of them is initialized but another one not, they
|
|
||||||
* aren't equal.)
|
|
||||||
* $(LI If neither of them is initialized, they are equal.)
|
|
||||||
* )
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* that = The $(D_PSYMBOL Variant) to compare with.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if this $(D_PSYMBOL Variant) is equal to
|
|
||||||
* $(D_PARAM that), $(D_KEYWORD false) otherwise.
|
|
||||||
*/
|
|
||||||
bool opEquals()(auto ref inout(Variant) that) inout
|
|
||||||
{
|
|
||||||
if (this.tag != that.tag)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
static foreach (i, Type; Types)
|
|
||||||
{
|
|
||||||
if (this.tag == i)
|
|
||||||
{
|
|
||||||
return get!Type == that.get!Type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant = 5;
|
|
||||||
assert(variant.peek!int);
|
|
||||||
assert(variant.get!int == 5);
|
|
||||||
|
|
||||||
variant = 5.4;
|
|
||||||
assert(!variant.peek!int);
|
|
||||||
assert(variant.get!double == 5.4);
|
|
||||||
}
|
|
@ -95,38 +95,3 @@ import tanya.test.stub;
|
|||||||
NonCopyable[] nonCopyable;
|
NonCopyable[] nonCopyable;
|
||||||
initializeAll(nonCopyable);
|
initializeAll(nonCopyable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
const int[5] expected = [1, 2, 3, 4, 5];
|
|
||||||
int[5] actual = [4, 5, 1, 2, 3];
|
|
||||||
|
|
||||||
rotate(actual[0 .. 2], actual[2 .. $]);
|
|
||||||
assert(equal(actual[], expected[]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doesn't cause an infinite loop if back is shorter than the front
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
const int[5] expected = [1, 2, 3, 4, 5];
|
|
||||||
int[5] actual = [3, 4, 5, 1, 2];
|
|
||||||
|
|
||||||
rotate(actual[0 .. 3], actual[3 .. $]);
|
|
||||||
assert(equal(actual[], expected[]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Doesn't call .front on an empty front
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
import std.algorithm.comparison : equal;
|
|
||||||
|
|
||||||
const int[2] expected = [2, 8];
|
|
||||||
int[2] actual = expected;
|
|
||||||
|
|
||||||
rotate(actual[0 .. 0], actual[]);
|
|
||||||
assert(equal(actual[], expected[]));
|
|
||||||
}
|
|
||||||
|
@ -1,97 +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/. */
|
|
||||||
module tanya.async.tests.loop;
|
|
||||||
|
|
||||||
import core.time;
|
|
||||||
import tanya.async.loop;
|
|
||||||
import tanya.async.watcher;
|
|
||||||
import tanya.memory.allocator;
|
|
||||||
|
|
||||||
private final class DummyWatcher : Watcher
|
|
||||||
{
|
|
||||||
bool invoked;
|
|
||||||
|
|
||||||
override void invoke() @nogc
|
|
||||||
{
|
|
||||||
this.invoked = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class TestLoop : Loop
|
|
||||||
{
|
|
||||||
override protected bool reify(SocketWatcher watcher,
|
|
||||||
EventMask oldEvents,
|
|
||||||
EventMask events) @nogc
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected void poll() @nogc
|
|
||||||
{
|
|
||||||
assert(!this.done);
|
|
||||||
unloop();
|
|
||||||
}
|
|
||||||
|
|
||||||
override protected @property uint maxEvents()
|
|
||||||
const pure nothrow @safe @nogc
|
|
||||||
{
|
|
||||||
return 64U;
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc @system unittest
|
|
||||||
{
|
|
||||||
auto loop = defaultAllocator.make!TestLoop;
|
|
||||||
assert(loop.blockTime == 1.dur!"minutes");
|
|
||||||
|
|
||||||
loop.blockTime = 2.dur!"minutes";
|
|
||||||
assert(loop.blockTime == 2.dur!"minutes");
|
|
||||||
|
|
||||||
defaultAllocator.dispose(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc @system unittest
|
|
||||||
{
|
|
||||||
auto loop = defaultAllocator.make!TestLoop;
|
|
||||||
assert(loop.done);
|
|
||||||
|
|
||||||
loop.run();
|
|
||||||
assert(loop.done);
|
|
||||||
|
|
||||||
defaultAllocator.dispose(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc @system unittest
|
|
||||||
{
|
|
||||||
auto loop = defaultAllocator.make!TestLoop;
|
|
||||||
auto watcher = defaultAllocator.make!DummyWatcher;
|
|
||||||
loop.pendings.insertBack(watcher);
|
|
||||||
|
|
||||||
assert(!watcher.invoked);
|
|
||||||
loop.run();
|
|
||||||
assert(watcher.invoked);
|
|
||||||
|
|
||||||
defaultAllocator.dispose(loop);
|
|
||||||
defaultAllocator.dispose(watcher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc @system unittest
|
|
||||||
{
|
|
||||||
auto loop = defaultAllocator.make!TestLoop;
|
|
||||||
assert(loop.maxEvents == 64);
|
|
||||||
|
|
||||||
defaultAllocator.dispose(loop);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc @system unittest
|
|
||||||
{
|
|
||||||
auto oldLoop = defaultLoop;
|
|
||||||
auto loop = defaultAllocator.make!TestLoop;
|
|
||||||
|
|
||||||
defaultLoop = loop;
|
|
||||||
assert(defaultLoop is loop);
|
|
||||||
|
|
||||||
defaultLoop = oldLoop;
|
|
||||||
defaultAllocator.dispose(loop);
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
module tanya.tests.bitmanip;
|
|
||||||
|
|
||||||
import tanya.bitmanip;
|
|
||||||
|
|
||||||
// Casts to a boolean
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
assert(BitFlags!One(One.one));
|
|
||||||
assert(!BitFlags!One());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assigns to and compares with a single value
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
BitFlags!One bitFlags;
|
|
||||||
bitFlags = One.one;
|
|
||||||
assert(bitFlags == One.one);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assigns to and compares with the same type
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
auto bitFlags1 = BitFlags!One(One.one);
|
|
||||||
BitFlags!One bitFlags2;
|
|
||||||
bitFlags2 = bitFlags1;
|
|
||||||
assert(bitFlags1 == bitFlags2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
assert((BitFlags!One() | One.one) == BitFlags!One(One.one));
|
|
||||||
assert((BitFlags!One() | BitFlags!One(One.one)) == BitFlags!One(One.one));
|
|
||||||
|
|
||||||
assert(!(BitFlags!One() & BitFlags!One(One.one)));
|
|
||||||
|
|
||||||
assert(!(BitFlags!One(One.one) ^ One.one));
|
|
||||||
assert(BitFlags!One() ^ BitFlags!One(One.one));
|
|
||||||
|
|
||||||
assert(~BitFlags!One());
|
|
||||||
|
|
||||||
assert(BitFlags!One().toHash() == 0);
|
|
||||||
assert(BitFlags!One(One.one).toHash() != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// opBinaryRight is allowed
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static assert(is(typeof({ One.one | BitFlags!One(); })));
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum One : int
|
|
||||||
{
|
|
||||||
one = 1,
|
|
||||||
}
|
|
@ -258,7 +258,7 @@ unittest
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Typeid
|
// Typeid
|
||||||
nothrow pure @safe unittest
|
nothrow @safe unittest
|
||||||
{
|
{
|
||||||
assert(format!"{}"(typeid(int[])) == "int[]");
|
assert(format!"{}"(typeid(int[])) == "int[]");
|
||||||
|
|
||||||
|
@ -1,124 +0,0 @@
|
|||||||
module tanya.tests.typecons;
|
|
||||||
|
|
||||||
import tanya.test.stub;
|
|
||||||
import tanya.typecons;
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static assert(is(Tuple!(int, int)));
|
|
||||||
static assert(!is(Tuple!(int, 5)));
|
|
||||||
|
|
||||||
static assert(is(Tuple!(int, "first", int)));
|
|
||||||
static assert(is(Tuple!(int, "first", int, "second")));
|
|
||||||
static assert(is(Tuple!(int, "first", int)));
|
|
||||||
|
|
||||||
static assert(is(Tuple!(int, int, "second")));
|
|
||||||
static assert(!is(Tuple!("first", int, "second", int)));
|
|
||||||
static assert(!is(Tuple!(int, int, int)));
|
|
||||||
|
|
||||||
static assert(!is(Tuple!(int, "first")));
|
|
||||||
|
|
||||||
static assert(!is(Tuple!(int, double, char)));
|
|
||||||
static assert(!is(Tuple!(int, "first", double, "second", char, "third")));
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant;
|
|
||||||
variant = 5;
|
|
||||||
assert(variant.peek!int);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant;
|
|
||||||
variant = 5.0;
|
|
||||||
assert(!variant.peek!int);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant = 5;
|
|
||||||
assert(variant.get!int == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static assert(is(Variant!(int, float)));
|
|
||||||
static assert(is(Variant!int));
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static struct WithDestructorAndCopy
|
|
||||||
{
|
|
||||||
this(this) @nogc nothrow pure @safe
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~this() @nogc nothrow pure @safe
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
static assert(is(Variant!WithDestructorAndCopy));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Equality compares the underlying objects
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant1 = 5;
|
|
||||||
Variant!(int, double) variant2 = 5;
|
|
||||||
assert(variant1 == variant2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant1 = 5;
|
|
||||||
Variant!(int, double) variant2 = 6;
|
|
||||||
assert(variant1 != variant2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Differently typed variants aren't equal
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant1 = 5;
|
|
||||||
Variant!(int, double) variant2 = 5.0;
|
|
||||||
assert(variant1 != variant2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uninitialized variants are equal
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, double) variant1, variant2;
|
|
||||||
assert(variant1 == variant2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calls postblit constructor of the active type
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static struct S
|
|
||||||
{
|
|
||||||
bool called;
|
|
||||||
|
|
||||||
this(this)
|
|
||||||
{
|
|
||||||
this.called = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Variant!(int, S) variant1 = S();
|
|
||||||
auto variant2 = variant1;
|
|
||||||
assert(variant2.get!S.called);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant.type is null if the Variant doesn't have a value
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
Variant!(int, uint) variant;
|
|
||||||
assert(variant.type is null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Variant can contain only distinct types
|
|
||||||
@nogc nothrow pure @safe unittest
|
|
||||||
{
|
|
||||||
static assert(!is(Variant!(int, int)));
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user