Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
a648e2120a | |||
bc61809050 | |||
8c42cbfd63 | |||
58664570f9 | |||
decb82f437 | |||
357c7e279d | |||
32e19c8b58 | |||
f5c6c5b483 | |||
ba2d086fb8 | |||
7a0241b484 | |||
36dad80e18 | |||
29d883150e | |||
e2bed0cfcb | |||
38afeac071 | |||
001c7c3e33 | |||
d4ab339feb | |||
8477312769 | |||
67f90e137d | |||
f264fd5597 | |||
9e75620f1b | |||
45825946c0 | |||
8afb552d59 | |||
e4091669f8 | |||
1cb9349226 | |||
06620dc5df | |||
708d95db49 | |||
85d9361bfb | |||
a6a6f496eb | |||
db12f03264 | |||
231aedb8ad | |||
c3b63ee40d | |||
6f405c5e08 | |||
16cf8478cf | |||
8915a0c7a7 | |||
e5c7edb72c | |||
64e0d666ed | |||
f2aac680c5 | |||
65c3ca14ec | |||
4fa47153ba | |||
d629525a4b | |||
33d321f0d7 | |||
3d64d59ba9 | |||
4635835a99 | |||
8725ec5f20 | |||
9a4c8cea06 | |||
eb360bda38 | |||
4b1cd2cbfd | |||
628153e2e8 | |||
7aa9ac9f4a | |||
cd944a61b7 | |||
8156d0fe3a | |||
47ef787353 | |||
6436ad49df | |||
e1964e47a5 | |||
6e2ce5d686 | |||
ba6bf554fb | |||
b1d2b9bd9e | |||
9b953198fa | |||
bc2a6d2703 | |||
b458250ad7 | |||
b08d5e5d83 | |||
445b872e91 | |||
5e16fe98d6 | |||
647cfe03c2 | |||
4cd6126d6b | |||
b870179a35 | |||
aabb4fb534 | |||
4d8b95812e | |||
e921413249 | |||
49cae88645 | |||
402fdfae89 | |||
7892c1a930 | |||
b90517580e | |||
85380ac3fc | |||
b90c56395c | |||
d0ada39fa7 | |||
f4145abfd1 | |||
093d499729 | |||
f90a03501b | |||
c6a99b114e | |||
43319e4e3a | |||
33dbf042c2 | |||
3c23aca6a6 | |||
885fca9b5e | |||
074d027629 | |||
f4b90d8b51 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
@ -7,14 +7,17 @@ os:
|
||||
language: d
|
||||
|
||||
d:
|
||||
- dmd-2.073.0
|
||||
- dmd-2.074.0
|
||||
- dmd-2.073.2
|
||||
- dmd-2.072.2
|
||||
- dmd-2.071.2
|
||||
- dmd-2.070.2
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- ARCH=x86_64
|
||||
|
||||
script:
|
||||
- dub test --arch=$ARCH
|
||||
- dub test -b unittest-cov --arch=$ARCH
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
42
README.md
42
README.md
@ -1,6 +1,8 @@
|
||||
# Tanya
|
||||
|
||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||
[](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
|
||||
[](https://codecov.io/gh/caraus-ecms/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||
@ -21,32 +23,32 @@ data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||
Tanya consists of the following packages:
|
||||
|
||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||
* `container`: Queue, Vector, Singly linked list, buffers.
|
||||
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
|
||||
string.
|
||||
* `math`: Arbitrary precision integer and a set of functions.
|
||||
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||
helper functions).
|
||||
* `network`: URL-Parsing, sockets.
|
||||
* `network`: URL-Parsing, sockets, utilities.
|
||||
|
||||
### Supported compilers
|
||||
|
||||
* dmd 2.073.0
|
||||
* dmd 2.072.2
|
||||
* dmd 2.071.2
|
||||
* dmd 2.070.2
|
||||
| dmd |
|
||||
|:-------:|
|
||||
| 2.074.0 |
|
||||
| 2.073.2 |
|
||||
| 2.072.2 |
|
||||
| 2.071.2 |
|
||||
|
||||
### Current status
|
||||
|
||||
The library is currently under development, but the API is becoming gradually
|
||||
stable.
|
||||
Following modules are under development:
|
||||
|
||||
`container`s are being extended to support ranges. Also following modules are
|
||||
coming soon:
|
||||
* UTF-8 string.
|
||||
* Hash table.
|
||||
|
||||
`math` package contains an arbitrary precision integer implementation that
|
||||
needs more test cases, better performance and some additional features
|
||||
(constructing from a string and an ubyte array, and converting it back).
|
||||
| Feature | Branch | Build status |
|
||||
|--------------|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| BitVector | bitvector | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|
||||
| TLS | crypto | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
|
||||
| File IO | io | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
|
||||
| Hash table | horton-table | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/horton-table) |
|
||||
|
||||
### Further characteristics
|
||||
|
||||
@ -57,10 +59,14 @@ is being tested on Windows and FreeBSD as well.
|
||||
|
||||
* The library isn't thread-safe. Thread-safity should be added later.
|
||||
|
||||
## Release management
|
||||
|
||||
3-week release cycle.
|
||||
|
||||
## Contributing
|
||||
|
||||
Since I'm mostly busy writing new code and implementing new features I would
|
||||
appreciate, if anyone uses the library. It would help me to improve the
|
||||
codebase and fix issues.
|
||||
|
||||
Feel free to contact me if you have any questions.
|
||||
Feel free to contact me if you have any questions: info@caraus.de.
|
||||
|
52
appveyor.yml
Normal file
52
appveyor.yml
Normal file
@ -0,0 +1,52 @@
|
||||
platform: x64
|
||||
os: Visual Studio 2017
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- DC: dmd
|
||||
DVersion: 2.074.0
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.073.2
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.072.2
|
||||
arch: x86
|
||||
- DC: dmd
|
||||
DVersion: 2.071.2
|
||||
arch: x86
|
||||
|
||||
skip_tags: true
|
||||
|
||||
install:
|
||||
- ps: function SetUpDCompiler
|
||||
{
|
||||
$env:toolchain = "msvc";
|
||||
$version = $env:DVersion;
|
||||
Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z";
|
||||
echo "finished.";
|
||||
pushd c:\\;
|
||||
7z x dmd.7z > $null;
|
||||
popd;
|
||||
}
|
||||
- ps: SetUpDCompiler
|
||||
|
||||
- ps: if($env:DVersion -eq "2.071.2"){
|
||||
Invoke-WebRequest "http://code.dlang.org/files/dub-1.2.1-windows-x86.zip" -OutFile "dub.zip";
|
||||
7z x dub.zip -odub > $null;
|
||||
Move-Item "dub/dub.exe" "C:\dmd2\windows\bin"
|
||||
}
|
||||
|
||||
before_build:
|
||||
- ps: $env:PATH += ";C:\dmd2\windows\bin;";
|
||||
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch%
|
||||
|
||||
build_script:
|
||||
- echo dummy build script - dont remove me
|
||||
|
||||
test_script:
|
||||
- echo %DC%
|
||||
- echo %PATH%
|
||||
- 'dub --version'
|
||||
- '%DC% --version'
|
||||
- dub test --arch=x86 --compiler=%DC%
|
@ -18,7 +18,7 @@ import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
@ -29,153 +29,153 @@ import std.algorithm.comparison;
|
||||
|
||||
extern (C) nothrow @nogc
|
||||
{
|
||||
int epoll_create1(int flags);
|
||||
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
|
||||
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
|
||||
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 Vector!epoll_event events;
|
||||
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 = Vector!epoll_event(maxEvents, MmapPool.instance);
|
||||
}
|
||||
/**
|
||||
* 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, MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this() @nogc
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
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];
|
||||
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);
|
||||
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.enqueue(transport);
|
||||
}
|
||||
}
|
||||
if (events[i].events & EPOLLOUT)
|
||||
{
|
||||
transport.writeReady = true;
|
||||
if (transport.input.length)
|
||||
{
|
||||
feed(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.enqueue(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");
|
||||
}
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
override protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
{
|
||||
return min(super.blockTime, 1.dur!"seconds");
|
||||
}
|
||||
}
|
||||
|
@ -31,354 +31,354 @@ import core.sys.windows.winsock2;
|
||||
*/
|
||||
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||
{
|
||||
private SocketException exception;
|
||||
private SocketException exception;
|
||||
|
||||
private ReadBuffer!ubyte output;
|
||||
private ReadBuffer!ubyte output;
|
||||
|
||||
private WriteBuffer!ubyte input;
|
||||
private WriteBuffer!ubyte input;
|
||||
|
||||
private Protocol protocol_;
|
||||
private Protocol protocol_;
|
||||
|
||||
private bool closing;
|
||||
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, MmapPool.instance);
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
active = true;
|
||||
}
|
||||
/**
|
||||
* 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, MmapPool.instance);
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE socket !is null)
|
||||
*/
|
||||
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
||||
out (socket)
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
return cast(OverlappedConnectedSocket) socket_;
|
||||
}
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE socket !is null)
|
||||
*/
|
||||
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
||||
out (socket)
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
return cast(OverlappedConnectedSocket) socket_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||
*/
|
||||
bool isClosing() const pure nothrow @safe @nogc
|
||||
{
|
||||
return closing;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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_;
|
||||
}
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
@property Protocol protocol() pure nothrow @safe @nogc
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the protocol.
|
||||
*
|
||||
* The protocol is deallocated by the event loop, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE protocol !is null)
|
||||
*/
|
||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
protocol_ = protocol;
|
||||
}
|
||||
/**
|
||||
* Switches the protocol.
|
||||
*
|
||||
* The protocol is deallocated by the event loop, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE protocol !is null)
|
||||
*/
|
||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
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 = MmapPool.instance.make!SocketState;
|
||||
socket.beginSend(input[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
MmapPool.instance.dispose(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.disconnected(exception);
|
||||
MmapPool.instance.dispose(protocol_);
|
||||
defaultAllocator.dispose(exception);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 = MmapPool.instance.make!SocketState;
|
||||
socket.beginSend(input[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
MmapPool.instance.dispose(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
protocol.disconnected(exception);
|
||||
MmapPool.instance.dispose(protocol_);
|
||||
defaultAllocator.dispose(exception);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class IOCPLoop : Loop
|
||||
{
|
||||
protected HANDLE completionPort;
|
||||
protected HANDLE completionPort;
|
||||
|
||||
protected OVERLAPPED overlap;
|
||||
protected OVERLAPPED overlap;
|
||||
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
|
||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||
if (!completionPort)
|
||||
{
|
||||
throw make!BadLoopException(defaultAllocator,
|
||||
"Creating completion port failed");
|
||||
}
|
||||
}
|
||||
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);
|
||||
/**
|
||||
* 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(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
||||
completionPort,
|
||||
cast(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
socket.beginAccept(overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.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);
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
socket.beginAccept(overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.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(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
||||
completionPort,
|
||||
cast(ULONG_PTR) (cast(void*) watcher),
|
||||
0) !is completionPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Begin to read
|
||||
if (!(oldEvents & Event.read) && (events & Event.read))
|
||||
{
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
transport.socket.beginReceive(transport.output[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Begin to read
|
||||
if (!(oldEvents & Event.read) && (events & Event.read))
|
||||
{
|
||||
try
|
||||
{
|
||||
overlapped = MmapPool.instance.make!SocketState;
|
||||
transport.socket.beginReceive(transport.output[], overlapped);
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void kill(StreamTransport transport,
|
||||
SocketException exception = null) @nogc
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
transport.socket.shutdown();
|
||||
defaultAllocator.dispose(transport.socket);
|
||||
transport.exception = exception;
|
||||
pendings.enqueue(transport);
|
||||
}
|
||||
private void kill(StreamTransport transport,
|
||||
SocketException exception = null) @nogc
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
transport.socket.shutdown();
|
||||
defaultAllocator.dispose(transport.socket);
|
||||
transport.exception = exception;
|
||||
pendings.enqueue(transport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
override protected void poll() @nogc
|
||||
{
|
||||
DWORD lpNumberOfBytes;
|
||||
ULONG_PTR key;
|
||||
LPOVERLAPPED overlap;
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
override protected void poll() @nogc
|
||||
{
|
||||
DWORD lpNumberOfBytes;
|
||||
ULONG_PTR key;
|
||||
LPOVERLAPPED overlap;
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
|
||||
auto result = GetQueuedCompletionStatus(completionPort,
|
||||
&lpNumberOfBytes,
|
||||
&key,
|
||||
&overlap,
|
||||
timeout);
|
||||
if (result == FALSE && overlap == NULL)
|
||||
{
|
||||
return; // Timeout
|
||||
}
|
||||
auto result = GetQueuedCompletionStatus(completionPort,
|
||||
&lpNumberOfBytes,
|
||||
&key,
|
||||
&overlap,
|
||||
timeout);
|
||||
if (result == FALSE && overlap == NULL)
|
||||
{
|
||||
return; // Timeout
|
||||
}
|
||||
|
||||
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
||||
assert(overlapped !is null);
|
||||
scope (failure)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
}
|
||||
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
||||
assert(overlapped !is null);
|
||||
scope (failure)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
}
|
||||
|
||||
switch (overlapped.event)
|
||||
{
|
||||
case OverlappedSocketEvent.accept:
|
||||
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
||||
assert(connection !is null);
|
||||
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 listener = cast(OverlappedStreamSocket) connection.socket;
|
||||
assert(listener !is null);
|
||||
|
||||
auto socket = listener.endAccept(overlapped);
|
||||
auto transport = MmapPool.instance.make!StreamTransport(socket);
|
||||
auto socket = listener.endAccept(overlapped);
|
||||
auto transport = MmapPool.instance.make!StreamTransport(socket);
|
||||
|
||||
connection.incoming.enqueue(transport);
|
||||
connection.incoming.enqueue(transport);
|
||||
|
||||
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
|
||||
pendings.enqueue(connection);
|
||||
listener.beginAccept(overlapped);
|
||||
break;
|
||||
case OverlappedSocketEvent.read:
|
||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||
assert(transport !is null);
|
||||
pendings.enqueue(connection);
|
||||
listener.beginAccept(overlapped);
|
||||
break;
|
||||
case OverlappedSocketEvent.read:
|
||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||
assert(transport !is null);
|
||||
|
||||
if (!transport.active)
|
||||
{
|
||||
MmapPool.instance.dispose(transport);
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
return;
|
||||
}
|
||||
if (!transport.active)
|
||||
{
|
||||
MmapPool.instance.dispose(transport);
|
||||
MmapPool.instance.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;
|
||||
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.enqueue(transport);
|
||||
}
|
||||
break;
|
||||
case OverlappedSocketEvent.write:
|
||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||
assert(transport !is null);
|
||||
transport.output += received;
|
||||
// Receive was interrupted because the buffer is full. We have to continue.
|
||||
if (full)
|
||||
{
|
||||
transport.socket.beginReceive(transport.output[], overlapped);
|
||||
}
|
||||
pendings.enqueue(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");
|
||||
}
|
||||
}
|
||||
}
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,31 +12,31 @@ module tanya.async.event.kqueue;
|
||||
|
||||
version (OSX)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (TVOS)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (WatchOS)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
version = MacBSD;
|
||||
}
|
||||
|
||||
version (MacBSD):
|
||||
@ -50,62 +50,62 @@ import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
|
||||
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
||||
{
|
||||
*kevp = kevent_t(args);
|
||||
*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
|
||||
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 */
|
||||
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) */
|
||||
/* 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 */
|
||||
/* 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 */
|
||||
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 */
|
||||
/* returned values */
|
||||
EV_EOF = 0x8000, /* EOF detected */
|
||||
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||
}
|
||||
|
||||
extern(C) int kqueue() nothrow @nogc;
|
||||
@ -115,211 +115,211 @@ extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||
|
||||
final class KqueueLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
private Vector!kevent_t events;
|
||||
private Vector!kevent_t changes;
|
||||
private size_t changeCount;
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
|
||||
if ((fd = kqueue()) == -1)
|
||||
{
|
||||
throw make!BadLoopException(defaultAllocator,
|
||||
"kqueue initialization failed");
|
||||
}
|
||||
events = Vector!kevent_t(64, MmapPool.instance);
|
||||
changes = Vector!kevent_t(64, MmapPool.instance);
|
||||
}
|
||||
if ((fd = kqueue()) == -1)
|
||||
{
|
||||
throw make!BadLoopException(defaultAllocator,
|
||||
"kqueue initialization failed");
|
||||
}
|
||||
events = Array!kevent_t(64, MmapPool.instance);
|
||||
changes = Array!kevent_t(64, MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this() @nogc
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this() @nogc
|
||||
{
|
||||
close(fd);
|
||||
}
|
||||
|
||||
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||
{
|
||||
if (changes.length <= changeCount)
|
||||
{
|
||||
changes.length = changeCount + maxEvents;
|
||||
}
|
||||
EV_SET(&changes[changeCount],
|
||||
cast(ulong) socket,
|
||||
filter,
|
||||
flags,
|
||||
0U,
|
||||
0L,
|
||||
null);
|
||||
++changeCount;
|
||||
}
|
||||
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||
{
|
||||
if (changes.length <= changeCount)
|
||||
{
|
||||
changes.length = changeCount + maxEvents;
|
||||
}
|
||||
EV_SET(&changes[changeCount],
|
||||
cast(ulong) socket,
|
||||
filter,
|
||||
flags,
|
||||
0U,
|
||||
0L,
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if (changeCount > maxEvents)
|
||||
{
|
||||
events.length = changes.length;
|
||||
}
|
||||
|
||||
auto eventCount = kevent(fd,
|
||||
changes.get().ptr,
|
||||
cast(int) changeCount,
|
||||
events.get().ptr,
|
||||
maxEvents,
|
||||
&ts);
|
||||
changeCount = 0;
|
||||
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;
|
||||
}
|
||||
if (eventCount < 0)
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw defaultAllocator.make!BadLoopException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i; i < eventCount; ++i)
|
||||
{
|
||||
assert(connections.length > events[i].ident);
|
||||
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);
|
||||
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.enqueue(transport);
|
||||
}
|
||||
}
|
||||
else if (events[i].filter == EVFILT_WRITE)
|
||||
{
|
||||
transport.writeReady = true;
|
||||
if (transport.input.length)
|
||||
{
|
||||
feed(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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.enqueue(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");
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.container.vector;
|
||||
import tanya.container.array;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
@ -27,371 +27,374 @@ import tanya.network.socket;
|
||||
*/
|
||||
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
||||
{
|
||||
private SelectorLoop loop;
|
||||
private SelectorLoop loop;
|
||||
|
||||
private SocketException exception;
|
||||
private SocketException exception;
|
||||
|
||||
package ReadBuffer!ubyte output;
|
||||
package ReadBuffer!ubyte output;
|
||||
|
||||
package WriteBuffer!ubyte input;
|
||||
package WriteBuffer!ubyte input;
|
||||
|
||||
private Protocol protocol_;
|
||||
private Protocol protocol_;
|
||||
|
||||
private bool closing;
|
||||
private bool closing;
|
||||
|
||||
/// Received notification that the underlying socket is write-ready.
|
||||
package bool writeReady;
|
||||
/// 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
super(socket);
|
||||
this.loop = loop;
|
||||
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
active = true;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
super(socket);
|
||||
this.loop = loop;
|
||||
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE socket !is null)
|
||||
*/
|
||||
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
||||
out (socket)
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
return cast(ConnectedSocket) socket_;
|
||||
}
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE socket !is null)
|
||||
*/
|
||||
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
||||
out (socket)
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
return cast(ConnectedSocket) socket_;
|
||||
}
|
||||
|
||||
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
}
|
||||
private @property void socket(ConnectedSocket socket)
|
||||
pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
@property Protocol protocol() pure nothrow @safe @nogc
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
/**
|
||||
* Returns: Application protocol.
|
||||
*/
|
||||
@property Protocol protocol() pure nothrow @safe @nogc
|
||||
{
|
||||
return protocol_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the protocol.
|
||||
*
|
||||
* The protocol is deallocated by the event loop, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE protocol !is null)
|
||||
*/
|
||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
protocol_ = protocol;
|
||||
}
|
||||
/**
|
||||
* Switches the protocol.
|
||||
*
|
||||
* The protocol is deallocated by the event loop, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE protocol !is null)
|
||||
*/
|
||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(protocol !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
protocol_ = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||
*/
|
||||
bool isClosing() const pure nothrow @safe @nogc
|
||||
{
|
||||
return closing;
|
||||
}
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
MmapPool.instance.dispose(protocol_);
|
||||
defaultAllocator.dispose(exception);
|
||||
active = false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
MmapPool.instance.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;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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 Vector!SocketWatcher connections;
|
||||
/// Pending connections.
|
||||
protected Array!SocketWatcher connections;
|
||||
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
||||
}
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
|
||||
}
|
||||
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (ref connection; 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)
|
||||
{
|
||||
MmapPool.instance.dispose(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (ref connection; 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)
|
||||
{
|
||||
MmapPool.instance.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;
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
transport.socket.shutdown();
|
||||
defaultAllocator.dispose(transport.socket);
|
||||
transport.exception = exception;
|
||||
pendings.enqueue(transport);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
transport.socket.shutdown();
|
||||
defaultAllocator.dispose(transport.socket);
|
||||
transport.exception = exception;
|
||||
pendings.enqueue(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);
|
||||
}
|
||||
body
|
||||
{
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
if (connections.length <= watcher.socket)
|
||||
{
|
||||
connections.length = watcher.socket.handle + maxEvents / 2;
|
||||
}
|
||||
connections[watcher.socket.handle] = watcher;
|
||||
|
||||
super.start(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);
|
||||
}
|
||||
body
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
ConnectedSocket client;
|
||||
try
|
||||
{
|
||||
client = (cast(StreamSocket) connection.socket).accept();
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
defaultAllocator.dispose(e);
|
||||
break;
|
||||
}
|
||||
if (client is null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* Accept incoming connections.
|
||||
*
|
||||
* Params:
|
||||
* connection = Connection watcher ready to accept.
|
||||
*/
|
||||
package void acceptConnections(ConnectionWatcher connection) @nogc
|
||||
in
|
||||
{
|
||||
assert(connection !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
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;
|
||||
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 = MmapPool.instance.make!StreamTransport(this, client);
|
||||
connections[client.handle] = transport;
|
||||
}
|
||||
else
|
||||
{
|
||||
transport.socket = client;
|
||||
}
|
||||
if (connections.length > client.handle)
|
||||
{
|
||||
transport = cast(StreamTransport) connections[client.handle];
|
||||
}
|
||||
else
|
||||
{
|
||||
connections.length = client.handle + maxEvents / 2;
|
||||
}
|
||||
if (transport is null)
|
||||
{
|
||||
transport = MmapPool.instance.make!StreamTransport(this, client);
|
||||
connections[client.handle] = transport;
|
||||
}
|
||||
else
|
||||
{
|
||||
transport.socket = client;
|
||||
}
|
||||
|
||||
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
connection.incoming.enqueue(transport);
|
||||
}
|
||||
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
connection.incoming.enqueue(transport);
|
||||
}
|
||||
|
||||
if (!connection.incoming.empty)
|
||||
{
|
||||
pendings.enqueue(connection);
|
||||
}
|
||||
}
|
||||
if (!connection.incoming.empty)
|
||||
{
|
||||
pendings.enqueue(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ import core.sys.windows.windef;
|
||||
*/
|
||||
class State
|
||||
{
|
||||
/// For internal use by Windows API.
|
||||
align(1) OVERLAPPED overlapped;
|
||||
/// For internal use by Windows API.
|
||||
align(1) OVERLAPPED overlapped;
|
||||
|
||||
/// File/socket handle.
|
||||
HANDLE handle;
|
||||
/// File/socket handle.
|
||||
HANDLE handle;
|
||||
|
||||
/// For keeping events or event masks.
|
||||
int event;
|
||||
/// For keeping events or event masks.
|
||||
int event;
|
||||
}
|
||||
|
@ -15,50 +15,50 @@
|
||||
*
|
||||
* class EchoProtocol : TransmissionControlProtocol
|
||||
* {
|
||||
* private DuplexTransport transport;
|
||||
* private DuplexTransport transport;
|
||||
*
|
||||
* void received(in ubyte[] data) @nogc
|
||||
* {
|
||||
* transport.write(data);
|
||||
* }
|
||||
* void received(in ubyte[] data) @nogc
|
||||
* {
|
||||
* transport.write(data);
|
||||
* }
|
||||
*
|
||||
* void connected(DuplexTransport transport) @nogc
|
||||
* {
|
||||
* this.transport = transport;
|
||||
* }
|
||||
* void connected(DuplexTransport transport) @nogc
|
||||
* {
|
||||
* this.transport = transport;
|
||||
* }
|
||||
*
|
||||
* void disconnected(SocketException e) @nogc
|
||||
* {
|
||||
* }
|
||||
* void disconnected(SocketException e) @nogc
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void main()
|
||||
* {
|
||||
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||
* 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;
|
||||
* }
|
||||
* 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);
|
||||
* sock.bind(address);
|
||||
* sock.listen(5);
|
||||
*
|
||||
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||
* io.setProtocol!EchoProtocol;
|
||||
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||
* io.setProtocol!EchoProtocol;
|
||||
*
|
||||
* defaultLoop.start(io);
|
||||
* defaultLoop.run();
|
||||
* defaultLoop.start(io);
|
||||
* defaultLoop.run();
|
||||
*
|
||||
* sock.shutdown();
|
||||
* defaultAllocator.dispose(io);
|
||||
* defaultAllocator.dispose(sock);
|
||||
* defaultAllocator.dispose(address);
|
||||
* sock.shutdown();
|
||||
* defaultAllocator.dispose(io);
|
||||
* defaultAllocator.dispose(sock);
|
||||
* defaultAllocator.dispose(address);
|
||||
* }
|
||||
* ---
|
||||
*/
|
||||
@ -81,33 +81,33 @@ version (DisableBackends)
|
||||
}
|
||||
else version (linux)
|
||||
{
|
||||
import tanya.async.event.epoll;
|
||||
version = Epoll;
|
||||
import tanya.async.event.epoll;
|
||||
version = Epoll;
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
import tanya.async.event.iocp;
|
||||
version = IOCP;
|
||||
import tanya.async.event.iocp;
|
||||
version = IOCP;
|
||||
}
|
||||
else version (OSX)
|
||||
{
|
||||
version = Kqueue;
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = Kqueue;
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
version = Kqueue;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = Kqueue;
|
||||
version = Kqueue;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,11 +115,11 @@ else version (DragonFlyBSD)
|
||||
*/
|
||||
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.
|
||||
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;
|
||||
@ -129,150 +129,150 @@ alias EventMask = BitFlags!Event;
|
||||
*/
|
||||
abstract class Loop
|
||||
{
|
||||
private bool done;
|
||||
private bool done;
|
||||
|
||||
/// Pending watchers.
|
||||
protected Queue!Watcher pendings;
|
||||
/// Pending watchers.
|
||||
protected Queue!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;
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
pendings = Queue!Watcher(MmapPool.instance);
|
||||
}
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this() @nogc
|
||||
{
|
||||
pendings = Queue!Watcher(MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (w; pendings)
|
||||
{
|
||||
MmapPool.instance.dispose(w);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (w; pendings)
|
||||
{
|
||||
MmapPool.instance.dispose(w);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the loop.
|
||||
*/
|
||||
void run() @nogc
|
||||
{
|
||||
done = false;
|
||||
do
|
||||
{
|
||||
poll();
|
||||
/**
|
||||
* Starts the loop.
|
||||
*/
|
||||
void run() @nogc
|
||||
{
|
||||
done = false;
|
||||
do
|
||||
{
|
||||
poll();
|
||||
|
||||
// Invoke pendings
|
||||
foreach (ref w; pendings)
|
||||
{
|
||||
w.invoke();
|
||||
}
|
||||
}
|
||||
while (!done);
|
||||
}
|
||||
// Invoke pendings
|
||||
foreach (ref w; pendings)
|
||||
{
|
||||
w.invoke();
|
||||
}
|
||||
}
|
||||
while (!done);
|
||||
}
|
||||
|
||||
/**
|
||||
* Break out of the loop.
|
||||
*/
|
||||
void unloop() @safe pure nothrow @nogc
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
/**
|
||||
* Break out of the loop.
|
||||
*/
|
||||
void unloop() @safe pure nothrow @nogc
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start watching.
|
||||
*
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void start(ConnectionWatcher watcher) @nogc
|
||||
{
|
||||
if (watcher.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
watcher.active = 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));
|
||||
}
|
||||
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;
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
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;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
blockTime_ = blockTime;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
blockTime_ = blockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
abstract protected void poll() @nogc;
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
abstract protected void poll() @nogc;
|
||||
|
||||
/// Maximal block time.
|
||||
protected Duration blockTime_ = 1.dur!"minutes";
|
||||
/// Maximal block time.
|
||||
protected Duration blockTime_ = 1.dur!"minutes";
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,17 +280,17 @@ abstract class 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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -302,24 +302,24 @@ class BadLoopException : Exception
|
||||
*/
|
||||
@property Loop defaultLoop() @nogc
|
||||
{
|
||||
if (defaultLoop_ !is null)
|
||||
{
|
||||
return defaultLoop_;
|
||||
}
|
||||
version (Epoll)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
||||
}
|
||||
else version (IOCP)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
||||
}
|
||||
else version (Kqueue)
|
||||
{
|
||||
import tanya.async.event.kqueue;
|
||||
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
||||
}
|
||||
return defaultLoop_;
|
||||
if (defaultLoop_ !is null)
|
||||
{
|
||||
return defaultLoop_;
|
||||
}
|
||||
version (Epoll)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
||||
}
|
||||
else version (IOCP)
|
||||
{
|
||||
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
||||
}
|
||||
else version (Kqueue)
|
||||
{
|
||||
import tanya.async.event.kqueue;
|
||||
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
||||
}
|
||||
return defaultLoop_;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -331,16 +331,16 @@ class BadLoopException : Exception
|
||||
* your implementation to this property.
|
||||
*
|
||||
* Params:
|
||||
* loop = The event loop.
|
||||
* loop = The event loop.
|
||||
*/
|
||||
@property void defaultLoop(Loop loop) @nogc
|
||||
in
|
||||
{
|
||||
assert(loop !is null);
|
||||
assert(loop !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
defaultLoop_ = loop;
|
||||
defaultLoop_ = loop;
|
||||
}
|
||||
|
||||
private Loop defaultLoop_;
|
||||
|
@ -18,28 +18,28 @@ import tanya.async.transport;
|
||||
*/
|
||||
interface Protocol
|
||||
{
|
||||
/**
|
||||
* Params:
|
||||
* data = Read data.
|
||||
*/
|
||||
void received(in ubyte[] data) @nogc;
|
||||
/**
|
||||
* 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 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;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,13 +32,13 @@ interface ReadTransport : Transport
|
||||
*/
|
||||
interface WriteTransport : Transport
|
||||
{
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data) @nogc;
|
||||
/**
|
||||
* Write some data to the transport.
|
||||
*
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,46 +46,46 @@ interface WriteTransport : 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);
|
||||
}
|
||||
/**
|
||||
* 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, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* Params:
|
||||
* protocol = Application protocol.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE protocol !is null)
|
||||
*/
|
||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(protocol !is null);
|
||||
}
|
||||
/**
|
||||
* Switches the protocol.
|
||||
*
|
||||
* The protocol is deallocated by the event loop, it should currently be
|
||||
* allocated with $(D_PSYMBOL MmapPool).
|
||||
*
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Close the transport.
|
||||
*
|
||||
* Buffered data will be flushed. No more data will be received.
|
||||
*/
|
||||
void close() @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -93,8 +93,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
|
||||
*/
|
||||
interface SocketTransport : Transport
|
||||
{
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property Socket socket() pure nothrow @safe @nogc;
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property Socket socket() pure nothrow @safe @nogc;
|
||||
}
|
||||
|
@ -27,13 +27,13 @@ import tanya.network.socket;
|
||||
*/
|
||||
abstract class Watcher
|
||||
{
|
||||
/// Whether the watcher is active.
|
||||
bool active;
|
||||
/// Whether the watcher is active.
|
||||
bool active;
|
||||
|
||||
/**
|
||||
* Invoke some action on event.
|
||||
*/
|
||||
void invoke() @nogc;
|
||||
/**
|
||||
* Invoke some action on event.
|
||||
*/
|
||||
void invoke() @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,32 +41,32 @@ abstract class Watcher
|
||||
*/
|
||||
abstract class SocketWatcher : Watcher
|
||||
{
|
||||
/// Watched socket.
|
||||
protected Socket socket_;
|
||||
/// 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);
|
||||
}
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE socket !is null)
|
||||
*/
|
||||
this(Socket socket) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property Socket socket() pure nothrow @safe @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
/**
|
||||
* Returns: Socket.
|
||||
*/
|
||||
@property Socket socket() pure nothrow @safe @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,44 +74,44 @@ abstract class SocketWatcher : Watcher
|
||||
*/
|
||||
class ConnectionWatcher : SocketWatcher
|
||||
{
|
||||
/// Incoming connection queue.
|
||||
Queue!DuplexTransport incoming;
|
||||
/// Incoming connection queue.
|
||||
Queue!DuplexTransport incoming;
|
||||
|
||||
private Protocol delegate() @nogc protocolFactory;
|
||||
private Protocol delegate() @nogc protocolFactory;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(Socket socket) @nogc
|
||||
{
|
||||
super(socket);
|
||||
incoming = Queue!DuplexTransport(MmapPool.instance);
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(Socket socket) @nogc
|
||||
{
|
||||
super(socket);
|
||||
incoming = Queue!DuplexTransport(MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* P = Protocol should be used.
|
||||
*/
|
||||
void setProtocol(P : Protocol)() @nogc
|
||||
{
|
||||
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* P = Protocol should be used.
|
||||
*/
|
||||
void setProtocol(P : Protocol)() @nogc
|
||||
{
|
||||
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes new connection callback.
|
||||
*/
|
||||
override void invoke() @nogc
|
||||
in
|
||||
{
|
||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||
}
|
||||
body
|
||||
{
|
||||
foreach (transport; incoming)
|
||||
{
|
||||
transport.protocol = protocolFactory();
|
||||
transport.protocol.connected(transport);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Invokes new connection callback.
|
||||
*/
|
||||
override void invoke() @nogc
|
||||
in
|
||||
{
|
||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||
}
|
||||
body
|
||||
{
|
||||
foreach (transport; incoming)
|
||||
{
|
||||
transport.protocol = protocolFactory();
|
||||
transport.protocol.connected(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
1646
source/tanya/container/array.d
Normal file
1646
source/tanya/container/array.d
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -9,14 +9,23 @@
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
*/
|
||||
module tanya.container.entry;
|
||||
|
||||
package struct SEntry(T)
|
||||
{
|
||||
/// Item content.
|
||||
T content;
|
||||
// Item content.
|
||||
T content;
|
||||
|
||||
/// Next item.
|
||||
SEntry* next;
|
||||
// Next item.
|
||||
SEntry* next;
|
||||
}
|
||||
|
||||
package struct DEntry(T)
|
||||
{
|
||||
// Item content.
|
||||
T content;
|
||||
|
||||
// Previous and next item.
|
||||
DEntry* next, prev;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -3,14 +3,17 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Abstract data types whose instances are collections of other objects.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016-2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
*/
|
||||
module tanya.container;
|
||||
|
||||
public import tanya.container.array;
|
||||
public import tanya.container.buffer;
|
||||
public import tanya.container.list;
|
||||
public import tanya.container.vector;
|
||||
public import tanya.container.string;
|
||||
public import tanya.container.queue;
|
||||
|
@ -3,11 +3,13 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* FIFO queue.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016-2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
*/
|
||||
module tanya.container.queue;
|
||||
|
||||
import core.exception;
|
||||
@ -20,267 +22,267 @@ import tanya.memory;
|
||||
* FIFO queue.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
* T = Content type.
|
||||
*/
|
||||
struct Queue(T)
|
||||
{
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
while (!empty)
|
||||
{
|
||||
dequeue();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
while (!empty)
|
||||
{
|
||||
dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many elements are in the queue. It iterates through the queue
|
||||
* to count the elements.
|
||||
*
|
||||
* Returns: How many elements are in the queue.
|
||||
*/
|
||||
size_t length() const
|
||||
{
|
||||
size_t len;
|
||||
for (const(SEntry!T)* i = first; i !is null; i = i.next)
|
||||
{
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
/**
|
||||
* Returns how many elements are in the queue. It iterates through the queue
|
||||
* to count the elements.
|
||||
*
|
||||
* Returns: How many elements are in the queue.
|
||||
*/
|
||||
size_t length() const
|
||||
{
|
||||
size_t len;
|
||||
for (const(SEntry!T)* i = first; i !is null; i = i.next)
|
||||
{
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
|
||||
assert(q.length == 0);
|
||||
q.enqueue(5);
|
||||
assert(q.length == 1);
|
||||
q.enqueue(4);
|
||||
assert(q.length == 2);
|
||||
q.enqueue(9);
|
||||
assert(q.length == 3);
|
||||
assert(q.length == 0);
|
||||
q.enqueue(5);
|
||||
assert(q.length == 1);
|
||||
q.enqueue(4);
|
||||
assert(q.length == 2);
|
||||
q.enqueue(9);
|
||||
assert(q.length == 3);
|
||||
|
||||
q.dequeue();
|
||||
assert(q.length == 2);
|
||||
q.dequeue();
|
||||
assert(q.length == 1);
|
||||
q.dequeue();
|
||||
assert(q.length == 0);
|
||||
}
|
||||
q.dequeue();
|
||||
assert(q.length == 2);
|
||||
q.dequeue();
|
||||
assert(q.length == 1);
|
||||
q.dequeue();
|
||||
assert(q.length == 0);
|
||||
}
|
||||
|
||||
private void enqueueEntry(ref SEntry!T* entry)
|
||||
{
|
||||
if (empty)
|
||||
{
|
||||
first = rear = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
rear.next = entry;
|
||||
rear = rear.next;
|
||||
}
|
||||
}
|
||||
private void enqueueEntry(ref SEntry!T* entry)
|
||||
{
|
||||
if (empty)
|
||||
{
|
||||
first = rear = entry;
|
||||
}
|
||||
else
|
||||
{
|
||||
rear.next = entry;
|
||||
rear = rear.next;
|
||||
}
|
||||
}
|
||||
|
||||
private SEntry!T* allocateEntry()
|
||||
{
|
||||
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
|
||||
if (temp is null)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
private SEntry!T* allocateEntry()
|
||||
{
|
||||
auto temp = cast(SEntry!T*) allocator.allocate(SEntry!T.sizeof);
|
||||
if (temp is null)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
void enqueue(ref T x)
|
||||
{
|
||||
auto temp = allocateEntry();
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
void enqueue(ref T x)
|
||||
{
|
||||
auto temp = allocateEntry();
|
||||
|
||||
*temp = SEntry!T.init;
|
||||
temp.content = x;
|
||||
*temp = SEntry!T.init;
|
||||
temp.content = x;
|
||||
|
||||
enqueueEntry(temp);
|
||||
}
|
||||
enqueueEntry(temp);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void enqueue(T x)
|
||||
{
|
||||
auto temp = allocateEntry();
|
||||
/// Ditto.
|
||||
void enqueue(T x)
|
||||
{
|
||||
auto temp = allocateEntry();
|
||||
|
||||
moveEmplace(x, (*temp).content);
|
||||
(*temp).next = null;
|
||||
moveEmplace(x, (*temp).content);
|
||||
(*temp).next = null;
|
||||
|
||||
enqueueEntry(temp);
|
||||
}
|
||||
enqueueEntry(temp);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
|
||||
assert(q.empty);
|
||||
q.enqueue(8);
|
||||
q.enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
assert(q.empty);
|
||||
q.enqueue(8);
|
||||
q.enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() const
|
||||
{
|
||||
return first is null;
|
||||
}
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() const
|
||||
{
|
||||
return first is null;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
int value = 7;
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
int value = 7;
|
||||
|
||||
assert(q.empty);
|
||||
q.enqueue(value);
|
||||
assert(!q.empty);
|
||||
}
|
||||
assert(q.empty);
|
||||
q.enqueue(value);
|
||||
assert(!q.empty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the position to the next element.
|
||||
*
|
||||
* Returns: Dequeued element.
|
||||
*/
|
||||
T dequeue()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next;
|
||||
T ret = move(first.content);
|
||||
/**
|
||||
* Move the position to the next element.
|
||||
*
|
||||
* Returns: Dequeued element.
|
||||
*/
|
||||
T dequeue()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next;
|
||||
T ret = move(first.content);
|
||||
|
||||
allocator.dispose(first);
|
||||
first = n;
|
||||
return ret;
|
||||
}
|
||||
allocator.dispose(first);
|
||||
first = n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
|
||||
q.enqueue(8);
|
||||
q.enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
q.enqueue(8);
|
||||
q.enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_KEYWORD foreach) iteration. The elements will be automatically
|
||||
* dequeued.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*
|
||||
* Returns: The value returned from $(D_PARAM dg).
|
||||
*/
|
||||
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
/**
|
||||
* $(D_KEYWORD foreach) iteration. The elements will be automatically
|
||||
* dequeued.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*
|
||||
* Returns: The value returned from $(D_PARAM dg).
|
||||
*/
|
||||
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
for (size_t i = 0; !empty; ++i)
|
||||
{
|
||||
auto e = dequeue();
|
||||
if ((result = dg(i, e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
for (size_t i = 0; !empty; ++i)
|
||||
{
|
||||
auto e = dequeue();
|
||||
if ((result = dg(i, e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(scope int delegate(ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
/// Ditto.
|
||||
int opApply(scope int delegate(ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
while (!empty)
|
||||
{
|
||||
auto e = dequeue();
|
||||
if ((result = dg(e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
while (!empty)
|
||||
{
|
||||
auto e = dequeue();
|
||||
if ((result = dg(e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
|
||||
size_t j;
|
||||
q.enqueue(5);
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
foreach (i, e; q)
|
||||
{
|
||||
assert(i != 2 || e == 9);
|
||||
assert(i != 1 || e == 4);
|
||||
assert(i != 0 || e == 5);
|
||||
++j;
|
||||
}
|
||||
assert(j == 3);
|
||||
assert(q.empty);
|
||||
size_t j;
|
||||
q.enqueue(5);
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
foreach (i, e; q)
|
||||
{
|
||||
assert(i != 2 || e == 9);
|
||||
assert(i != 1 || e == 4);
|
||||
assert(i != 0 || e == 5);
|
||||
++j;
|
||||
}
|
||||
assert(j == 3);
|
||||
assert(q.empty);
|
||||
|
||||
j = 0;
|
||||
q.enqueue(5);
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
foreach (e; q)
|
||||
{
|
||||
assert(j != 2 || e == 9);
|
||||
assert(j != 1 || e == 4);
|
||||
assert(j != 0 || e == 5);
|
||||
++j;
|
||||
}
|
||||
assert(j == 3);
|
||||
assert(q.empty);
|
||||
}
|
||||
j = 0;
|
||||
q.enqueue(5);
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
foreach (e; q)
|
||||
{
|
||||
assert(j != 2 || e == 9);
|
||||
assert(j != 1 || e == 4);
|
||||
assert(j != 0 || e == 5);
|
||||
++j;
|
||||
}
|
||||
assert(j == 3);
|
||||
assert(q.empty);
|
||||
}
|
||||
|
||||
private SEntry!T* first;
|
||||
private SEntry!T* rear;
|
||||
private SEntry!T* first;
|
||||
private SEntry!T* rear;
|
||||
|
||||
mixin DefaultAllocator;
|
||||
mixin DefaultAllocator;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
Queue!int q;
|
||||
Queue!int q;
|
||||
|
||||
q.enqueue(5);
|
||||
assert(!q.empty);
|
||||
q.enqueue(5);
|
||||
assert(!q.empty);
|
||||
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
q.enqueue(4);
|
||||
q.enqueue(9);
|
||||
|
||||
assert(q.dequeue() == 5);
|
||||
assert(q.dequeue() == 5);
|
||||
|
||||
foreach (i, ref e; q)
|
||||
{
|
||||
assert(i != 0 || e == 4);
|
||||
assert(i != 1 || e == 9);
|
||||
}
|
||||
assert(q.empty);
|
||||
foreach (i, ref e; q)
|
||||
{
|
||||
assert(i != 0 || e == 4);
|
||||
assert(i != 1 || e == 9);
|
||||
}
|
||||
assert(q.empty);
|
||||
}
|
||||
|
1517
source/tanya/container/string.d
Normal file
1517
source/tanya/container/string.d
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,11 +3,11 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* Copyright: Eugene Wissner 2016-2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.math;
|
||||
|
||||
import std.traits;
|
||||
@ -16,7 +16,7 @@ public import tanya.math.random;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
import std.algorithm.iteration;
|
||||
import std.algorithm.iteration;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -26,12 +26,12 @@ version (unittest)
|
||||
* is used to allocate the result.
|
||||
*
|
||||
* Params:
|
||||
* I = Base type.
|
||||
* G = Exponent type.
|
||||
* H = Divisor type:
|
||||
* x = Base.
|
||||
* y = Exponent.
|
||||
* z = Divisor.
|
||||
* 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).
|
||||
@ -39,134 +39,163 @@ version (unittest)
|
||||
* 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)
|
||||
if (isIntegral!I && isIntegral!G && isIntegral!H)
|
||||
in
|
||||
{
|
||||
assert(z > 0, "Division by zero.");
|
||||
assert(z > 0, "Division by zero.");
|
||||
}
|
||||
body
|
||||
{
|
||||
G mask = G.max / 2 + 1;
|
||||
H result;
|
||||
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;
|
||||
}
|
||||
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);
|
||||
result *= result;
|
||||
if (bit)
|
||||
{
|
||||
result *= x;
|
||||
}
|
||||
result %= z;
|
||||
}
|
||||
while (mask >>= 1);
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
|
||||
if (is(I == Integer))
|
||||
I pow(I)(const auto ref I x, const auto ref I y, const auto ref I z)
|
||||
if (is(I == Integer))
|
||||
in
|
||||
{
|
||||
assert(z.length > 0, "Division by zero.");
|
||||
assert(z.length > 0, "Division by zero.");
|
||||
}
|
||||
body
|
||||
{
|
||||
size_t i = y.length;
|
||||
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
|
||||
Integer result = Integer(x.allocator);
|
||||
size_t i;
|
||||
auto tmp1 = Integer(x, x.allocator);
|
||||
auto result = Integer(x.allocator);
|
||||
bool firstBit;
|
||||
|
||||
if (x.length == 0 && i != 0)
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
while (i)
|
||||
{
|
||||
--i;
|
||||
for (ubyte mask = 0x01; mask; mask <<= 1)
|
||||
{
|
||||
if (y.rep[i] & mask)
|
||||
{
|
||||
result *= tmp1;
|
||||
result %= z;
|
||||
}
|
||||
tmp2 = tmp1;
|
||||
tmp1 *= tmp2;
|
||||
tmp1 %= z;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
if (x.size == 0 && y.size != 0)
|
||||
{
|
||||
i = y.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
while (i < y.size)
|
||||
{
|
||||
for (uint mask = 0x01; mask != 0x10000000; mask <<= 1)
|
||||
{
|
||||
if (y.rep[i] & mask)
|
||||
{
|
||||
result *= tmp1;
|
||||
result %= z;
|
||||
}
|
||||
auto tmp2 = tmp1;
|
||||
tmp1 *= tmp2;
|
||||
tmp1 %= z;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
pure nothrow @safe @nogc unittest
|
||||
{
|
||||
assert(pow(3, 5, 7) == 5);
|
||||
assert(pow(2, 2, 1) == 0);
|
||||
assert(pow(3, 3, 3) == 0);
|
||||
assert(pow(7, 4, 2) == 1);
|
||||
assert(pow(53, 0, 2) == 1);
|
||||
assert(pow(53, 1, 3) == 2);
|
||||
assert(pow(53, 2, 5) == 4);
|
||||
assert(pow(0, 0, 5) == 1);
|
||||
assert(pow(0, 5, 5) == 0);
|
||||
assert(pow(3, 5, 7) == 5);
|
||||
assert(pow(2, 2, 1) == 0);
|
||||
assert(pow(3, 3, 3) == 0);
|
||||
assert(pow(7, 4, 2) == 1);
|
||||
assert(pow(53, 0, 2) == 1);
|
||||
assert(pow(53, 1, 3) == 2);
|
||||
assert(pow(53, 2, 5) == 4);
|
||||
assert(pow(0, 0, 5) == 1);
|
||||
assert(pow(0, 5, 5) == 0);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
|
||||
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
|
||||
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
|
||||
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
|
||||
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
|
||||
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
|
||||
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
|
||||
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
|
||||
assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0);
|
||||
assert(pow(Integer(3), Integer(5), Integer(7)) == 5);
|
||||
assert(pow(Integer(2), Integer(2), Integer(1)) == 0);
|
||||
assert(pow(Integer(3), Integer(3), Integer(3)) == 0);
|
||||
assert(pow(Integer(7), Integer(4), Integer(2)) == 1);
|
||||
assert(pow(Integer(53), Integer(0), Integer(2)) == 1);
|
||||
assert(pow(Integer(53), Integer(1), Integer(3)) == 2);
|
||||
assert(pow(Integer(53), Integer(2), Integer(5)) == 4);
|
||||
assert(pow(Integer(0), Integer(0), Integer(5)) == 1);
|
||||
assert(pow(Integer(0), Integer(5), Integer(5)) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $(D_PARAM x) is a prime.
|
||||
*
|
||||
* Params:
|
||||
* x = The number should be checked.
|
||||
* x = The number should be checked.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if $(D_PARAM x) is a prime number,
|
||||
* $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool isPseudoprime(ulong x) nothrow pure @safe @nogc
|
||||
{
|
||||
return pow(2, x - 1, x) == 1;
|
||||
return pow(2, x - 1, x) == 1;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
|
||||
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
|
||||
74827, 9973, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 899809363, 982451653];
|
||||
uint[30] known = [74623, 74653, 74687, 74699, 74707, 74713, 74717, 74719,
|
||||
74843, 74747, 74759, 74761, 74771, 74779, 74797, 74821,
|
||||
74827, 9973, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 104729, 15485867, 49979693, 104395303,
|
||||
593441861, 899809363, 982451653];
|
||||
|
||||
known.each!((ref x) => assert(isPseudoprime(x)));
|
||||
known.each!((ref x) => assert(isPseudoprime(x)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* I = Value type.
|
||||
* x = Value.
|
||||
*
|
||||
* Returns: The absolute value of a number.
|
||||
*/
|
||||
I abs(I : Integer)(const auto ref I x)
|
||||
{
|
||||
auto result = Integer(x, x.allocator);
|
||||
result.sign = Sign.positive;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I abs(I : Integer)(I x)
|
||||
{
|
||||
x.sign = Sign.positive;
|
||||
return x;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I abs(I)(const I x)
|
||||
if (isIntegral!I)
|
||||
{
|
||||
return x >= 0 ? x : -x;
|
||||
}
|
||||
|
@ -8,8 +8,8 @@
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.math.random;
|
||||
|
||||
import std.digest.sha;
|
||||
@ -27,20 +27,20 @@ enum maxGather = 128;
|
||||
*/
|
||||
class EntropyException : Exception
|
||||
{
|
||||
/**
|
||||
* Params:
|
||||
* msg = Message to output.
|
||||
* file = The file where the exception occurred.
|
||||
* line = The line number where the exception occurred.
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string msg,
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__,
|
||||
Throwable next = null) pure @safe nothrow const @nogc
|
||||
{
|
||||
super(msg, file, line, next);
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* msg = Message to output.
|
||||
* file = The file where the exception occurred.
|
||||
* line = The line number where the exception occurred.
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string msg,
|
||||
string file = __FILE__,
|
||||
size_t line = __LINE__,
|
||||
Throwable next = null) pure @safe nothrow const @nogc
|
||||
{
|
||||
super(msg, file, line, next);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,103 +48,103 @@ class EntropyException : Exception
|
||||
*/
|
||||
abstract class EntropySource
|
||||
{
|
||||
/// Amount of already generated entropy.
|
||||
protected ushort size_;
|
||||
/// Amount of already generated entropy.
|
||||
protected ushort size_;
|
||||
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
@property immutable(ubyte) threshold() const @safe pure nothrow;
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
@property immutable(ubyte) threshold() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
@property immutable(bool) strong() const @safe pure nothrow;
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
@property immutable(bool) strong() const @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Amount of already generated entropy.
|
||||
*/
|
||||
@property ushort size() const @safe pure nothrow
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
/**
|
||||
* Returns: Amount of already generated entropy.
|
||||
*/
|
||||
@property ushort size() const @safe pure nothrow
|
||||
{
|
||||
return size_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Amount of already generated entropy. Cannot be smaller than the
|
||||
* already set value.
|
||||
*/
|
||||
@property void size(ushort size) @safe pure nothrow
|
||||
{
|
||||
size_ = size;
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* size = Amount of already generated entropy. Cannot be smaller than the
|
||||
* already set value.
|
||||
*/
|
||||
@property void size(ushort size) @safe pure nothrow
|
||||
{
|
||||
size_ = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
Nullable!ubyte poll(out ubyte[maxGather] output);
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
Nullable!ubyte poll(out ubyte[maxGather] output);
|
||||
}
|
||||
|
||||
version (linux)
|
||||
{
|
||||
extern (C) long syscall(long number, ...) nothrow;
|
||||
extern (C) long syscall(long number, ...) nothrow;
|
||||
|
||||
/**
|
||||
* Uses getrandom system call.
|
||||
*/
|
||||
class PlatformEntropySource : EntropySource
|
||||
{
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
override @property immutable(ubyte) threshold() const @safe pure nothrow
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
/**
|
||||
* Uses getrandom system call.
|
||||
*/
|
||||
class PlatformEntropySource : EntropySource
|
||||
{
|
||||
/**
|
||||
* Returns: Minimum bytes required from the entropy source.
|
||||
*/
|
||||
override @property immutable(ubyte) threshold() const @safe pure nothrow
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
override @property immutable(bool) strong() const @safe pure nothrow
|
||||
{
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Returns: Whether this entropy source is strong.
|
||||
*/
|
||||
override @property immutable(bool) strong() const @safe pure nothrow
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
|
||||
out (length)
|
||||
{
|
||||
assert(length <= maxGather);
|
||||
}
|
||||
body
|
||||
{
|
||||
// int getrandom(void *buf, size_t buflen, unsigned int flags);
|
||||
auto length = syscall(318, output.ptr, output.length, 0);
|
||||
Nullable!ubyte ret;
|
||||
/**
|
||||
* Poll the entropy source.
|
||||
*
|
||||
* Params:
|
||||
* output = Buffer to save the generate random sequence (the method will
|
||||
* to fill the buffer).
|
||||
*
|
||||
* Returns: Number of bytes that were copied to the $(D_PARAM output)
|
||||
* or $(D_PSYMBOL Nullable!ubyte.init) on error.
|
||||
*/
|
||||
override Nullable!ubyte poll(out ubyte[maxGather] output) nothrow
|
||||
out (length)
|
||||
{
|
||||
assert(length <= maxGather);
|
||||
}
|
||||
body
|
||||
{
|
||||
// int getrandom(void *buf, size_t buflen, unsigned int flags);
|
||||
auto length = syscall(318, output.ptr, output.length, 0);
|
||||
Nullable!ubyte ret;
|
||||
|
||||
if (length >= 0)
|
||||
{
|
||||
ret = cast(ubyte) length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
if (length >= 0)
|
||||
{
|
||||
ret = cast(ubyte) length;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -156,165 +156,165 @@ version (linux)
|
||||
*
|
||||
* output = entropy.random;
|
||||
*
|
||||
* defaultAllocator.finalize(entropy);
|
||||
* defaultAllocator.dispose(entropy);
|
||||
* ---
|
||||
*/
|
||||
class Entropy
|
||||
{
|
||||
/// Entropy sources.
|
||||
protected EntropySource[] sources;
|
||||
/// Entropy sources.
|
||||
protected EntropySource[] sources;
|
||||
|
||||
private ubyte sourceCount_;
|
||||
private ubyte sourceCount_;
|
||||
|
||||
private shared Allocator allocator;
|
||||
private shared Allocator allocator;
|
||||
|
||||
/// Entropy accumulator.
|
||||
protected SHA!(maxGather * 8, 512) accumulator;
|
||||
/// Entropy accumulator.
|
||||
protected SHA!(maxGather * 8, 512) accumulator;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* maxSources = Maximum amount of entropy sources can be set.
|
||||
* allocator = Allocator to allocate entropy sources available on the
|
||||
* system.
|
||||
*/
|
||||
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator.resizeArray(sources, maxSources);
|
||||
/**
|
||||
* Params:
|
||||
* maxSources = Maximum amount of entropy sources can be set.
|
||||
* allocator = Allocator to allocate entropy sources available on the
|
||||
* system.
|
||||
*/
|
||||
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator.resize(sources, maxSources);
|
||||
|
||||
version (linux)
|
||||
{
|
||||
this ~= allocator.make!PlatformEntropySource;
|
||||
}
|
||||
}
|
||||
version (linux)
|
||||
{
|
||||
this ~= allocator.make!PlatformEntropySource;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Amount of the registered entropy sources.
|
||||
*/
|
||||
@property ubyte sourceCount() const @safe pure nothrow
|
||||
{
|
||||
return sourceCount_;
|
||||
}
|
||||
/**
|
||||
* Returns: Amount of the registered entropy sources.
|
||||
*/
|
||||
@property ubyte sourceCount() const @safe pure nothrow
|
||||
{
|
||||
return sourceCount_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entropy source.
|
||||
*
|
||||
* Params:
|
||||
* source = Entropy source.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL this).
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL EntropySource)
|
||||
*/
|
||||
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
|
||||
if (Op == "~")
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ <= sources.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
sources[sourceCount_++] = source;
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Add an entropy source.
|
||||
*
|
||||
* Params:
|
||||
* source = Entropy source.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL this).
|
||||
*
|
||||
* See_Also:
|
||||
* $(D_PSYMBOL EntropySource)
|
||||
*/
|
||||
Entropy opOpAssign(string Op)(EntropySource source) @safe pure nothrow
|
||||
if (Op == "~")
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ <= sources.length);
|
||||
}
|
||||
body
|
||||
{
|
||||
sources[sourceCount_++] = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Generated random sequence.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
|
||||
* registered or it failed.
|
||||
*/
|
||||
@property ubyte[blockSize] random()
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ > 0, "No entropy sources defined.");
|
||||
}
|
||||
body
|
||||
{
|
||||
bool haveStrong;
|
||||
ushort done;
|
||||
ubyte[blockSize] output;
|
||||
/**
|
||||
* Returns: Generated random sequence.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL EntropyException) if no strong entropy source was
|
||||
* registered or it failed.
|
||||
*/
|
||||
@property ubyte[blockSize] random()
|
||||
in
|
||||
{
|
||||
assert(sourceCount_ > 0, "No entropy sources defined.");
|
||||
}
|
||||
body
|
||||
{
|
||||
bool haveStrong;
|
||||
ushort done;
|
||||
ubyte[blockSize] output;
|
||||
|
||||
do
|
||||
{
|
||||
ubyte[maxGather] buffer;
|
||||
do
|
||||
{
|
||||
ubyte[maxGather] buffer;
|
||||
|
||||
// Run through our entropy sources
|
||||
for (ubyte i; i < sourceCount; ++i)
|
||||
{
|
||||
auto outputLength = sources[i].poll(buffer);
|
||||
// Run through our entropy sources
|
||||
for (ubyte i; i < sourceCount; ++i)
|
||||
{
|
||||
auto outputLength = sources[i].poll(buffer);
|
||||
|
||||
if (!outputLength.isNull)
|
||||
{
|
||||
if (outputLength > 0)
|
||||
{
|
||||
update(i, buffer, outputLength);
|
||||
sources[i].size = cast(ushort) (sources[i].size + outputLength);
|
||||
}
|
||||
if (sources[i].size < sources[i].threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (sources[i].strong)
|
||||
{
|
||||
haveStrong = true;
|
||||
}
|
||||
}
|
||||
done = 257;
|
||||
}
|
||||
}
|
||||
while (++done < 256);
|
||||
if (!outputLength.isNull)
|
||||
{
|
||||
if (outputLength > 0)
|
||||
{
|
||||
update(i, buffer, outputLength);
|
||||
sources[i].size = cast(ushort) (sources[i].size + outputLength);
|
||||
}
|
||||
if (sources[i].size < sources[i].threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (sources[i].strong)
|
||||
{
|
||||
haveStrong = true;
|
||||
}
|
||||
}
|
||||
done = 257;
|
||||
}
|
||||
}
|
||||
while (++done < 256);
|
||||
|
||||
if (!haveStrong)
|
||||
{
|
||||
throw allocator.make!EntropyException("No strong entropy source defined.");
|
||||
}
|
||||
if (!haveStrong)
|
||||
{
|
||||
throw allocator.make!EntropyException("No strong entropy source defined.");
|
||||
}
|
||||
|
||||
output = accumulator.finish();
|
||||
output = accumulator.finish();
|
||||
|
||||
// Reset accumulator and counters and recycle existing entropy
|
||||
accumulator.start();
|
||||
// Reset accumulator and counters and recycle existing entropy
|
||||
accumulator.start();
|
||||
|
||||
// Perform second SHA-512 on entropy
|
||||
output = sha512Of(output);
|
||||
// Perform second SHA-512 on entropy
|
||||
output = sha512Of(output);
|
||||
|
||||
for (ubyte i = 0; i < sourceCount; ++i)
|
||||
{
|
||||
sources[i].size = 0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
for (ubyte i = 0; i < sourceCount; ++i)
|
||||
{
|
||||
sources[i].size = 0;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entropy accumulator.
|
||||
*
|
||||
* Params:
|
||||
* sourceId = Entropy source index in $(D_PSYMBOL sources).
|
||||
* data = Data got from the entropy source.
|
||||
* length = Length of the received data.
|
||||
*/
|
||||
protected void update(in ubyte sourceId,
|
||||
ref ubyte[maxGather] data,
|
||||
ubyte length) @safe pure nothrow
|
||||
{
|
||||
ubyte[2] header;
|
||||
/**
|
||||
* Update entropy accumulator.
|
||||
*
|
||||
* Params:
|
||||
* sourceId = Entropy source index in $(D_PSYMBOL sources).
|
||||
* data = Data got from the entropy source.
|
||||
* length = Length of the received data.
|
||||
*/
|
||||
protected void update(in ubyte sourceId,
|
||||
ref ubyte[maxGather] data,
|
||||
ubyte length) @safe pure nothrow
|
||||
{
|
||||
ubyte[2] header;
|
||||
|
||||
if (length > blockSize)
|
||||
{
|
||||
data[0..64] = sha512Of(data);
|
||||
length = blockSize;
|
||||
}
|
||||
if (length > blockSize)
|
||||
{
|
||||
data[0..64] = sha512Of(data);
|
||||
length = blockSize;
|
||||
}
|
||||
|
||||
header[0] = sourceId;
|
||||
header[1] = length;
|
||||
header[0] = sourceId;
|
||||
header[1] = length;
|
||||
|
||||
accumulator.put(header);
|
||||
accumulator.put(data[0..length]);
|
||||
}
|
||||
accumulator.put(header);
|
||||
accumulator.put(data[0..length]);
|
||||
}
|
||||
}
|
||||
|
@ -15,53 +15,53 @@ module tanya.memory.allocator;
|
||||
*/
|
||||
interface Allocator
|
||||
{
|
||||
/**
|
||||
* Returns: Alignment offered.
|
||||
*/
|
||||
@property uint alignment() const shared pure nothrow @safe @nogc;
|
||||
/**
|
||||
* Returns: Alignment offered.
|
||||
*/
|
||||
@property uint alignment() const shared pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: Pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(in size_t size) shared nothrow @nogc;
|
||||
/**
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: Pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(in size_t size) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) shared nothrow @nogc;
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: Pointer to the allocated memory.
|
||||
*/
|
||||
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: Pointer to the allocated memory.
|
||||
*/
|
||||
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Reallocates a memory block in place if possible or returns
|
||||
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
/**
|
||||
* Reallocates a memory block in place if possible or returns
|
||||
* $(D_KEYWORD false). This function cannot be used to allocate or
|
||||
* deallocate memory, so if $(D_PARAM p) is $(D_KEYWORD null) or
|
||||
* $(D_PARAM size) is `0`, it should return $(D_KEYWORD false).
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool reallocateInPlace(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
}
|
||||
|
185
source/tanya/memory/mallocator.d
Normal file
185
source/tanya/memory/mallocator.d
Normal file
@ -0,0 +1,185 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.memory.mallocator;
|
||||
|
||||
import core.stdc.stdlib;
|
||||
import std.algorithm.comparison;
|
||||
import tanya.memory.allocator;
|
||||
|
||||
/**
|
||||
* Wrapper for malloc/realloc/free from the C standard library.
|
||||
*/
|
||||
final class Mallocator : Allocator
|
||||
{
|
||||
/**
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(in size_t size) shared nothrow @nogc
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
auto p = malloc(size + psize);
|
||||
|
||||
return p is null ? null : p[psize .. psize + size];
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
auto p = Mallocator.instance.allocate(20);
|
||||
|
||||
assert(p.length == 20);
|
||||
|
||||
Mallocator.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) shared nothrow @nogc
|
||||
{
|
||||
if (p !is null)
|
||||
{
|
||||
free(p.ptr - psize);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
assert(Mallocator.instance.deallocate(p));
|
||||
|
||||
p = Mallocator.instance.allocate(10);
|
||||
assert(Mallocator.instance.deallocate(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reallocating in place isn't supported.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD false).
|
||||
*/
|
||||
bool reallocateInPlace(ref void[] p, const size_t size) shared nothrow @nogc
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: Whether the reallocation was successful.
|
||||
*/
|
||||
bool reallocate(ref void[] p, const size_t size) shared nothrow @nogc
|
||||
{
|
||||
if (size == 0)
|
||||
{
|
||||
if (deallocate(p))
|
||||
{
|
||||
p = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (p is null)
|
||||
{
|
||||
p = allocate(size);
|
||||
return p is null ? false : true;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto r = realloc(p.ptr - psize, size + psize);
|
||||
|
||||
if (r !is null)
|
||||
{
|
||||
p = r[psize .. psize + size];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
|
||||
assert(Mallocator.instance.reallocate(p, 20));
|
||||
assert(p.length == 20);
|
||||
|
||||
assert(Mallocator.instance.reallocate(p, 30));
|
||||
assert(p.length == 30);
|
||||
|
||||
assert(Mallocator.instance.reallocate(p, 10));
|
||||
assert(p.length == 10);
|
||||
|
||||
assert(Mallocator.instance.reallocate(p, 0));
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The alignment offered.
|
||||
*/
|
||||
@property uint alignment() shared const pure nothrow @safe @nogc
|
||||
{
|
||||
return cast(uint) max(double.alignof, real.alignof);
|
||||
}
|
||||
|
||||
/**
|
||||
* Static allocator instance and initializer.
|
||||
*
|
||||
* Returns: The global $(D_PSYMBOL Allocator) instance.
|
||||
*/
|
||||
static @property ref shared(Mallocator) instance() @nogc nothrow
|
||||
{
|
||||
if (instance_ is null)
|
||||
{
|
||||
immutable size = __traits(classInstanceSize, Mallocator) + psize;
|
||||
void* p = malloc(size);
|
||||
|
||||
if (p !is null)
|
||||
{
|
||||
p[psize .. size] = typeid(Mallocator).initializer[];
|
||||
instance_ = cast(shared Mallocator) p[psize .. size].ptr;
|
||||
}
|
||||
}
|
||||
return instance_;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
{
|
||||
assert(instance is instance);
|
||||
}
|
||||
|
||||
private enum psize = 8;
|
||||
|
||||
private shared static Mallocator instance_;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,8 @@
|
||||
module tanya.memory;
|
||||
|
||||
import core.exception;
|
||||
public import std.experimental.allocator : make, makeArray;
|
||||
import std.algorithm.iteration;
|
||||
public import std.experimental.allocator : make;
|
||||
import std.traits;
|
||||
public import tanya.memory.allocator;
|
||||
|
||||
@ -23,59 +24,61 @@ public import tanya.memory.allocator;
|
||||
*/
|
||||
mixin template DefaultAllocator()
|
||||
{
|
||||
/// Allocator.
|
||||
protected shared Allocator allocator_;
|
||||
/// Allocator.
|
||||
protected shared Allocator allocator_;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* allocator = The allocator should be used.
|
||||
*/
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator_ = allocator;
|
||||
}
|
||||
/**
|
||||
* Params:
|
||||
* allocator = The allocator should be used.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator_ !is null)
|
||||
*/
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator_ = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* This property checks if the allocator was set in the constructor
|
||||
* and sets it to the default one, if not.
|
||||
*
|
||||
* Returns: Used allocator.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE allocator_ !is null)
|
||||
*/
|
||||
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
allocator_ = defaultAllocator;
|
||||
}
|
||||
return allocator_;
|
||||
}
|
||||
/**
|
||||
* This property checks if the allocator was set in the constructor
|
||||
* and sets it to the default one, if not.
|
||||
*
|
||||
* Returns: Used allocator.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
allocator_ = defaultAllocator;
|
||||
}
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
@property shared(Allocator) allocator() const nothrow @trusted @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
return defaultAllocator;
|
||||
}
|
||||
return cast(shared Allocator) allocator_;
|
||||
}
|
||||
/// Ditto.
|
||||
@property shared(Allocator) allocator() const nothrow @trusted @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
return defaultAllocator;
|
||||
}
|
||||
return cast(shared Allocator) allocator_;
|
||||
}
|
||||
}
|
||||
|
||||
// From druntime
|
||||
@ -85,28 +88,28 @@ shared Allocator allocator;
|
||||
|
||||
shared static this() nothrow @trusted @nogc
|
||||
{
|
||||
import tanya.memory.mmappool;
|
||||
allocator = MmapPool.instance;
|
||||
import tanya.memory.mmappool;
|
||||
allocator = MmapPool.instance;
|
||||
}
|
||||
|
||||
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
return allocator;
|
||||
return allocator;
|
||||
}
|
||||
|
||||
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
.allocator = allocator;
|
||||
.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -114,101 +117,166 @@ body
|
||||
* object of type $(D_PARAM T).
|
||||
*
|
||||
* Params:
|
||||
* T = Object type.
|
||||
* T = Object type.
|
||||
*/
|
||||
template stateSize(T)
|
||||
{
|
||||
static if (is(T == class) || is(T == interface))
|
||||
{
|
||||
enum stateSize = __traits(classInstanceSize, T);
|
||||
}
|
||||
else
|
||||
{
|
||||
enum stateSize = T.sizeof;
|
||||
}
|
||||
static if (is(T == class) || is(T == interface))
|
||||
{
|
||||
enum stateSize = __traits(classInstanceSize, T);
|
||||
}
|
||||
else
|
||||
{
|
||||
enum stateSize = T.sizeof;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Raw size.
|
||||
* alignment = Alignment.
|
||||
* size = Raw size.
|
||||
* alignment = Alignment.
|
||||
*
|
||||
* Returns: Aligned size.
|
||||
*/
|
||||
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
|
||||
size_t alignedSize(const size_t size, const size_t alignment = 8)
|
||||
pure nothrow @safe @nogc
|
||||
{
|
||||
return (size - 1) / alignment * alignment + alignment;
|
||||
return (size - 1) / alignment * alignment + alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used to create, resize or destroy a dynamic array. It
|
||||
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
|
||||
* allocated part of the array is initialized only if $(D_PARAM Init)
|
||||
* is set. This function can be trusted only in the data structures that
|
||||
* can ensure that the array is allocated/rellocated/deallocated with the
|
||||
* same allocator.
|
||||
* may throw $(D_PSYMBOL OutOfMemoryError). The new
|
||||
* allocated part of the array isn't initialized. This function can be trusted
|
||||
* only in the data structures that can ensure that the array is
|
||||
* allocated/rellocated/deallocated with the same allocator.
|
||||
*
|
||||
* Params:
|
||||
* T = Element type of the array being created.
|
||||
* Init = If should be initialized.
|
||||
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
|
||||
* allocator = The allocator used for getting memory.
|
||||
* array = A reference to the array being changed.
|
||||
* length = New array length.
|
||||
* T = Element type of the array being created.
|
||||
* allocator = The allocator used for getting memory.
|
||||
* array = A reference to the array being changed.
|
||||
* length = New array length.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
|
||||
* not be reallocated. In the latter
|
||||
* Returns: $(D_PARAM array).
|
||||
*/
|
||||
package(tanya) bool resize(T,
|
||||
bool Init = true,
|
||||
bool Throws = true)
|
||||
(shared Allocator allocator,
|
||||
ref T[] array,
|
||||
in size_t length) @trusted
|
||||
package(tanya) T[] resize(T)(shared Allocator allocator,
|
||||
auto ref T[] array,
|
||||
const size_t length) @trusted
|
||||
{
|
||||
void[] buf = array;
|
||||
static if (Init)
|
||||
{
|
||||
immutable oldLength = array.length;
|
||||
}
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
static if (Throws)
|
||||
{
|
||||
onOutOfMemoryError;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||
array = cast(T[]) buf;
|
||||
if (length == 0)
|
||||
{
|
||||
if (allocator.deallocate(array))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
onOutOfMemoryErrorNoGC();
|
||||
}
|
||||
}
|
||||
|
||||
static if (Init)
|
||||
{
|
||||
if (oldLength < length)
|
||||
{
|
||||
array[oldLength .. $] = T.init;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
void[] buf = array;
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
onOutOfMemoryErrorNoGC();
|
||||
}
|
||||
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||
array = cast(T[]) buf;
|
||||
|
||||
return array;
|
||||
}
|
||||
package(tanya) alias resizeArray = resize;
|
||||
|
||||
///
|
||||
unittest
|
||||
private unittest
|
||||
{
|
||||
int[] p;
|
||||
int[] p;
|
||||
|
||||
defaultAllocator.resizeArray(p, 20);
|
||||
assert(p.length == 20);
|
||||
p = defaultAllocator.resize(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
defaultAllocator.resizeArray(p, 30);
|
||||
assert(p.length == 30);
|
||||
p = defaultAllocator.resize(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
defaultAllocator.resizeArray(p, 10);
|
||||
assert(p.length == 10);
|
||||
p = defaultAllocator.resize(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
defaultAllocator.resizeArray(p, 0);
|
||||
assert(p is null);
|
||||
p = defaultAllocator.resize(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
/*
|
||||
* Destroys the object.
|
||||
* Returns the memory should be freed.
|
||||
*/
|
||||
package(tanya) void[] finalize(T)(ref T* p)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
destroy(*p);
|
||||
}
|
||||
return (cast(void*) p)[0 .. T.sizeof];
|
||||
}
|
||||
|
||||
package(tanya) void[] finalize(T)(ref T p)
|
||||
if (is(T == class) || is(T == interface))
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
static if (is(T == interface))
|
||||
{
|
||||
version(Windows)
|
||||
{
|
||||
import core.sys.windows.unknwn : IUnknown;
|
||||
static assert(!is(T : IUnknown), "COM interfaces can't be destroyed in "
|
||||
~ __PRETTY_FUNCTION__);
|
||||
}
|
||||
auto ob = cast(Object) p;
|
||||
}
|
||||
else
|
||||
{
|
||||
alias ob = p;
|
||||
}
|
||||
auto ptr = cast(void*) ob;
|
||||
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||
|
||||
auto ppv = cast(void**) ptr;
|
||||
if (!*ppv)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
auto pc = cast(ClassInfo*) *ppv;
|
||||
scope (exit)
|
||||
{
|
||||
*ppv = null;
|
||||
}
|
||||
|
||||
auto c = *pc;
|
||||
do
|
||||
{
|
||||
// Assume the destructor is @nogc. Leave it nothrow since the destructor
|
||||
// shouldn't throw and if it does, it is an error anyway.
|
||||
if (c.destructor)
|
||||
{
|
||||
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
|
||||
}
|
||||
}
|
||||
while ((c = c.base) !is null);
|
||||
|
||||
if (ppv[1]) // if monitor is not null
|
||||
{
|
||||
_d_monitordelete(cast(Object) ptr, true);
|
||||
}
|
||||
return support;
|
||||
}
|
||||
|
||||
package(tanya) void[] finalize(T)(ref T[] p)
|
||||
{
|
||||
static if (hasElaborateDestructor!(typeof(p[0])))
|
||||
{
|
||||
p.each!((ref e) => destroy(e));
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,101 +285,25 @@ unittest
|
||||
* allocator.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of $(D_PARAM p).
|
||||
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||
* p = Object or array to be destroyed.
|
||||
* T = Type of $(D_PARAM p).
|
||||
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||
* p = Object or array to be destroyed.
|
||||
*/
|
||||
void dispose(T)(shared Allocator allocator, auto ref T* p)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
destroy(*p);
|
||||
}
|
||||
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||
if (is(T == class) || is(T == interface))
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
static if (is(T == interface))
|
||||
{
|
||||
version(Windows)
|
||||
{
|
||||
import core.sys.windows.unknwn : IUnknown;
|
||||
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
|
||||
~ __PRETTY_FUNCTION__);
|
||||
}
|
||||
auto ob = cast(Object) p;
|
||||
}
|
||||
else
|
||||
{
|
||||
alias ob = p;
|
||||
}
|
||||
auto ptr = cast(void *) ob;
|
||||
|
||||
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||
scope (success)
|
||||
{
|
||||
() @trusted { allocator.deallocate(support); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
auto ppv = cast(void**) ptr;
|
||||
if (!*ppv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto pc = cast(ClassInfo*) *ppv;
|
||||
scope (exit)
|
||||
{
|
||||
*ppv = null;
|
||||
}
|
||||
|
||||
auto c = *pc;
|
||||
do
|
||||
{
|
||||
// Assume the destructor is @nogc. Leave it nothrow since the destructor
|
||||
// shouldn't throw and if it does, it is an error anyway.
|
||||
if (c.destructor)
|
||||
{
|
||||
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
|
||||
}
|
||||
}
|
||||
while ((c = c.base) !is null);
|
||||
|
||||
if (ppv[1]) // if monitor is not null
|
||||
{
|
||||
_d_monitordelete(cast(Object) ptr, true);
|
||||
}
|
||||
() @trusted { allocator.deallocate(finalize(p)); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T[] p)
|
||||
private unittest
|
||||
{
|
||||
static if (hasElaborateDestructor!(typeof(p[0])))
|
||||
{
|
||||
import std.algorithm.iteration;
|
||||
p.each!(e => destroy(e));
|
||||
}
|
||||
() @trusted { allocator.deallocate(p); }();
|
||||
p = null;
|
||||
}
|
||||
struct S
|
||||
{
|
||||
~this()
|
||||
{
|
||||
}
|
||||
}
|
||||
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
|
||||
|
||||
unittest
|
||||
{
|
||||
struct S
|
||||
{
|
||||
~this()
|
||||
{
|
||||
}
|
||||
}
|
||||
auto p = cast(S[]) defaultAllocator.allocate(S.sizeof);
|
||||
|
||||
defaultAllocator.dispose(p);
|
||||
defaultAllocator.dispose(p);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
356
source/tanya/network/inet.d
Normal file
356
source/tanya/network/inet.d
Normal file
@ -0,0 +1,356 @@
|
||||
/* 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/. */
|
||||
|
||||
/**
|
||||
* Internet utilities.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016-2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.network.inet;
|
||||
|
||||
import std.math;
|
||||
import std.range.primitives;
|
||||
import std.traits;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
version (Windows)
|
||||
{
|
||||
import core.sys.windows.winsock2;
|
||||
version = PlattformUnittest;
|
||||
}
|
||||
else version (Posix)
|
||||
{
|
||||
import core.sys.posix.arpa.inet;
|
||||
version = PlattformUnittest;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an unsigned integer as an $(D_KEYWORD ubyte) range.
|
||||
*
|
||||
* The range is bidirectional. The byte order is always big-endian.
|
||||
*
|
||||
* It can accept any unsigned integral type but the value should fit
|
||||
* in $(D_PARAM L) bytes.
|
||||
*
|
||||
* Params:
|
||||
* L = Desired range length.
|
||||
*/
|
||||
struct NetworkOrder(uint L)
|
||||
if (L > ubyte.sizeof && L <= ulong.sizeof)
|
||||
{
|
||||
static if (L > uint.sizeof)
|
||||
{
|
||||
private alias StorageType = ulong;
|
||||
}
|
||||
else static if (L > ushort.sizeof)
|
||||
{
|
||||
private alias StorageType = uint;
|
||||
}
|
||||
else static if (L > ubyte.sizeof)
|
||||
{
|
||||
private alias StorageType = ushort;
|
||||
}
|
||||
else
|
||||
{
|
||||
private alias StorageType = ubyte;
|
||||
}
|
||||
|
||||
private StorageType value;
|
||||
private size_t size = L;
|
||||
|
||||
const pure nothrow @safe @nogc invariant
|
||||
{
|
||||
assert(this.size <= L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new range.
|
||||
*
|
||||
* $(D_PARAM T) can be any unsigned type but $(D_PARAM value) cannot be
|
||||
* larger than the maximum can be stored in $(D_PARAM L) bytes. Otherwise
|
||||
* an assertion failure will be caused.
|
||||
*
|
||||
* Params:
|
||||
* T = Value type.
|
||||
* value = The value should be represented by this range.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE value <= 2 ^^ (length * 8) - 1).
|
||||
*/
|
||||
this(T)(const T value)
|
||||
if (isUnsigned!T)
|
||||
in
|
||||
{
|
||||
assert(value <= pow(2, L * 8) - 1);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.value = value & StorageType.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: LSB.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE length > 0).
|
||||
*/
|
||||
@property ubyte back() const
|
||||
in
|
||||
{
|
||||
assert(this.length > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
return this.value & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: MSB.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE length > 0).
|
||||
*/
|
||||
@property ubyte front() const
|
||||
in
|
||||
{
|
||||
assert(this.length > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
return (this.value >> ((this.length - 1) * 8)) & 0xff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminates the LSB.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE length > 0).
|
||||
*/
|
||||
void popBack()
|
||||
in
|
||||
{
|
||||
assert(this.length > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.value >>= 8;
|
||||
--this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminates the MSB.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE length > 0).
|
||||
*/
|
||||
void popFront()
|
||||
in
|
||||
{
|
||||
assert(this.length > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.value &= StorageType.max >> ((StorageType.sizeof - this.length) * 8);
|
||||
--this.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Copy of this range.
|
||||
*/
|
||||
typeof(this) save() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether the range is empty.
|
||||
*/
|
||||
@property bool empty() const
|
||||
{
|
||||
return this.length == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Byte length.
|
||||
*/
|
||||
@property size_t length() const
|
||||
{
|
||||
return this.size;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pure nothrow @safe @nogc unittest
|
||||
{
|
||||
auto networkOrder = NetworkOrder!3(0xae34e2u);
|
||||
assert(!networkOrder.empty);
|
||||
assert(networkOrder.front == 0xae);
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.length == 2);
|
||||
assert(networkOrder.front == 0x34);
|
||||
assert(networkOrder.back == 0xe2);
|
||||
|
||||
networkOrder.popBack();
|
||||
assert(networkOrder.length == 1);
|
||||
assert(networkOrder.front == 0x34);
|
||||
assert(networkOrder.front == 0x34);
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.empty);
|
||||
}
|
||||
|
||||
// Static.
|
||||
private unittest
|
||||
{
|
||||
static assert(isBidirectionalRange!(NetworkOrder!4));
|
||||
static assert(isBidirectionalRange!(NetworkOrder!8));
|
||||
static assert(!is(NetworkOrder!9));
|
||||
static assert(!is(NetworkOrder!1));
|
||||
}
|
||||
|
||||
// Tests against the system's htonl, htons.
|
||||
version (PlattformUnittest)
|
||||
{
|
||||
private unittest
|
||||
{
|
||||
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
|
||||
{
|
||||
const value = pow(2, counter) - 1;
|
||||
const inNetworkOrder = htonl(value);
|
||||
const p = cast(ubyte*) &inNetworkOrder;
|
||||
auto networkOrder = NetworkOrder!4(value);
|
||||
|
||||
assert(networkOrder.length == 4);
|
||||
assert(!networkOrder.empty);
|
||||
assert(networkOrder.front == *p);
|
||||
assert(networkOrder.back == *(p + 3));
|
||||
|
||||
networkOrder.popBack();
|
||||
assert(networkOrder.length == 3);
|
||||
assert(networkOrder.front == *p);
|
||||
assert(networkOrder.back == *(p + 2));
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.length == 2);
|
||||
assert(networkOrder.front == *(p + 1));
|
||||
assert(networkOrder.back == *(p + 2));
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.length == 1);
|
||||
assert(networkOrder.front == *(p + 2));
|
||||
assert(networkOrder.back == *(p + 2));
|
||||
|
||||
networkOrder.popBack();
|
||||
assert(networkOrder.length == 0);
|
||||
assert(networkOrder.empty);
|
||||
}
|
||||
|
||||
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
|
||||
{
|
||||
const value = cast(ushort) (pow(2, counter) - 1);
|
||||
const inNetworkOrder = htons(value);
|
||||
const p = cast(ubyte*) &inNetworkOrder;
|
||||
|
||||
auto networkOrder = NetworkOrder!2(value);
|
||||
|
||||
assert(networkOrder.length == 2);
|
||||
assert(!networkOrder.empty);
|
||||
assert(networkOrder.front == *p);
|
||||
assert(networkOrder.back == *(p + 1));
|
||||
|
||||
networkOrder.popBack();
|
||||
assert(networkOrder.length == 1);
|
||||
assert(networkOrder.front == *p);
|
||||
assert(networkOrder.back == *p);
|
||||
|
||||
networkOrder.popBack();
|
||||
assert(networkOrder.length == 0);
|
||||
assert(networkOrder.empty);
|
||||
|
||||
networkOrder = NetworkOrder!2(value);
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.length == 1);
|
||||
assert(networkOrder.front == *(p + 1));
|
||||
assert(networkOrder.back == *(p + 1));
|
||||
|
||||
networkOrder.popFront();
|
||||
assert(networkOrder.length == 0);
|
||||
assert(networkOrder.empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the $(D_KEYWORD ubyte) input range $(D_PARAM range) to
|
||||
* $(D_PARAM T).
|
||||
*
|
||||
* The byte order of $(D_PARAM r) is assumed to be big-endian. The length
|
||||
* cannot be larger than $(D_INLINECODE T.sizeof). Otherwise an assertion
|
||||
* failure will be caused.
|
||||
*
|
||||
* Params:
|
||||
* T = Desired return type.
|
||||
* R = Range type.
|
||||
* range = Input range.
|
||||
*
|
||||
* Returns: Integral representation of $(D_PARAM range) with the host byte
|
||||
* order.
|
||||
*/
|
||||
T toHostOrder(T = size_t, R)(R range)
|
||||
if (isInputRange!R
|
||||
&& !isInfinite!R
|
||||
&& is(Unqual!(ElementType!R) == ubyte)
|
||||
&& isUnsigned!T)
|
||||
{
|
||||
T ret;
|
||||
ushort pos = T.sizeof * 8;
|
||||
|
||||
for (; !range.empty && range.front == 0; pos -= 8, range.popFront())
|
||||
{
|
||||
}
|
||||
for (; !range.empty; range.popFront())
|
||||
{
|
||||
assert(pos != 0);
|
||||
pos -= 8;
|
||||
ret |= (cast(T) range.front) << pos;
|
||||
}
|
||||
|
||||
return ret >> pos;
|
||||
}
|
||||
|
||||
///
|
||||
pure nothrow @safe @nogc unittest
|
||||
{
|
||||
const value = 0xae34e2u;
|
||||
auto networkOrder = NetworkOrder!4(value);
|
||||
assert(networkOrder.toHostOrder() == value);
|
||||
}
|
||||
|
||||
// Tests against the system's htonl, htons.
|
||||
version (PlattformUnittest)
|
||||
{
|
||||
private unittest
|
||||
{
|
||||
for (uint counter; counter <= 8 * uint.sizeof; ++counter)
|
||||
{
|
||||
const value = pow(2, counter) - 1;
|
||||
const inNetworkOrder = htonl(value);
|
||||
const p = cast(ubyte*) &inNetworkOrder;
|
||||
auto networkOrder = NetworkOrder!4(value);
|
||||
|
||||
assert(p[0 .. uint.sizeof].toHostOrder() == value);
|
||||
}
|
||||
for (ushort counter; counter <= 8 * ushort.sizeof; ++counter)
|
||||
{
|
||||
const value = cast(ushort) (pow(2, counter) - 1);
|
||||
const inNetworkOrder = htons(value);
|
||||
const p = cast(ubyte*) &inNetworkOrder;
|
||||
auto networkOrder = NetworkOrder!2(value);
|
||||
|
||||
assert(p[0 .. ushort.sizeof].toHostOrder() == value);
|
||||
}
|
||||
}
|
||||
}
|
17
source/tanya/network/package.d
Normal file
17
source/tanya/network/package.d
Normal file
@ -0,0 +1,17 @@
|
||||
/* 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-2017.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.network;
|
||||
|
||||
public import tanya.network.inet;
|
||||
public import tanya.network.socket;
|
||||
public import tanya.network.url;
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user