Compare commits
54 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 | |||
d629525a4b | |||
33d321f0d7 | |||
3d64d59ba9 | |||
4635835a99 | |||
8725ec5f20 | |||
9a4c8cea06 | |||
eb360bda38 | |||
4b1cd2cbfd | |||
cd944a61b7 | |||
47ef787353 | |||
6436ad49df | |||
e1964e47a5 | |||
43319e4e3a | |||
33dbf042c2 | |||
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
|
@ -17,4 +17,7 @@ env:
|
|||||||
- ARCH=x86_64
|
- ARCH=x86_64
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- dub test --arch=$ARCH
|
- dub test -b unittest-cov --arch=$ARCH
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
35
README.md
35
README.md
@ -1,6 +1,8 @@
|
|||||||
# Tanya
|
# 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://code.dlang.org/packages/tanya)
|
[](https://code.dlang.org/packages/tanya)
|
||||||
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||||
@ -21,11 +23,12 @@ data structures and utilities that depend on the Garbage Collector in Phobos.
|
|||||||
Tanya consists of the following packages:
|
Tanya consists of the following packages:
|
||||||
|
|
||||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
* `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.
|
* `math`: Arbitrary precision integer and a set of functions.
|
||||||
* `memory`: Tools for manual memory management (allocator, reference counting,
|
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||||
helper functions).
|
helper functions).
|
||||||
* `network`: URL-Parsing, sockets.
|
* `network`: URL-Parsing, sockets, utilities.
|
||||||
|
|
||||||
### Supported compilers
|
### Supported compilers
|
||||||
|
|
||||||
@ -38,20 +41,14 @@ helper functions).
|
|||||||
|
|
||||||
### Current status
|
### Current status
|
||||||
|
|
||||||
The library is currently under development, but the API is becoming gradually
|
Following modules are under development:
|
||||||
stable.
|
|
||||||
|
|
||||||
Following modules are coming soon:
|
| Feature | Branch | Build status |
|
||||||
|
|--------------|:------------:|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| Feature | 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) |
|
||||||
| UTF-8 string | [](https://travis-ci.org/caraus-ecms/tanya) |
|
| File IO | io | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
|
||||||
| BitVector | [](https://travis-ci.org/caraus-ecms/tanya) |
|
| Hash table | horton-table | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/horton-table) |
|
||||||
| Hash table | N/A |
|
|
||||||
|
|
||||||
`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).
|
|
||||||
|
|
||||||
### Further characteristics
|
### Further characteristics
|
||||||
|
|
||||||
@ -62,10 +59,14 @@ is being tested on Windows and FreeBSD as well.
|
|||||||
|
|
||||||
* The library isn't thread-safe. Thread-safity should be added later.
|
* The library isn't thread-safe. Thread-safity should be added later.
|
||||||
|
|
||||||
|
## Release management
|
||||||
|
|
||||||
|
3-week release cycle.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Since I'm mostly busy writing new code and implementing new features I would
|
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
|
appreciate, if anyone uses the library. It would help me to improve the
|
||||||
codebase and fix issues.
|
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.loop;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -29,153 +29,153 @@ import std.algorithm.comparison;
|
|||||||
|
|
||||||
extern (C) nothrow @nogc
|
extern (C) nothrow @nogc
|
||||||
{
|
{
|
||||||
int epoll_create1(int flags);
|
int epoll_create1(int flags);
|
||||||
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
|
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_wait (int epfd, epoll_event *events, int maxevents, int timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class EpollLoop : SelectorLoop
|
final class EpollLoop : SelectorLoop
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private Vector!epoll_event events;
|
private Array!epoll_event events;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
||||||
}
|
}
|
||||||
super();
|
super();
|
||||||
events = Vector!epoll_event(maxEvents, MmapPool.instance);
|
events = Array!epoll_event(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this() @nogc
|
~this() @nogc
|
||||||
{
|
{
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called if the backend configuration changes.
|
* Should be called if the backend configuration changes.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
* oldEvents = The events were already set.
|
* oldEvents = The events were already set.
|
||||||
* events = The events should be set.
|
* events = The events should be set.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
protected override bool reify(SocketWatcher watcher,
|
protected override bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events) @nogc
|
EventMask events) @nogc
|
||||||
{
|
{
|
||||||
int op = EPOLL_CTL_DEL;
|
int op = EPOLL_CTL_DEL;
|
||||||
epoll_event ev;
|
epoll_event ev;
|
||||||
|
|
||||||
if (events == oldEvents)
|
if (events == oldEvents)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (events && oldEvents)
|
if (events && oldEvents)
|
||||||
{
|
{
|
||||||
op = EPOLL_CTL_MOD;
|
op = EPOLL_CTL_MOD;
|
||||||
}
|
}
|
||||||
else if (events && !oldEvents)
|
else if (events && !oldEvents)
|
||||||
{
|
{
|
||||||
op = EPOLL_CTL_ADD;
|
op = EPOLL_CTL_ADD;
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.data.fd = watcher.socket.handle;
|
ev.data.fd = watcher.socket.handle;
|
||||||
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
ev.events = (events & (Event.read | Event.accept) ? EPOLLIN | EPOLLPRI : 0)
|
||||||
| (events & Event.write ? EPOLLOUT : 0)
|
| (events & Event.write ? EPOLLOUT : 0)
|
||||||
| EPOLLET;
|
| EPOLLET;
|
||||||
|
|
||||||
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
|
return epoll_ctl(fd, op, watcher.socket.handle, &ev) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
protected override void poll() @nogc
|
protected override void poll() @nogc
|
||||||
{
|
{
|
||||||
// Don't block
|
// Don't block
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||||
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
|
auto eventCount = epoll_wait(fd, events.get().ptr, maxEvents, timeout);
|
||||||
|
|
||||||
if (eventCount < 0)
|
if (eventCount < 0)
|
||||||
{
|
{
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!BadLoopException();
|
throw defaultAllocator.make!BadLoopException();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i = 0; i < eventCount; ++i)
|
for (auto i = 0; i < eventCount; ++i)
|
||||||
{
|
{
|
||||||
auto transport = cast(StreamTransport) connections[events[i].data.fd];
|
auto transport = cast(StreamTransport) connections[events[i].data.fd];
|
||||||
|
|
||||||
if (transport is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
|
auto connection = cast(ConnectionWatcher) connections[events[i].data.fd];
|
||||||
assert(connection !is null);
|
assert(connection !is null);
|
||||||
|
|
||||||
acceptConnections(connection);
|
acceptConnections(connection);
|
||||||
}
|
}
|
||||||
else if (events[i].events & EPOLLERR)
|
else if (events[i].events & EPOLLERR)
|
||||||
{
|
{
|
||||||
kill(transport);
|
kill(transport);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
else if (events[i].events & (EPOLLIN | EPOLLPRI | EPOLLHUP))
|
||||||
{
|
{
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ptrdiff_t received;
|
ptrdiff_t received;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
received = transport.socket.receive(transport.output[]);
|
received = transport.socket.receive(transport.output[]);
|
||||||
transport.output += received;
|
transport.output += received;
|
||||||
}
|
}
|
||||||
while (received);
|
while (received);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
kill(transport, exception);
|
kill(transport, exception);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (transport.output.length)
|
else if (transport.output.length)
|
||||||
{
|
{
|
||||||
pendings.enqueue(transport);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (events[i].events & EPOLLOUT)
|
if (events[i].events & EPOLLOUT)
|
||||||
{
|
{
|
||||||
transport.writeReady = true;
|
transport.writeReady = true;
|
||||||
if (transport.input.length)
|
if (transport.input.length)
|
||||||
{
|
{
|
||||||
feed(transport);
|
feed(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The blocking time.
|
* Returns: The blocking time.
|
||||||
*/
|
*/
|
||||||
override protected @property inout(Duration) blockTime()
|
override protected @property inout(Duration) blockTime()
|
||||||
inout @safe pure nothrow
|
inout @safe pure nothrow
|
||||||
{
|
{
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
return min(super.blockTime, 1.dur!"seconds");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,354 +31,354 @@ import core.sys.windows.winsock2;
|
|||||||
*/
|
*/
|
||||||
final class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
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.
|
* Creates new completion port transport.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE socket !is null)
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
this(OverlappedConnectedSocket socket) @nogc
|
this(OverlappedConnectedSocket socket) @nogc
|
||||||
{
|
{
|
||||||
super(socket);
|
super(socket);
|
||||||
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Socket.
|
* Returns: Socket.
|
||||||
*
|
*
|
||||||
* Postcondition: $(D_INLINECODE socket !is null)
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
override @property OverlappedConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
out (socket)
|
out (socket)
|
||||||
{
|
{
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
return cast(OverlappedConnectedSocket) socket_;
|
return cast(OverlappedConnectedSocket) socket_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
*/
|
*/
|
||||||
bool isClosing() const pure nothrow @safe @nogc
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return closing;
|
return closing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the transport.
|
* Close the transport.
|
||||||
*
|
*
|
||||||
* Buffered data will be flushed. No more data will be received.
|
* Buffered data will be flushed. No more data will be received.
|
||||||
*/
|
*/
|
||||||
void close() pure nothrow @safe @nogc
|
void close() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
closing = true;
|
closing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write some data to the transport.
|
* Write some data to the transport.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data) @nogc
|
void write(ubyte[] data) @nogc
|
||||||
{
|
{
|
||||||
input ~= data;
|
input ~= data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Application protocol.
|
* Returns: Application protocol.
|
||||||
*/
|
*/
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return protocol_;
|
return protocol_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the protocol.
|
* Switches the protocol.
|
||||||
*
|
*
|
||||||
* The protocol is deallocated by the event loop, it should currently be
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
* allocated with $(D_PSYMBOL MmapPool).
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* protocol = Application protocol.
|
* protocol = Application protocol.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
*/
|
*/
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(protocol !is null);
|
assert(protocol !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
protocol_ = protocol;
|
protocol_ = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the watcher callback.
|
* Invokes the watcher callback.
|
||||||
*/
|
*/
|
||||||
override void invoke() @nogc
|
override void invoke() @nogc
|
||||||
{
|
{
|
||||||
if (output.length)
|
if (output.length)
|
||||||
{
|
{
|
||||||
immutable empty = input.length == 0;
|
immutable empty = input.length == 0;
|
||||||
protocol.received(output[0 .. $]);
|
protocol.received(output[0 .. $]);
|
||||||
output.clear();
|
output.clear();
|
||||||
if (empty)
|
if (empty)
|
||||||
{
|
{
|
||||||
SocketState overlapped;
|
SocketState overlapped;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
overlapped = MmapPool.instance.make!SocketState;
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
socket.beginSend(input[], overlapped);
|
socket.beginSend(input[], overlapped);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
MmapPool.instance.dispose(e);
|
MmapPool.instance.dispose(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
protocol.disconnected(exception);
|
protocol.disconnected(exception);
|
||||||
MmapPool.instance.dispose(protocol_);
|
MmapPool.instance.dispose(protocol_);
|
||||||
defaultAllocator.dispose(exception);
|
defaultAllocator.dispose(exception);
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class IOCPLoop : Loop
|
final class IOCPLoop : Loop
|
||||||
{
|
{
|
||||||
protected HANDLE completionPort;
|
protected HANDLE completionPort;
|
||||||
|
|
||||||
protected OVERLAPPED overlap;
|
protected OVERLAPPED overlap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||||
if (!completionPort)
|
if (!completionPort)
|
||||||
{
|
{
|
||||||
throw make!BadLoopException(defaultAllocator,
|
throw make!BadLoopException(defaultAllocator,
|
||||||
"Creating completion port failed");
|
"Creating completion port failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called if the backend configuration changes.
|
* Should be called if the backend configuration changes.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
* oldEvents = The events were already set.
|
* oldEvents = The events were already set.
|
||||||
* events = The events should be set.
|
* events = The events should be set.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
override protected bool reify(SocketWatcher watcher,
|
override protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events) @nogc
|
EventMask events) @nogc
|
||||||
{
|
{
|
||||||
SocketState overlapped;
|
SocketState overlapped;
|
||||||
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
||||||
{
|
{
|
||||||
auto socket = cast(OverlappedStreamSocket) watcher.socket;
|
auto socket = cast(OverlappedStreamSocket) watcher.socket;
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
|
|
||||||
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
if (CreateIoCompletionPort(cast(HANDLE) socket.handle,
|
||||||
completionPort,
|
completionPort,
|
||||||
cast(ULONG_PTR) (cast(void*) watcher),
|
cast(ULONG_PTR) (cast(void*) watcher),
|
||||||
0) !is completionPort)
|
0) !is completionPort)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
overlapped = MmapPool.instance.make!SocketState;
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
socket.beginAccept(overlapped);
|
socket.beginAccept(overlapped);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
defaultAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!(oldEvents & Event.read) && (events & Event.read)
|
if (!(oldEvents & Event.read) && (events & Event.read)
|
||||||
|| !(oldEvents & Event.write) && (events & Event.write))
|
|| !(oldEvents & Event.write) && (events & Event.write))
|
||||||
{
|
{
|
||||||
auto transport = cast(StreamTransport) watcher;
|
auto transport = cast(StreamTransport) watcher;
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
|
|
||||||
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
if (CreateIoCompletionPort(cast(HANDLE) transport.socket.handle,
|
||||||
completionPort,
|
completionPort,
|
||||||
cast(ULONG_PTR) (cast(void*) watcher),
|
cast(ULONG_PTR) (cast(void*) watcher),
|
||||||
0) !is completionPort)
|
0) !is completionPort)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin to read
|
// Begin to read
|
||||||
if (!(oldEvents & Event.read) && (events & Event.read))
|
if (!(oldEvents & Event.read) && (events & Event.read))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
overlapped = MmapPool.instance.make!SocketState;
|
overlapped = MmapPool.instance.make!SocketState;
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
defaultAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void kill(StreamTransport transport,
|
private void kill(StreamTransport transport,
|
||||||
SocketException exception = null) @nogc
|
SocketException exception = null) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
transport.socket.shutdown();
|
transport.socket.shutdown();
|
||||||
defaultAllocator.dispose(transport.socket);
|
defaultAllocator.dispose(transport.socket);
|
||||||
transport.exception = exception;
|
transport.exception = exception;
|
||||||
pendings.enqueue(transport);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
override protected void poll() @nogc
|
override protected void poll() @nogc
|
||||||
{
|
{
|
||||||
DWORD lpNumberOfBytes;
|
DWORD lpNumberOfBytes;
|
||||||
ULONG_PTR key;
|
ULONG_PTR key;
|
||||||
LPOVERLAPPED overlap;
|
LPOVERLAPPED overlap;
|
||||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||||
|
|
||||||
auto result = GetQueuedCompletionStatus(completionPort,
|
auto result = GetQueuedCompletionStatus(completionPort,
|
||||||
&lpNumberOfBytes,
|
&lpNumberOfBytes,
|
||||||
&key,
|
&key,
|
||||||
&overlap,
|
&overlap,
|
||||||
timeout);
|
timeout);
|
||||||
if (result == FALSE && overlap == NULL)
|
if (result == FALSE && overlap == NULL)
|
||||||
{
|
{
|
||||||
return; // Timeout
|
return; // Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
auto overlapped = (cast(SocketState) ((cast(void*) overlap) - 8));
|
||||||
assert(overlapped !is null);
|
assert(overlapped !is null);
|
||||||
scope (failure)
|
scope (failure)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (overlapped.event)
|
switch (overlapped.event)
|
||||||
{
|
{
|
||||||
case OverlappedSocketEvent.accept:
|
case OverlappedSocketEvent.accept:
|
||||||
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
auto connection = cast(ConnectionWatcher) (cast(void*) key);
|
||||||
assert(connection !is null);
|
assert(connection !is null);
|
||||||
|
|
||||||
auto listener = cast(OverlappedStreamSocket) connection.socket;
|
auto listener = cast(OverlappedStreamSocket) connection.socket;
|
||||||
assert(listener !is null);
|
assert(listener !is null);
|
||||||
|
|
||||||
auto socket = listener.endAccept(overlapped);
|
auto socket = listener.endAccept(overlapped);
|
||||||
auto transport = MmapPool.instance.make!StreamTransport(socket);
|
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);
|
pendings.enqueue(connection);
|
||||||
listener.beginAccept(overlapped);
|
listener.beginAccept(overlapped);
|
||||||
break;
|
break;
|
||||||
case OverlappedSocketEvent.read:
|
case OverlappedSocketEvent.read:
|
||||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
|
|
||||||
if (!transport.active)
|
if (!transport.active)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(transport);
|
MmapPool.instance.dispose(transport);
|
||||||
MmapPool.instance.dispose(overlapped);
|
MmapPool.instance.dispose(overlapped);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int received;
|
int received;
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
received = transport.socket.endReceive(overlapped);
|
received = transport.socket.endReceive(overlapped);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
// We want to get one last notification to destroy the watcher.
|
// We want to get one last notification to destroy the watcher.
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
kill(transport, exception);
|
kill(transport, exception);
|
||||||
}
|
}
|
||||||
else if (received > 0)
|
else if (received > 0)
|
||||||
{
|
{
|
||||||
immutable full = transport.output.free == received;
|
immutable full = transport.output.free == received;
|
||||||
|
|
||||||
transport.output += received;
|
transport.output += received;
|
||||||
// Receive was interrupted because the buffer is full. We have to continue.
|
// Receive was interrupted because the buffer is full. We have to continue.
|
||||||
if (full)
|
if (full)
|
||||||
{
|
{
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
}
|
}
|
||||||
pendings.enqueue(transport);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OverlappedSocketEvent.write:
|
case OverlappedSocketEvent.write:
|
||||||
auto transport = cast(StreamTransport) (cast(void*) key);
|
auto transport = cast(StreamTransport) (cast(void*) key);
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
|
|
||||||
transport.input += transport.socket.endSend(overlapped);
|
transport.input += transport.socket.endSend(overlapped);
|
||||||
if (transport.input.length > 0)
|
if (transport.input.length > 0)
|
||||||
{
|
{
|
||||||
transport.socket.beginSend(transport.input[], overlapped);
|
transport.socket.beginSend(transport.input[], overlapped);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
transport.socket.beginReceive(transport.output[], overlapped);
|
transport.socket.beginReceive(transport.output[], overlapped);
|
||||||
if (transport.isClosing())
|
if (transport.isClosing())
|
||||||
{
|
{
|
||||||
kill(transport);
|
kill(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false, "Unknown event");
|
assert(false, "Unknown event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,31 +12,31 @@ module tanya.async.event.kqueue;
|
|||||||
|
|
||||||
version (OSX)
|
version (OSX)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (iOS)
|
else version (iOS)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (TVOS)
|
else version (TVOS)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (WatchOS)
|
else version (WatchOS)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (FreeBSD)
|
else version (FreeBSD)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (OpenBSD)
|
else version (OpenBSD)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
else version (DragonFlyBSD)
|
else version (DragonFlyBSD)
|
||||||
{
|
{
|
||||||
version = MacBSD;
|
version = MacBSD;
|
||||||
}
|
}
|
||||||
|
|
||||||
version (MacBSD):
|
version (MacBSD):
|
||||||
@ -50,62 +50,62 @@ import tanya.async.event.selector;
|
|||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
|
|
||||||
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
||||||
{
|
{
|
||||||
*kevp = kevent_t(args);
|
*kevp = kevent_t(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum : short
|
enum : short
|
||||||
{
|
{
|
||||||
EVFILT_READ = -1,
|
EVFILT_READ = -1,
|
||||||
EVFILT_WRITE = -2,
|
EVFILT_WRITE = -2,
|
||||||
EVFILT_AIO = -3, /* attached to aio requests */
|
EVFILT_AIO = -3, /* attached to aio requests */
|
||||||
EVFILT_VNODE = -4, /* attached to vnodes */
|
EVFILT_VNODE = -4, /* attached to vnodes */
|
||||||
EVFILT_PROC = -5, /* attached to struct proc */
|
EVFILT_PROC = -5, /* attached to struct proc */
|
||||||
EVFILT_SIGNAL = -6, /* attached to struct proc */
|
EVFILT_SIGNAL = -6, /* attached to struct proc */
|
||||||
EVFILT_TIMER = -7, /* timers */
|
EVFILT_TIMER = -7, /* timers */
|
||||||
EVFILT_MACHPORT = -8, /* Mach portsets */
|
EVFILT_MACHPORT = -8, /* Mach portsets */
|
||||||
EVFILT_FS = -9, /* filesystem events */
|
EVFILT_FS = -9, /* filesystem events */
|
||||||
EVFILT_USER = -10, /* User events */
|
EVFILT_USER = -10, /* User events */
|
||||||
EVFILT_VM = -12, /* virtual memory events */
|
EVFILT_VM = -12, /* virtual memory events */
|
||||||
EVFILT_SYSCOUNT = 11
|
EVFILT_SYSCOUNT = 11
|
||||||
}
|
}
|
||||||
|
|
||||||
struct kevent_t
|
struct kevent_t
|
||||||
{
|
{
|
||||||
uintptr_t ident; /* identifier for this event */
|
uintptr_t ident; /* identifier for this event */
|
||||||
short filter; /* filter for event */
|
short filter; /* filter for event */
|
||||||
ushort flags;
|
ushort flags;
|
||||||
uint fflags;
|
uint fflags;
|
||||||
intptr_t data;
|
intptr_t data;
|
||||||
void *udata; /* opaque user data identifier */
|
void *udata; /* opaque user data identifier */
|
||||||
}
|
}
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
/* actions */
|
/* actions */
|
||||||
EV_ADD = 0x0001, /* add event to kq (implies enable) */
|
EV_ADD = 0x0001, /* add event to kq (implies enable) */
|
||||||
EV_DELETE = 0x0002, /* delete event from kq */
|
EV_DELETE = 0x0002, /* delete event from kq */
|
||||||
EV_ENABLE = 0x0004, /* enable event */
|
EV_ENABLE = 0x0004, /* enable event */
|
||||||
EV_DISABLE = 0x0008, /* disable event (not reported) */
|
EV_DISABLE = 0x0008, /* disable event (not reported) */
|
||||||
|
|
||||||
/* flags */
|
/* flags */
|
||||||
EV_ONESHOT = 0x0010, /* only report one occurrence */
|
EV_ONESHOT = 0x0010, /* only report one occurrence */
|
||||||
EV_CLEAR = 0x0020, /* clear event state after reporting */
|
EV_CLEAR = 0x0020, /* clear event state after reporting */
|
||||||
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
|
EV_RECEIPT = 0x0040, /* force EV_ERROR on success, data=0 */
|
||||||
EV_DISPATCH = 0x0080, /* disable event after reporting */
|
EV_DISPATCH = 0x0080, /* disable event after reporting */
|
||||||
|
|
||||||
EV_SYSFLAGS = 0xF000, /* reserved by system */
|
EV_SYSFLAGS = 0xF000, /* reserved by system */
|
||||||
EV_FLAG1 = 0x2000, /* filter-specific flag */
|
EV_FLAG1 = 0x2000, /* filter-specific flag */
|
||||||
|
|
||||||
/* returned values */
|
/* returned values */
|
||||||
EV_EOF = 0x8000, /* EOF detected */
|
EV_EOF = 0x8000, /* EOF detected */
|
||||||
EV_ERROR = 0x4000, /* error, data contains errno */
|
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||||
}
|
}
|
||||||
|
|
||||||
extern(C) int kqueue() nothrow @nogc;
|
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
|
final class KqueueLoop : SelectorLoop
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private Vector!kevent_t events;
|
private Array!kevent_t events;
|
||||||
private Vector!kevent_t changes;
|
private Array!kevent_t changes;
|
||||||
private size_t changeCount;
|
private size_t changeCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Maximal event count can be got at a time
|
* Returns: Maximal event count can be got at a time
|
||||||
* (should be supported by the backend).
|
* (should be supported by the backend).
|
||||||
*/
|
*/
|
||||||
override protected @property uint maxEvents()
|
override protected @property uint maxEvents()
|
||||||
const pure nothrow @safe @nogc
|
const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return cast(uint) events.length;
|
return cast(uint) events.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if ((fd = kqueue()) == -1)
|
if ((fd = kqueue()) == -1)
|
||||||
{
|
{
|
||||||
throw make!BadLoopException(defaultAllocator,
|
throw make!BadLoopException(defaultAllocator,
|
||||||
"kqueue initialization failed");
|
"kqueue initialization failed");
|
||||||
}
|
}
|
||||||
events = Vector!kevent_t(64, MmapPool.instance);
|
events = Array!kevent_t(64, MmapPool.instance);
|
||||||
changes = Vector!kevent_t(64, MmapPool.instance);
|
changes = Array!kevent_t(64, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this() @nogc
|
~this() @nogc
|
||||||
{
|
{
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(socket_t socket, short filter, ushort flags) @nogc
|
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||||
{
|
{
|
||||||
if (changes.length <= changeCount)
|
if (changes.length <= changeCount)
|
||||||
{
|
{
|
||||||
changes.length = changeCount + maxEvents;
|
changes.length = changeCount + maxEvents;
|
||||||
}
|
}
|
||||||
EV_SET(&changes[changeCount],
|
EV_SET(&changes[changeCount],
|
||||||
cast(ulong) socket,
|
cast(ulong) socket,
|
||||||
filter,
|
filter,
|
||||||
flags,
|
flags,
|
||||||
0U,
|
0U,
|
||||||
0L,
|
0L,
|
||||||
null);
|
null);
|
||||||
++changeCount;
|
++changeCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called if the backend configuration changes.
|
* Should be called if the backend configuration changes.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
* oldEvents = The events were already set.
|
* oldEvents = The events were already set.
|
||||||
* events = The events should be set.
|
* events = The events should be set.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
override protected bool reify(SocketWatcher watcher,
|
override protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events) @nogc
|
EventMask events) @nogc
|
||||||
{
|
{
|
||||||
if (events != oldEvents)
|
if (events != oldEvents)
|
||||||
{
|
{
|
||||||
if (oldEvents & Event.read || oldEvents & Event.accept)
|
if (oldEvents & Event.read || oldEvents & Event.accept)
|
||||||
{
|
{
|
||||||
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
|
set(watcher.socket.handle, EVFILT_READ, EV_DELETE);
|
||||||
}
|
}
|
||||||
if (oldEvents & Event.write)
|
if (oldEvents & Event.write)
|
||||||
{
|
{
|
||||||
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
|
set(watcher.socket.handle, EVFILT_WRITE, EV_DELETE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (events & (Event.read | events & Event.accept))
|
if (events & (Event.read | events & Event.accept))
|
||||||
{
|
{
|
||||||
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
|
set(watcher.socket.handle, EVFILT_READ, EV_ADD | EV_ENABLE);
|
||||||
}
|
}
|
||||||
if (events & Event.write)
|
if (events & Event.write)
|
||||||
{
|
{
|
||||||
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
|
set(watcher.socket.handle, EVFILT_WRITE, EV_ADD | EV_DISPATCH);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
protected override void poll() @nogc
|
protected override void poll() @nogc
|
||||||
{
|
{
|
||||||
timespec ts;
|
timespec ts;
|
||||||
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
||||||
|
|
||||||
if (changeCount > maxEvents)
|
if (changeCount > maxEvents)
|
||||||
{
|
{
|
||||||
events.length = changes.length;
|
events.length = changes.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto eventCount = kevent(fd,
|
auto eventCount = kevent(fd,
|
||||||
changes.get().ptr,
|
changes.get().ptr,
|
||||||
cast(int) changeCount,
|
cast(int) changeCount,
|
||||||
events.get().ptr,
|
events.get().ptr,
|
||||||
maxEvents,
|
maxEvents,
|
||||||
&ts);
|
&ts);
|
||||||
changeCount = 0;
|
changeCount = 0;
|
||||||
|
|
||||||
if (eventCount < 0)
|
if (eventCount < 0)
|
||||||
{
|
{
|
||||||
if (errno != EINTR)
|
if (errno != EINTR)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!BadLoopException();
|
throw defaultAllocator.make!BadLoopException();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i; i < eventCount; ++i)
|
for (int i; i < eventCount; ++i)
|
||||||
{
|
{
|
||||||
assert(connections.length > events[i].ident);
|
assert(connections.length > events[i].ident);
|
||||||
|
|
||||||
auto transport = cast(StreamTransport) connections[events[i].ident];
|
auto transport = cast(StreamTransport) connections[events[i].ident];
|
||||||
// If it is a ConnectionWatcher. Accept connections.
|
// If it is a ConnectionWatcher. Accept connections.
|
||||||
if (transport is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
|
auto connection = cast(ConnectionWatcher) connections[events[i].ident];
|
||||||
assert(connection !is null);
|
assert(connection !is null);
|
||||||
|
|
||||||
acceptConnections(connection);
|
acceptConnections(connection);
|
||||||
}
|
}
|
||||||
else if (events[i].flags & EV_ERROR)
|
else if (events[i].flags & EV_ERROR)
|
||||||
{
|
{
|
||||||
kill(transport);
|
kill(transport);
|
||||||
}
|
}
|
||||||
else if (events[i].filter == EVFILT_READ)
|
else if (events[i].filter == EVFILT_READ)
|
||||||
{
|
{
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ptrdiff_t received;
|
ptrdiff_t received;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
received = transport.socket.receive(transport.output[]);
|
received = transport.socket.receive(transport.output[]);
|
||||||
transport.output += received;
|
transport.output += received;
|
||||||
}
|
}
|
||||||
while (received);
|
while (received);
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (transport.socket.disconnected)
|
if (transport.socket.disconnected)
|
||||||
{
|
{
|
||||||
kill(transport, exception);
|
kill(transport, exception);
|
||||||
}
|
}
|
||||||
else if (transport.output.length)
|
else if (transport.output.length)
|
||||||
{
|
{
|
||||||
pendings.enqueue(transport);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (events[i].filter == EVFILT_WRITE)
|
else if (events[i].filter == EVFILT_WRITE)
|
||||||
{
|
{
|
||||||
transport.writeReady = true;
|
transport.writeReady = true;
|
||||||
if (transport.input.length)
|
if (transport.input.length)
|
||||||
{
|
{
|
||||||
feed(transport);
|
feed(transport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The blocking time.
|
* Returns: The blocking time.
|
||||||
*/
|
*/
|
||||||
override protected @property inout(Duration) blockTime()
|
override protected @property inout(Duration) blockTime()
|
||||||
inout @nogc @safe pure nothrow
|
inout @nogc @safe pure nothrow
|
||||||
{
|
{
|
||||||
return min(super.blockTime, 1.dur!"seconds");
|
return min(super.blockTime, 1.dur!"seconds");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the transport couldn't send the data, the further sending should
|
* If the transport couldn't send the data, the further sending should
|
||||||
* be handled by the event loop.
|
* be handled by the event loop.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* transport = Transport.
|
* transport = Transport.
|
||||||
* exception = Exception thrown on sending.
|
* exception = Exception thrown on sending.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
* transport will be destroyed then).
|
* transport will be destroyed then).
|
||||||
*/
|
*/
|
||||||
protected override bool feed(StreamTransport transport,
|
protected override bool feed(StreamTransport transport,
|
||||||
SocketException exception = null) @nogc
|
SocketException exception = null) @nogc
|
||||||
{
|
{
|
||||||
if (!super.feed(transport, exception))
|
if (!super.feed(transport, exception))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!transport.writeReady)
|
if (!transport.writeReady)
|
||||||
{
|
{
|
||||||
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
|
set(transport.socket.handle, EVFILT_WRITE, EV_DISPATCH);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import tanya.async.protocol;
|
|||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.buffer;
|
import tanya.container.buffer;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -27,371 +27,374 @@ import tanya.network.socket;
|
|||||||
*/
|
*/
|
||||||
package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
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.
|
/// Received notification that the underlying socket is write-ready.
|
||||||
package bool writeReady;
|
package bool writeReady;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* loop = Event loop.
|
* loop = Event loop.
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
|
* Precondition: $(D_INLINECODE loop !is null && socket !is null)
|
||||||
*/
|
*/
|
||||||
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(loop !is null);
|
assert(loop !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
super(socket);
|
super(socket);
|
||||||
this.loop = loop;
|
this.loop = loop;
|
||||||
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Socket.
|
* Returns: Socket.
|
||||||
*
|
*
|
||||||
* Postcondition: $(D_INLINECODE socket !is null)
|
* Postcondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
override @property ConnectedSocket socket() pure nothrow @safe @nogc
|
||||||
out (socket)
|
out (socket)
|
||||||
{
|
{
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
return cast(ConnectedSocket) socket_;
|
return cast(ConnectedSocket) socket_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
private @property void socket(ConnectedSocket socket)
|
||||||
in
|
pure nothrow @safe @nogc
|
||||||
{
|
in
|
||||||
assert(socket !is null);
|
{
|
||||||
}
|
assert(socket !is null);
|
||||||
body
|
}
|
||||||
{
|
body
|
||||||
socket_ = socket;
|
{
|
||||||
}
|
socket_ = socket;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Application protocol.
|
* Returns: Application protocol.
|
||||||
*/
|
*/
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return protocol_;
|
return protocol_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the protocol.
|
* Switches the protocol.
|
||||||
*
|
*
|
||||||
* The protocol is deallocated by the event loop, it should currently be
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
* allocated with $(D_PSYMBOL MmapPool).
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* protocol = Application protocol.
|
* protocol = Application protocol.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
*/
|
*/
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(protocol !is null);
|
assert(protocol !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
protocol_ = protocol;
|
protocol_ = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
*/
|
*/
|
||||||
bool isClosing() const pure nothrow @safe @nogc
|
bool isClosing() const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return closing;
|
return closing;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the transport.
|
* Close the transport.
|
||||||
*
|
*
|
||||||
* Buffered data will be flushed. No more data will be received.
|
* Buffered data will be flushed. No more data will be received.
|
||||||
*/
|
*/
|
||||||
void close() @nogc
|
void close() @nogc
|
||||||
{
|
{
|
||||||
closing = true;
|
closing = true;
|
||||||
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write));
|
loop.reify(this,
|
||||||
}
|
EventMask(Event.read, Event.write),
|
||||||
|
EventMask(Event.write));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes the watcher callback.
|
* Invokes the watcher callback.
|
||||||
*/
|
*/
|
||||||
override void invoke() @nogc
|
override void invoke() @nogc
|
||||||
{
|
{
|
||||||
if (output.length)
|
if (output.length)
|
||||||
{
|
{
|
||||||
protocol.received(output[0 .. $]);
|
protocol.received(output[0 .. $]);
|
||||||
output.clear();
|
output.clear();
|
||||||
if (isClosing() && input.length == 0)
|
if (isClosing() && input.length == 0)
|
||||||
{
|
{
|
||||||
loop.kill(this);
|
loop.kill(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
protocol.disconnected(exception);
|
protocol.disconnected(exception);
|
||||||
MmapPool.instance.dispose(protocol_);
|
MmapPool.instance.dispose(protocol_);
|
||||||
defaultAllocator.dispose(exception);
|
defaultAllocator.dispose(exception);
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write some data to the transport.
|
* Write some data to the transport.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data) @nogc
|
void write(ubyte[] data) @nogc
|
||||||
{
|
{
|
||||||
if (!data.length)
|
if (!data.length)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Try to write if the socket is write ready.
|
// Try to write if the socket is write ready.
|
||||||
if (writeReady)
|
if (writeReady)
|
||||||
{
|
{
|
||||||
ptrdiff_t sent;
|
ptrdiff_t sent;
|
||||||
SocketException exception;
|
SocketException exception;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sent = socket.send(data);
|
sent = socket.send(data);
|
||||||
if (sent == 0)
|
if (sent == 0)
|
||||||
{
|
{
|
||||||
writeReady = false;
|
writeReady = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
writeReady = false;
|
writeReady = false;
|
||||||
exception = e;
|
exception = e;
|
||||||
}
|
}
|
||||||
if (sent < data.length)
|
if (sent < data.length)
|
||||||
{
|
{
|
||||||
input ~= data[sent..$];
|
input ~= data[sent..$];
|
||||||
loop.feed(this, exception);
|
loop.feed(this, exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
input ~= data;
|
input ~= data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SelectorLoop : Loop
|
abstract class SelectorLoop : Loop
|
||||||
{
|
{
|
||||||
/// Pending connections.
|
/// Pending connections.
|
||||||
protected Vector!SocketWatcher connections;
|
protected Array!SocketWatcher connections;
|
||||||
|
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
~this() @nogc
|
~this() @nogc
|
||||||
{
|
{
|
||||||
foreach (ref connection; connections)
|
foreach (ref connection; connections)
|
||||||
{
|
{
|
||||||
// We want to free only the transports. ConnectionWatcher are created by the
|
// We want to free only the transports. ConnectionWatcher are
|
||||||
// user and should be freed by himself.
|
// created by the user and should be freed by himself.
|
||||||
if (cast(StreamTransport) connection !is null)
|
if (cast(StreamTransport) connection !is null)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(connection);
|
MmapPool.instance.dispose(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should be called if the backend configuration changes.
|
* Should be called if the backend configuration changes.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
* oldEvents = The events were already set.
|
* oldEvents = The events were already set.
|
||||||
* events = The events should be set.
|
* events = The events should be set.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
override abstract protected bool reify(SocketWatcher watcher,
|
override abstract protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events) @nogc;
|
EventMask events) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kills the watcher and closes the connection.
|
* Kills the watcher and closes the connection.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* transport = Transport.
|
* transport = Transport.
|
||||||
* exception = Occurred exception.
|
* exception = Occurred exception.
|
||||||
*/
|
*/
|
||||||
protected void kill(StreamTransport transport,
|
protected void kill(StreamTransport transport,
|
||||||
SocketException exception = null) @nogc
|
SocketException exception = null) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
transport.socket.shutdown();
|
transport.socket.shutdown();
|
||||||
defaultAllocator.dispose(transport.socket);
|
defaultAllocator.dispose(transport.socket);
|
||||||
transport.exception = exception;
|
transport.exception = exception;
|
||||||
pendings.enqueue(transport);
|
pendings.enqueue(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the transport couldn't send the data, the further sending should
|
* If the transport couldn't send the data, the further sending should
|
||||||
* be handled by the event loop.
|
* be handled by the event loop.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* transport = Transport.
|
* transport = Transport.
|
||||||
* exception = Exception thrown on sending.
|
* exception = Exception thrown on sending.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
* Returns: $(D_KEYWORD true) if the operation could be successfully
|
||||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||||
* transport will be destroyed then).
|
* transport will be destroyed then).
|
||||||
*/
|
*/
|
||||||
protected bool feed(StreamTransport transport,
|
protected bool feed(StreamTransport transport,
|
||||||
SocketException exception = null) @nogc
|
SocketException exception = null) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(transport !is null);
|
assert(transport !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
while (transport.input.length && transport.writeReady)
|
while (transport.input.length && transport.writeReady)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ptrdiff_t sent = transport.socket.send(transport.input[]);
|
ptrdiff_t sent = transport.socket.send(transport.input[]);
|
||||||
if (sent == 0)
|
if (sent == 0)
|
||||||
{
|
{
|
||||||
transport.writeReady = false;
|
transport.writeReady = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
transport.input += sent;
|
transport.input += sent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
exception = e;
|
exception = e;
|
||||||
transport.writeReady = false;
|
transport.writeReady = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (exception !is null)
|
if (exception !is null)
|
||||||
{
|
{
|
||||||
kill(transport, exception);
|
kill(transport, exception);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (transport.input.length == 0 && transport.isClosing())
|
if (transport.input.length == 0 && transport.isClosing())
|
||||||
{
|
{
|
||||||
kill(transport);
|
kill(transport);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start watching.
|
* Start watching.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
override void start(ConnectionWatcher watcher) @nogc
|
override void start(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (watcher.active)
|
if (watcher.active)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connections.length <= watcher.socket)
|
if (connections.length <= watcher.socket)
|
||||||
{
|
{
|
||||||
connections.length = watcher.socket.handle + maxEvents / 2;
|
connections.length = watcher.socket.handle + maxEvents / 2;
|
||||||
}
|
}
|
||||||
connections[watcher.socket.handle] = watcher;
|
connections[watcher.socket.handle] = watcher;
|
||||||
|
|
||||||
super.start(watcher);
|
super.start(watcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accept incoming connections.
|
* Accept incoming connections.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* connection = Connection watcher ready to accept.
|
* connection = Connection watcher ready to accept.
|
||||||
*/
|
*/
|
||||||
package void acceptConnections(ConnectionWatcher connection) @nogc
|
package void acceptConnections(ConnectionWatcher connection) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(connection !is null);
|
assert(connection !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
ConnectedSocket client;
|
ConnectedSocket client;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client = (cast(StreamSocket) connection.socket).accept();
|
client = (cast(StreamSocket) connection.socket).accept();
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
defaultAllocator.dispose(e);
|
defaultAllocator.dispose(e);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (client is null)
|
if (client is null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamTransport transport;
|
StreamTransport transport;
|
||||||
|
|
||||||
if (connections.length > client.handle)
|
if (connections.length > client.handle)
|
||||||
{
|
{
|
||||||
transport = cast(StreamTransport) connections[client.handle];
|
transport = cast(StreamTransport) connections[client.handle];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connections.length = client.handle + maxEvents / 2;
|
connections.length = client.handle + maxEvents / 2;
|
||||||
}
|
}
|
||||||
if (transport is null)
|
if (transport is null)
|
||||||
{
|
{
|
||||||
transport = MmapPool.instance.make!StreamTransport(this, client);
|
transport = MmapPool.instance.make!StreamTransport(this, client);
|
||||||
connections[client.handle] = transport;
|
connections[client.handle] = transport;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
transport.socket = client;
|
transport.socket = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
reify(transport, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||||
connection.incoming.enqueue(transport);
|
connection.incoming.enqueue(transport);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!connection.incoming.empty)
|
if (!connection.incoming.empty)
|
||||||
{
|
{
|
||||||
pendings.enqueue(connection);
|
pendings.enqueue(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,12 +21,12 @@ import core.sys.windows.windef;
|
|||||||
*/
|
*/
|
||||||
class State
|
class State
|
||||||
{
|
{
|
||||||
/// For internal use by Windows API.
|
/// For internal use by Windows API.
|
||||||
align(1) OVERLAPPED overlapped;
|
align(1) OVERLAPPED overlapped;
|
||||||
|
|
||||||
/// File/socket handle.
|
/// File/socket handle.
|
||||||
HANDLE handle;
|
HANDLE handle;
|
||||||
|
|
||||||
/// For keeping events or event masks.
|
/// For keeping events or event masks.
|
||||||
int event;
|
int event;
|
||||||
}
|
}
|
||||||
|
@ -15,50 +15,50 @@
|
|||||||
*
|
*
|
||||||
* class EchoProtocol : TransmissionControlProtocol
|
* class EchoProtocol : TransmissionControlProtocol
|
||||||
* {
|
* {
|
||||||
* private DuplexTransport transport;
|
* private DuplexTransport transport;
|
||||||
*
|
*
|
||||||
* void received(in ubyte[] data) @nogc
|
* void received(in ubyte[] data) @nogc
|
||||||
* {
|
* {
|
||||||
* transport.write(data);
|
* transport.write(data);
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void connected(DuplexTransport transport) @nogc
|
* void connected(DuplexTransport transport) @nogc
|
||||||
* {
|
* {
|
||||||
* this.transport = transport;
|
* this.transport = transport;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void disconnected(SocketException e) @nogc
|
* void disconnected(SocketException e) @nogc
|
||||||
* {
|
* {
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* void main()
|
* 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)
|
* version (Windows)
|
||||||
* {
|
* {
|
||||||
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
||||||
* }
|
* }
|
||||||
* else
|
* else
|
||||||
* {
|
* {
|
||||||
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
||||||
* sock.blocking = false;
|
* sock.blocking = false;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* sock.bind(address);
|
* sock.bind(address);
|
||||||
* sock.listen(5);
|
* sock.listen(5);
|
||||||
*
|
*
|
||||||
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||||
* io.setProtocol!EchoProtocol;
|
* io.setProtocol!EchoProtocol;
|
||||||
*
|
*
|
||||||
* defaultLoop.start(io);
|
* defaultLoop.start(io);
|
||||||
* defaultLoop.run();
|
* defaultLoop.run();
|
||||||
*
|
*
|
||||||
* sock.shutdown();
|
* sock.shutdown();
|
||||||
* defaultAllocator.dispose(io);
|
* defaultAllocator.dispose(io);
|
||||||
* defaultAllocator.dispose(sock);
|
* defaultAllocator.dispose(sock);
|
||||||
* defaultAllocator.dispose(address);
|
* defaultAllocator.dispose(address);
|
||||||
* }
|
* }
|
||||||
* ---
|
* ---
|
||||||
*/
|
*/
|
||||||
@ -81,33 +81,33 @@ version (DisableBackends)
|
|||||||
}
|
}
|
||||||
else version (linux)
|
else version (linux)
|
||||||
{
|
{
|
||||||
import tanya.async.event.epoll;
|
import tanya.async.event.epoll;
|
||||||
version = Epoll;
|
version = Epoll;
|
||||||
}
|
}
|
||||||
else version (Windows)
|
else version (Windows)
|
||||||
{
|
{
|
||||||
import tanya.async.event.iocp;
|
import tanya.async.event.iocp;
|
||||||
version = IOCP;
|
version = IOCP;
|
||||||
}
|
}
|
||||||
else version (OSX)
|
else version (OSX)
|
||||||
{
|
{
|
||||||
version = Kqueue;
|
version = Kqueue;
|
||||||
}
|
}
|
||||||
else version (iOS)
|
else version (iOS)
|
||||||
{
|
{
|
||||||
version = Kqueue;
|
version = Kqueue;
|
||||||
}
|
}
|
||||||
else version (FreeBSD)
|
else version (FreeBSD)
|
||||||
{
|
{
|
||||||
version = Kqueue;
|
version = Kqueue;
|
||||||
}
|
}
|
||||||
else version (OpenBSD)
|
else version (OpenBSD)
|
||||||
{
|
{
|
||||||
version = Kqueue;
|
version = Kqueue;
|
||||||
}
|
}
|
||||||
else version (DragonFlyBSD)
|
else version (DragonFlyBSD)
|
||||||
{
|
{
|
||||||
version = Kqueue;
|
version = Kqueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,11 +115,11 @@ else version (DragonFlyBSD)
|
|||||||
*/
|
*/
|
||||||
enum Event : uint
|
enum Event : uint
|
||||||
{
|
{
|
||||||
none = 0x00, /// No events.
|
none = 0x00, /// No events.
|
||||||
read = 0x01, /// Non-blocking read call.
|
read = 0x01, /// Non-blocking read call.
|
||||||
write = 0x02, /// Non-blocking write call.
|
write = 0x02, /// Non-blocking write call.
|
||||||
accept = 0x04, /// Connection made.
|
accept = 0x04, /// Connection made.
|
||||||
error = 0x80000000, /// Sent when an error occurs.
|
error = 0x80000000, /// Sent when an error occurs.
|
||||||
}
|
}
|
||||||
|
|
||||||
alias EventMask = BitFlags!Event;
|
alias EventMask = BitFlags!Event;
|
||||||
@ -129,150 +129,150 @@ alias EventMask = BitFlags!Event;
|
|||||||
*/
|
*/
|
||||||
abstract class Loop
|
abstract class Loop
|
||||||
{
|
{
|
||||||
private bool done;
|
private bool done;
|
||||||
|
|
||||||
/// Pending watchers.
|
/// Pending watchers.
|
||||||
protected Queue!Watcher pendings;
|
protected Queue!Watcher pendings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Maximal event count can be got at a time
|
* Returns: Maximal event count can be got at a time
|
||||||
* (should be supported by the backend).
|
* (should be supported by the backend).
|
||||||
*/
|
*/
|
||||||
protected @property uint maxEvents()
|
protected @property uint maxEvents()
|
||||||
const pure nothrow @safe @nogc
|
const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return 128U;
|
return 128U;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
*/
|
*/
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
pendings = Queue!Watcher(MmapPool.instance);
|
pendings = Queue!Watcher(MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Frees loop internals.
|
* Frees loop internals.
|
||||||
*/
|
*/
|
||||||
~this() @nogc
|
~this() @nogc
|
||||||
{
|
{
|
||||||
foreach (w; pendings)
|
foreach (w; pendings)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(w);
|
MmapPool.instance.dispose(w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts the loop.
|
* Starts the loop.
|
||||||
*/
|
*/
|
||||||
void run() @nogc
|
void run() @nogc
|
||||||
{
|
{
|
||||||
done = false;
|
done = false;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
poll();
|
poll();
|
||||||
|
|
||||||
// Invoke pendings
|
// Invoke pendings
|
||||||
foreach (ref w; pendings)
|
foreach (ref w; pendings)
|
||||||
{
|
{
|
||||||
w.invoke();
|
w.invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
while (!done);
|
while (!done);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Break out of the loop.
|
* Break out of the loop.
|
||||||
*/
|
*/
|
||||||
void unloop() @safe pure nothrow @nogc
|
void unloop() @safe pure nothrow @nogc
|
||||||
{
|
{
|
||||||
done = true;
|
done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start watching.
|
* Start watching.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
void start(ConnectionWatcher watcher) @nogc
|
void start(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (watcher.active)
|
if (watcher.active)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
watcher.active = true;
|
watcher.active = true;
|
||||||
|
|
||||||
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
reify(watcher, EventMask(Event.none), EventMask(Event.accept));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop watching.
|
* Stop watching.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
*/
|
*/
|
||||||
void stop(ConnectionWatcher watcher) @nogc
|
void stop(ConnectionWatcher watcher) @nogc
|
||||||
{
|
{
|
||||||
if (!watcher.active)
|
if (!watcher.active)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
watcher.active = false;
|
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.
|
* Should be called if the backend configuration changes.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* watcher = Watcher.
|
* watcher = Watcher.
|
||||||
* oldEvents = The events were already set.
|
* oldEvents = The events were already set.
|
||||||
* events = The events should be set.
|
* events = The events should be set.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||||
*/
|
*/
|
||||||
abstract protected bool reify(SocketWatcher watcher,
|
abstract protected bool reify(SocketWatcher watcher,
|
||||||
EventMask oldEvents,
|
EventMask oldEvents,
|
||||||
EventMask events) @nogc;
|
EventMask events) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: The blocking time.
|
* Returns: The blocking time.
|
||||||
*/
|
*/
|
||||||
protected @property inout(Duration) blockTime()
|
protected @property inout(Duration) blockTime()
|
||||||
inout @safe pure nothrow @nogc
|
inout @safe pure nothrow @nogc
|
||||||
{
|
{
|
||||||
// Don't block if we have to do.
|
// Don't block if we have to do.
|
||||||
return pendings.empty ? blockTime_ : Duration.zero;
|
return pendings.empty ? blockTime_ : Duration.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the blocking time for IO watchers.
|
* Sets the blocking time for IO watchers.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* blockTime = The blocking time. Cannot be larger than
|
* blockTime = The blocking time. Cannot be larger than
|
||||||
* $(D_PSYMBOL maxBlockTime).
|
* $(D_PSYMBOL maxBlockTime).
|
||||||
*/
|
*/
|
||||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
||||||
assert(!blockTime.isNegative);
|
assert(!blockTime.isNegative);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
blockTime_ = blockTime;
|
blockTime_ = blockTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the actual polling.
|
* Does the actual polling.
|
||||||
*/
|
*/
|
||||||
abstract protected void poll() @nogc;
|
abstract protected void poll() @nogc;
|
||||||
|
|
||||||
/// Maximal block time.
|
/// Maximal block time.
|
||||||
protected Duration blockTime_ = 1.dur!"minutes";
|
protected Duration blockTime_ = 1.dur!"minutes";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -280,17 +280,17 @@ abstract class Loop
|
|||||||
*/
|
*/
|
||||||
class BadLoopException : Exception
|
class BadLoopException : Exception
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* file = The file where the exception occurred.
|
* file = The file where the exception occurred.
|
||||||
* line = The line number where the exception occurred.
|
* line = The line number where the exception occurred.
|
||||||
* next = The previous exception in the chain of exceptions, if any.
|
* next = The previous exception in the chain of exceptions, if any.
|
||||||
*/
|
*/
|
||||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
||||||
pure nothrow const @safe @nogc
|
pure nothrow const @safe @nogc
|
||||||
{
|
{
|
||||||
super("Event loop cannot be initialized.", file, line, next);
|
super("Event loop cannot be initialized.", file, line, next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -302,24 +302,24 @@ class BadLoopException : Exception
|
|||||||
*/
|
*/
|
||||||
@property Loop defaultLoop() @nogc
|
@property Loop defaultLoop() @nogc
|
||||||
{
|
{
|
||||||
if (defaultLoop_ !is null)
|
if (defaultLoop_ !is null)
|
||||||
{
|
{
|
||||||
return defaultLoop_;
|
return defaultLoop_;
|
||||||
}
|
}
|
||||||
version (Epoll)
|
version (Epoll)
|
||||||
{
|
{
|
||||||
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
defaultLoop_ = MmapPool.instance.make!EpollLoop;
|
||||||
}
|
}
|
||||||
else version (IOCP)
|
else version (IOCP)
|
||||||
{
|
{
|
||||||
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
defaultLoop_ = MmapPool.instance.make!IOCPLoop;
|
||||||
}
|
}
|
||||||
else version (Kqueue)
|
else version (Kqueue)
|
||||||
{
|
{
|
||||||
import tanya.async.event.kqueue;
|
import tanya.async.event.kqueue;
|
||||||
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
defaultLoop_ = MmapPool.instance.make!KqueueLoop;
|
||||||
}
|
}
|
||||||
return defaultLoop_;
|
return defaultLoop_;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -331,16 +331,16 @@ class BadLoopException : Exception
|
|||||||
* your implementation to this property.
|
* your implementation to this property.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* loop = The event loop.
|
* loop = The event loop.
|
||||||
*/
|
*/
|
||||||
@property void defaultLoop(Loop loop) @nogc
|
@property void defaultLoop(Loop loop) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(loop !is null);
|
assert(loop !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
defaultLoop_ = loop;
|
defaultLoop_ = loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Loop defaultLoop_;
|
private Loop defaultLoop_;
|
||||||
|
@ -18,28 +18,28 @@ import tanya.async.transport;
|
|||||||
*/
|
*/
|
||||||
interface Protocol
|
interface Protocol
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* data = Read data.
|
* data = Read data.
|
||||||
*/
|
*/
|
||||||
void received(in ubyte[] data) @nogc;
|
void received(in ubyte[] data) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is made.
|
* Called when a connection is made.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* transport = Protocol transport.
|
* transport = Protocol transport.
|
||||||
*/
|
*/
|
||||||
void connected(DuplexTransport transport) @nogc;
|
void connected(DuplexTransport transport) @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a connection is lost.
|
* Called when a connection is lost.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* exception = $(D_PSYMBOL Exception) if an error caused
|
* exception = $(D_PSYMBOL Exception) if an error caused
|
||||||
* the disconnect, $(D_KEYWORD null) otherwise.
|
* the disconnect, $(D_KEYWORD null) otherwise.
|
||||||
*/
|
*/
|
||||||
void disconnected(SocketException exception) @nogc;
|
void disconnected(SocketException exception) @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,13 +32,13 @@ interface ReadTransport : Transport
|
|||||||
*/
|
*/
|
||||||
interface WriteTransport : Transport
|
interface WriteTransport : Transport
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Write some data to the transport.
|
* Write some data to the transport.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* data = Data to send.
|
* data = Data to send.
|
||||||
*/
|
*/
|
||||||
void write(ubyte[] data) @nogc;
|
void write(ubyte[] data) @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,46 +46,46 @@ interface WriteTransport : Transport
|
|||||||
*/
|
*/
|
||||||
interface DuplexTransport : ReadTransport, WriteTransport
|
interface DuplexTransport : ReadTransport, WriteTransport
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns: Application protocol.
|
* Returns: Application protocol.
|
||||||
*
|
*
|
||||||
* Postcondition: $(D_INLINECODE protocol !is null)
|
* Postcondition: $(D_INLINECODE protocol !is null)
|
||||||
*/
|
*/
|
||||||
@property Protocol protocol() pure nothrow @safe @nogc
|
@property Protocol protocol() pure nothrow @safe @nogc
|
||||||
out (protocol)
|
out (protocol)
|
||||||
{
|
{
|
||||||
assert(protocol !is null);
|
assert(protocol !is null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switches the protocol.
|
* Switches the protocol.
|
||||||
*
|
*
|
||||||
* The protocol is deallocated by the event loop, it should currently be
|
* The protocol is deallocated by the event loop, it should currently be
|
||||||
* allocated with $(D_PSYMBOL MmapPool).
|
* allocated with $(D_PSYMBOL MmapPool).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* protocol = Application protocol.
|
* protocol = Application protocol.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE protocol !is null)
|
* Precondition: $(D_INLINECODE protocol !is null)
|
||||||
*/
|
*/
|
||||||
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
@property void protocol(Protocol protocol) pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(protocol !is null);
|
assert(protocol !is null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns $(D_PARAM true) if the transport is closing or closed.
|
* Returns $(D_PARAM true) if the transport is closing or closed.
|
||||||
*/
|
*/
|
||||||
bool isClosing() const pure nothrow @safe @nogc;
|
bool isClosing() const pure nothrow @safe @nogc;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the transport.
|
* Close the transport.
|
||||||
*
|
*
|
||||||
* Buffered data will be flushed. No more data will be received.
|
* Buffered data will be flushed. No more data will be received.
|
||||||
*/
|
*/
|
||||||
void close() @nogc;
|
void close() @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -93,8 +93,8 @@ interface DuplexTransport : ReadTransport, WriteTransport
|
|||||||
*/
|
*/
|
||||||
interface SocketTransport : Transport
|
interface SocketTransport : Transport
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Returns: Socket.
|
* Returns: Socket.
|
||||||
*/
|
*/
|
||||||
@property Socket socket() pure nothrow @safe @nogc;
|
@property Socket socket() pure nothrow @safe @nogc;
|
||||||
}
|
}
|
||||||
|
@ -27,13 +27,13 @@ import tanya.network.socket;
|
|||||||
*/
|
*/
|
||||||
abstract class Watcher
|
abstract class Watcher
|
||||||
{
|
{
|
||||||
/// Whether the watcher is active.
|
/// Whether the watcher is active.
|
||||||
bool active;
|
bool active;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoke some action on event.
|
* Invoke some action on event.
|
||||||
*/
|
*/
|
||||||
void invoke() @nogc;
|
void invoke() @nogc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,32 +41,32 @@ abstract class Watcher
|
|||||||
*/
|
*/
|
||||||
abstract class SocketWatcher : Watcher
|
abstract class SocketWatcher : Watcher
|
||||||
{
|
{
|
||||||
/// Watched socket.
|
/// Watched socket.
|
||||||
protected Socket socket_;
|
protected Socket socket_;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE socket !is null)
|
* Precondition: $(D_INLINECODE socket !is null)
|
||||||
*/
|
*/
|
||||||
this(Socket socket) pure nothrow @safe @nogc
|
this(Socket socket) pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
socket_ = socket;
|
socket_ = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Socket.
|
* Returns: Socket.
|
||||||
*/
|
*/
|
||||||
@property Socket socket() pure nothrow @safe @nogc
|
@property Socket socket() pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return socket_;
|
return socket_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,44 +74,44 @@ abstract class SocketWatcher : Watcher
|
|||||||
*/
|
*/
|
||||||
class ConnectionWatcher : SocketWatcher
|
class ConnectionWatcher : SocketWatcher
|
||||||
{
|
{
|
||||||
/// Incoming connection queue.
|
/// Incoming connection queue.
|
||||||
Queue!DuplexTransport incoming;
|
Queue!DuplexTransport incoming;
|
||||||
|
|
||||||
private Protocol delegate() @nogc protocolFactory;
|
private Protocol delegate() @nogc protocolFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* socket = Socket.
|
* socket = Socket.
|
||||||
*/
|
*/
|
||||||
this(Socket socket) @nogc
|
this(Socket socket) @nogc
|
||||||
{
|
{
|
||||||
super(socket);
|
super(socket);
|
||||||
incoming = Queue!DuplexTransport(MmapPool.instance);
|
incoming = Queue!DuplexTransport(MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* P = Protocol should be used.
|
* P = Protocol should be used.
|
||||||
*/
|
*/
|
||||||
void setProtocol(P : Protocol)() @nogc
|
void setProtocol(P : Protocol)() @nogc
|
||||||
{
|
{
|
||||||
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes new connection callback.
|
* Invokes new connection callback.
|
||||||
*/
|
*/
|
||||||
override void invoke() @nogc
|
override void invoke() @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
foreach (transport; incoming)
|
foreach (transport; incoming)
|
||||||
{
|
{
|
||||||
transport.protocol = protocolFactory();
|
transport.protocol = protocolFactory();
|
||||||
transport.protocol.connected(transport);
|
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
@ -333,9 +333,9 @@ struct WriteBuffer(T = ubyte)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
* size = Initial buffer size and the size by which the buffer will
|
* size = Initial buffer size and the size by which the buffer will
|
||||||
* grow.
|
* grow.
|
||||||
* allocator = Allocator.
|
* allocator = Allocator.
|
||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
|
* Precondition: $(D_INLINECODE size > 0 && allocator !is null)
|
||||||
*/
|
*/
|
||||||
@ -428,7 +428,7 @@ struct WriteBuffer(T = ubyte)
|
|||||||
* Appends data to the buffer.
|
* Appends data to the buffer.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
|
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
|
||||||
*/
|
*/
|
||||||
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
|
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
|
||||||
if (op == "~")
|
if (op == "~")
|
||||||
@ -535,7 +535,7 @@ struct WriteBuffer(T = ubyte)
|
|||||||
* appropriately. Always call it after $(D_PSYMBOL opIndex).
|
* appropriately. Always call it after $(D_PSYMBOL opIndex).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* length = Length of the written data.
|
* length = Length of the written data.
|
||||||
*
|
*
|
||||||
* Returns: $(D_KEYWORD this).
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
|
@ -14,9 +14,18 @@ module tanya.container.entry;
|
|||||||
|
|
||||||
package struct SEntry(T)
|
package struct SEntry(T)
|
||||||
{
|
{
|
||||||
/// Item content.
|
// Item content.
|
||||||
T content;
|
T content;
|
||||||
|
|
||||||
/// Next item.
|
// Next item.
|
||||||
SEntry* next;
|
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
@ -12,7 +12,8 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.container;
|
module tanya.container;
|
||||||
|
|
||||||
|
public import tanya.container.array;
|
||||||
public import tanya.container.buffer;
|
public import tanya.container.buffer;
|
||||||
public import tanya.container.list;
|
public import tanya.container.list;
|
||||||
public import tanya.container.vector;
|
public import tanya.container.string;
|
||||||
public import tanya.container.queue;
|
public import tanya.container.queue;
|
||||||
|
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
@ -16,7 +16,7 @@ import std.algorithm;
|
|||||||
import std.ascii;
|
import std.ascii;
|
||||||
import std.range;
|
import std.range;
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,6 +160,45 @@ struct Integer
|
|||||||
assert(integer == 7383520307673030126);
|
assert(integer == 7383520307673030126);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the integer from a two's complement representation.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* R = Range type.
|
||||||
|
* value = Range.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this(R)(R value,
|
||||||
|
shared Allocator allocator = defaultAllocator)
|
||||||
|
if (isBidirectionalRange!R && hasLength!R
|
||||||
|
&& is(Unqual!(ElementType!R) == ubyte))
|
||||||
|
{
|
||||||
|
this(Sign.positive, value, allocator);
|
||||||
|
|
||||||
|
if (!value.empty && ((value.front & 0x80) != 0))
|
||||||
|
{
|
||||||
|
// Negative number.
|
||||||
|
opOpAssign!"-"(exp2(countBits()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow @safe @nogc unittest
|
||||||
|
{
|
||||||
|
{
|
||||||
|
ubyte[8] range = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
auto integer = Integer(range[]);
|
||||||
|
assert(integer == 7383520307673030126);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ubyte[8] range = [ 0xe6, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
auto integer = Integer(range[]);
|
||||||
|
assert(integer == -1839851729181745682);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the integer.
|
* Copies the integer.
|
||||||
*/
|
*/
|
||||||
@ -178,12 +217,64 @@ struct Integer
|
|||||||
allocator.resize(this.rep, 0);
|
allocator.resize(this.rep, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private const short[16] bitCounts = [
|
||||||
|
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// Counts the number of LSBs before the first non-zero bit.
|
||||||
|
private ptrdiff_t countLSBs() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
if (this.size == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrdiff_t bits;
|
||||||
|
for (bits = 0; (bits < this.size) && (this.rep[bits] == 0); ++bits)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
digit nonZero = this.rep[bits];
|
||||||
|
bits *= digitBitCount;
|
||||||
|
|
||||||
|
/* now scan this digit until a 1 is found */
|
||||||
|
if ((nonZero & 0x01) == 0)
|
||||||
|
{
|
||||||
|
digit bitCountsPos;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bitCountsPos = nonZero & 0x0f;
|
||||||
|
bits += bitCounts[bitCountsPos];
|
||||||
|
nonZero >>= 4;
|
||||||
|
}
|
||||||
|
while (bitCountsPos == 0);
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Integer byte length.
|
* Returns: Number of bytes in the two's complement representation.
|
||||||
*/
|
*/
|
||||||
@property size_t length() const pure nothrow @safe @nogc
|
@property size_t length() const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return (countBits() + 7) / 8; // Round up.
|
if (this.sign)
|
||||||
|
{
|
||||||
|
const bc = countBits();
|
||||||
|
auto length = bc + (8 - (bc & 0x07));
|
||||||
|
|
||||||
|
if (((countLSBs() + 1) == bc) && ((bc & 0x07) == 0))
|
||||||
|
{
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
return length / 8;
|
||||||
|
}
|
||||||
|
else if (this.size == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (countBits() / 8) + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -245,16 +336,10 @@ struct Integer
|
|||||||
ref Integer opAssign(T)(T value) nothrow @safe @nogc
|
ref Integer opAssign(T)(T value) nothrow @safe @nogc
|
||||||
if (is(T == Integer))
|
if (is(T == Integer))
|
||||||
{
|
{
|
||||||
if (this.allocator is value.allocator)
|
swap(this.rep, value.rep);
|
||||||
{
|
swap(this.sign, value.sign);
|
||||||
swap(this.rep, value.rep);
|
swap(this.size, value.size);
|
||||||
swap(this.sign, value.sign);
|
swap(this.allocator_, value.allocator_);
|
||||||
swap(this.size, value.size);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this = value;
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1343,79 +1428,72 @@ struct Integer
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector!ubyte toVector() const nothrow @safe @nogc
|
// Returns 2^^n.
|
||||||
|
private Integer exp2(size_t n) const nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
Vector!ubyte vector;
|
auto ret = Integer(allocator);
|
||||||
bool firstBit;
|
const bytes = n / digitBitCount;
|
||||||
ubyte carry;
|
|
||||||
|
ret.grow(bytes + 1);
|
||||||
|
ret.size = bytes + 1;
|
||||||
|
ret.rep[bytes] = (cast(digit) 1) << (n % digitBitCount);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Two's complement representation of the integer.
|
||||||
|
*/
|
||||||
|
Array!ubyte toArray() const nothrow @safe @nogc
|
||||||
|
out (array)
|
||||||
|
{
|
||||||
|
assert(array.length == length);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
Array!ubyte array;
|
||||||
|
|
||||||
if (this.size == 0)
|
if (this.size == 0)
|
||||||
{
|
{
|
||||||
return vector;
|
return array;
|
||||||
}
|
}
|
||||||
vector.reserve(this.size * digit.sizeof);
|
const bc = countBits();
|
||||||
|
const remainingBits = bc & 0x07;
|
||||||
|
|
||||||
// The first digit needs extra handling since it can have leading
|
array.reserve(bc / 8);
|
||||||
// non significant zeros.
|
if (remainingBits == 0)
|
||||||
int digitCount = digitBitCount - 8;
|
|
||||||
const first = this.rep[this.size - 1];
|
|
||||||
const prevBitCount = ((this.size - 1) * digitBitCount);
|
|
||||||
const fullBytesBitCount = ((prevBitCount - 1) / 8 + 1) * 8;
|
|
||||||
|
|
||||||
// Find out the right alignment of the first byte.
|
|
||||||
if ((fullBytesBitCount - prevBitCount) == 0)
|
|
||||||
{
|
{
|
||||||
digitCount -= digit.sizeof * 8 - digitBitCount;
|
array.insertBack(ubyte.init);
|
||||||
|
|
||||||
}
|
}
|
||||||
for (; digitCount >= 0; digitCount -= 8)
|
|
||||||
|
Integer tmp;
|
||||||
|
if (this.sign)
|
||||||
{
|
{
|
||||||
if (firstBit || ((first >> digitCount) != 0))
|
auto length = bc + (8 - remainingBits);
|
||||||
|
|
||||||
|
if (((countLSBs() + 1) == bc) && (remainingBits == 0))
|
||||||
{
|
{
|
||||||
firstBit = true;
|
length -= 8;
|
||||||
vector.insertBack(cast(ubyte) (first >> digitCount));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (digitCount >= -8)
|
tmp = exp2(length) + this;
|
||||||
{
|
|
||||||
carry = (first << -digitCount) & 0xff;
|
|
||||||
digitCount += digitBitCount;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
carry = 0;
|
tmp = this;
|
||||||
digitCount = digitBitCount - 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach_reverse (d; this.rep[0 .. this.size - 1])
|
do
|
||||||
{
|
{
|
||||||
if (carry != 0) // Check the carry from the previous digit.
|
array.insertBack(cast(ubyte) (tmp.rep[0] & 0xff));
|
||||||
{
|
tmp >>= 8;
|
||||||
vector.insertBack(cast(ubyte) (carry | (d >> digitCount)));
|
|
||||||
digitCount -= 8;
|
|
||||||
}
|
|
||||||
// Write the digit by bytes.
|
|
||||||
for (; digitCount >= 0; digitCount -= 8)
|
|
||||||
{
|
|
||||||
vector.insertBack(cast(ubyte) (d >> digitCount));
|
|
||||||
}
|
|
||||||
// Check for an incomplete byte.
|
|
||||||
if (digitCount >= -8)
|
|
||||||
{
|
|
||||||
carry = (d << -digitCount) & 0xff;
|
|
||||||
digitCount += digitBitCount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
carry = 0;
|
|
||||||
digitCount = digitBitCount - 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (carry != 0)
|
|
||||||
{
|
|
||||||
vector.insertBack(cast(ubyte) (carry >> (digitBitCount - digitCount)));
|
|
||||||
}
|
}
|
||||||
|
while (tmp != 0);
|
||||||
|
|
||||||
return vector;
|
array[].reverse();
|
||||||
|
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1425,15 +1503,15 @@ struct Integer
|
|||||||
auto integer = Integer(0x66778899aabbddee);
|
auto integer = Integer(0x66778899aabbddee);
|
||||||
ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto integer = Integer(0x03);
|
auto integer = Integer(0x03);
|
||||||
ubyte[1] expected = [ 0x03 ];
|
ubyte[1] expected = [ 0x03 ];
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ubyte[63] expected = [
|
ubyte[63] expected = [
|
||||||
@ -1448,8 +1526,8 @@ struct Integer
|
|||||||
];
|
];
|
||||||
auto integer = Integer(Sign.positive, expected[]);
|
auto integer = Integer(Sign.positive, expected[]);
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ubyte[14] expected = [
|
ubyte[14] expected = [
|
||||||
@ -1458,8 +1536,8 @@ struct Integer
|
|||||||
];
|
];
|
||||||
auto integer = Integer(Sign.positive, expected[]);
|
auto integer = Integer(Sign.positive, expected[]);
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -18,7 +18,19 @@ import std.range;
|
|||||||
import std.traits;
|
import std.traits;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
package(tanya) final class RefCountedStore(T)
|
private template Payload(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface) || isArray!T)
|
||||||
|
{
|
||||||
|
alias Payload = T;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias Payload = T*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RefCountedStore(T)
|
||||||
{
|
{
|
||||||
T payload;
|
T payload;
|
||||||
size_t counter = 1;
|
size_t counter = 1;
|
||||||
@ -34,7 +46,7 @@ package(tanya) final class RefCountedStore(T)
|
|||||||
mixin("return " ~ op ~ "counter;");
|
mixin("return " ~ op ~ "counter;");
|
||||||
}
|
}
|
||||||
|
|
||||||
int opCmp(size_t counter)
|
int opCmp(const size_t counter)
|
||||||
{
|
{
|
||||||
if (this.counter > counter)
|
if (this.counter > counter)
|
||||||
{
|
{
|
||||||
@ -50,7 +62,7 @@ package(tanya) final class RefCountedStore(T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int opEquals(size_t counter)
|
int opEquals(const size_t counter)
|
||||||
{
|
{
|
||||||
return this.counter == counter;
|
return this.counter == counter;
|
||||||
}
|
}
|
||||||
@ -81,15 +93,7 @@ private void unifiedDeleter(T)(RefCountedStore!T storage,
|
|||||||
*/
|
*/
|
||||||
struct RefCounted(T)
|
struct RefCounted(T)
|
||||||
{
|
{
|
||||||
static if (is(T == class) || is(T == interface) || isArray!T)
|
private alias Storage = RefCountedStore!(Payload!T);
|
||||||
{
|
|
||||||
private alias Payload = T;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
private alias Payload = T*;
|
|
||||||
}
|
|
||||||
private alias Storage = RefCountedStore!Payload;
|
|
||||||
|
|
||||||
private Storage storage;
|
private Storage storage;
|
||||||
private void function(Storage storage,
|
private void function(Storage storage,
|
||||||
@ -112,13 +116,18 @@ struct RefCounted(T)
|
|||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE allocator !is null)
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
*/
|
*/
|
||||||
this()(auto ref Payload value,
|
this()(auto ref Payload!T value,
|
||||||
shared Allocator allocator = defaultAllocator)
|
shared Allocator allocator = defaultAllocator)
|
||||||
{
|
{
|
||||||
this(allocator);
|
this(allocator);
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
|
|
||||||
move(value, this.storage.payload);
|
move(value, this.storage.payload);
|
||||||
|
static if (__traits(isRef, value))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
@ -139,7 +148,7 @@ struct RefCounted(T)
|
|||||||
{
|
{
|
||||||
if (count != 0)
|
if (count != 0)
|
||||||
{
|
{
|
||||||
++storage;
|
++this.storage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +161,7 @@ struct RefCounted(T)
|
|||||||
{
|
{
|
||||||
if (this.storage !is null && !(this.storage.counter && --this.storage))
|
if (this.storage !is null && !(this.storage.counter && --this.storage))
|
||||||
{
|
{
|
||||||
deleter(storage, allocator);
|
deleter(this.storage, allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,34 +172,34 @@ struct RefCounted(T)
|
|||||||
* If it is the last reference of the previously owned object,
|
* If it is the last reference of the previously owned object,
|
||||||
* it will be destroyed.
|
* it will be destroyed.
|
||||||
*
|
*
|
||||||
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
* To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
||||||
*
|
*
|
||||||
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
* be used. If you need a different allocator, create a new
|
* be used. If you need a different allocator, create a new
|
||||||
* $(D_PSYMBOL RefCounted) and assign it.
|
* $(D_PSYMBOL RefCounted) and assign it.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* rhs = $(D_KEYWORD this).
|
* rhs = New object.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
ref typeof(this) opAssign()(auto ref Payload rhs)
|
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||||
{
|
{
|
||||||
if (this.storage is null)
|
if (this.storage is null)
|
||||||
{
|
{
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
}
|
}
|
||||||
else if (this.storage > 1)
|
else if (this.storage > 1)
|
||||||
{
|
{
|
||||||
--this.storage;
|
--this.storage;
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
|
||||||
assert(this.storage.counter != 0);
|
|
||||||
finalize(this.storage.payload);
|
finalize(this.storage.payload);
|
||||||
this.storage.payload = Payload.init;
|
this.storage.payload = Payload!T.init;
|
||||||
}
|
}
|
||||||
move(rhs, this.storage.payload);
|
move(rhs, this.storage.payload);
|
||||||
return this;
|
return this;
|
||||||
@ -206,15 +215,13 @@ struct RefCounted(T)
|
|||||||
else if (this.storage > 1)
|
else if (this.storage > 1)
|
||||||
{
|
{
|
||||||
--this.storage;
|
--this.storage;
|
||||||
this.storage = null;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
deleter(this.storage, allocator);
|
||||||
assert(this.storage.counter != 0);
|
|
||||||
finalize(this.storage.payload);
|
|
||||||
this.storage.payload = Payload.init;
|
|
||||||
}
|
}
|
||||||
|
this.storage = null;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,8 +236,10 @@ struct RefCounted(T)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Reference to the owned object.
|
* Returns: Reference to the owned object.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE cound > 0).
|
||||||
*/
|
*/
|
||||||
inout(Payload) get() inout pure nothrow @safe @nogc
|
Payload!T get() pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(count > 0, "Attempted to access an uninitialized reference.");
|
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||||
@ -240,7 +249,7 @@ struct RefCounted(T)
|
|||||||
return storage.payload;
|
return storage.payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
static if (isPointer!Payload)
|
version (D_Ddoc)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
@ -251,6 +260,11 @@ struct RefCounted(T)
|
|||||||
*
|
*
|
||||||
* Returns: Reference to the pointed value.
|
* Returns: Reference to the pointed value.
|
||||||
*/
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*");
|
||||||
|
}
|
||||||
|
else static if (isPointer!(Payload!T))
|
||||||
|
{
|
||||||
ref T opUnary(string op)()
|
ref T opUnary(string op)()
|
||||||
if (op == "*")
|
if (op == "*")
|
||||||
{
|
{
|
||||||
@ -352,6 +366,44 @@ private unittest
|
|||||||
assert(rc.count == 1);
|
assert(rc.count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!int rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
rc = null;
|
||||||
|
assert(!rc.isInitialized);
|
||||||
|
assert(rc.count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
func(rc);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
rc = null;
|
||||||
|
assert(!rc.isInitialized);
|
||||||
|
assert(rc.count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(*rc == 5);
|
||||||
|
|
||||||
|
void func(RefCounted!int rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
rc = defaultAllocator.refCounted!int(4);
|
||||||
|
assert(*rc == 4);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
assert(*rc == 5);
|
||||||
|
}
|
||||||
|
|
||||||
private unittest
|
private unittest
|
||||||
{
|
{
|
||||||
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||||
@ -414,13 +466,13 @@ body
|
|||||||
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||||
rc.storage.payload = emplace!T(ptr, args);
|
rc.storage.payload = emplace!T(ptr, args);
|
||||||
}
|
}
|
||||||
rc.deleter = &unifiedDeleter!(RefCounted!T.Payload);
|
rc.deleter = &unifiedDeleter!(Payload!T);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||||
* $(D_PSYMBOL RefCounted) using.
|
* $(D_PSYMBOL RefCounted).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Array type.
|
* T = Array type.
|
||||||
@ -432,7 +484,8 @@ body
|
|||||||
* Precondition: $(D_INLINECODE allocator !is null
|
* Precondition: $(D_INLINECODE allocator !is null
|
||||||
* && size <= size_t.max / ElementType!T.sizeof)
|
* && size <= size_t.max / ElementType!T.sizeof)
|
||||||
*/
|
*/
|
||||||
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
|
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
|
||||||
|
@trusted
|
||||||
if (isArray!T)
|
if (isArray!T)
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@ -441,8 +494,7 @@ in
|
|||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto payload = cast(T) allocator.allocate(ElementType!T.sizeof * size);
|
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||||
initializeAll(payload);
|
|
||||||
return RefCounted!T(payload, allocator);
|
return RefCounted!T(payload, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,3 +547,266 @@ private @nogc unittest
|
|||||||
auto rc = defaultAllocator.refCounted!(int[])(5);
|
auto rc = defaultAllocator.refCounted!(int[])(5);
|
||||||
assert(rc.length == 5);
|
assert(rc.length == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @nogc unittest
|
||||||
|
{
|
||||||
|
static bool destroyed = false;
|
||||||
|
|
||||||
|
struct F
|
||||||
|
{
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!F();
|
||||||
|
}
|
||||||
|
assert(destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Value type.
|
||||||
|
*/
|
||||||
|
struct Scoped(T)
|
||||||
|
{
|
||||||
|
private Payload!T payload;
|
||||||
|
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(payload is null || allocator_ !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
||||||
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Value whose ownership is taken over.
|
||||||
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
||||||
|
* allocate/deallocate internal storage.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this()(auto ref Payload!T value,
|
||||||
|
shared Allocator allocator = defaultAllocator)
|
||||||
|
{
|
||||||
|
this(allocator);
|
||||||
|
|
||||||
|
move(value, this.payload);
|
||||||
|
static if (__traits(isRef, value))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL Scoped) is noncopyable.
|
||||||
|
*/
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the owned object.
|
||||||
|
*/
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
if (this.payload !is null)
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized this $(D_PARAM Scoped) and takes ownership over
|
||||||
|
* $(D_PARAM rhs).
|
||||||
|
*
|
||||||
|
* To reset $(D_PSYMBOL Scoped) assign $(D_KEYWORD null).
|
||||||
|
*
|
||||||
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
|
* be used. If you need a different allocator, create a new
|
||||||
|
* $(D_PSYMBOL Scoped) and assign it.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* rhs = New object.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD this).
|
||||||
|
*/
|
||||||
|
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
move(rhs, this.payload);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(null))
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(this) rhs)
|
||||||
|
{
|
||||||
|
swap(this.allocator_, rhs.allocator_);
|
||||||
|
swap(this.payload, rhs.payload);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Reference to the owned object.
|
||||||
|
*/
|
||||||
|
Payload!T get() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
version (D_Ddoc)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* op = Operation.
|
||||||
|
*
|
||||||
|
* Dereferences the pointer. It is defined only for pointers, not for
|
||||||
|
* reference types like classes, that can be accessed directly.
|
||||||
|
*
|
||||||
|
* Returns: Reference to the pointed value.
|
||||||
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*");
|
||||||
|
}
|
||||||
|
else static if (isPointer!(Payload!T))
|
||||||
|
{
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*")
|
||||||
|
{
|
||||||
|
return *payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
alias get this;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc unittest
|
||||||
|
{
|
||||||
|
auto p = defaultAllocator.make!int(5);
|
||||||
|
auto s = Scoped!int(p, defaultAllocator);
|
||||||
|
assert(p is null);
|
||||||
|
assert(*s == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc unittest
|
||||||
|
{
|
||||||
|
static bool destroyed = false;
|
||||||
|
|
||||||
|
struct F
|
||||||
|
{
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
|
||||||
|
}
|
||||||
|
assert(destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||||
|
* $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for
|
||||||
|
* the constructor of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the constructed object.
|
||||||
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
||||||
|
* allocator = Allocator.
|
||||||
|
* args = Constructor arguments of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
|
||||||
|
if (!is(T == interface) && !isAbstractClass!T
|
||||||
|
&& !isAssociativeArray!T && !isArray!T)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
auto payload = allocator.make!(T, shared Allocator, A)(args);
|
||||||
|
return Scoped!T(payload, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||||
|
* $(D_PSYMBOL Scoped).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Array type.
|
||||||
|
* size = Array size.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null
|
||||||
|
* && size <= size_t.max / ElementType!T.sizeof)
|
||||||
|
*/
|
||||||
|
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
|
||||||
|
@trusted
|
||||||
|
if (isArray!T)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
assert(size <= size_t.max / ElementType!T.sizeof);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||||
|
return Scoped!T(payload, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(typeof(defaultAllocator.scoped!B(5))));
|
||||||
|
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto s = defaultAllocator.scoped!int(5);
|
||||||
|
assert(*s == 5);
|
||||||
|
|
||||||
|
s = null;
|
||||||
|
assert(s is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto s = defaultAllocator.scoped!int(5);
|
||||||
|
assert(*s == 5);
|
||||||
|
|
||||||
|
s = defaultAllocator.scoped!int(4);
|
||||||
|
assert(*s == 4);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user