Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
ab930657b6 | |||
291920b479 | |||
999c9bdb0f | |||
405b6d9f9f | |||
87b74b2542 | |||
d6514cb515 | |||
976eb4bfbc | |||
fb843e3473 | |||
f3d48234c0 | |||
254b881da6 | |||
8e0b742748 | |||
a35e04c049 | |||
4271c8583e | |||
e27d0fe58c | |||
67952dabdb | |||
b8d5d4c2bd | |||
b6413823cd | |||
48e355b87f | |||
b3f4ea572e | |||
c73e704421 | |||
0561e96f21 | |||
86d87430da | |||
0156c5a883 | |||
c966b42ac3 | |||
200fff3714 | |||
28755b4d01 | |||
8bd6a14988 | |||
b41dcc9f37 | |||
38addb7a5b | |||
f7fb89fed0 | |||
e32af2d09e | |||
f1bc4dc2e2 | |||
40857e69b7 | |||
c1fb89af99 | |||
061cd6264b | |||
f437dafa6b | |||
54d0597657 | |||
ab9f96e0c7 | |||
711855474c | |||
b20f367aa8 | |||
a2dadda511 | |||
77dca31261 | |||
b87aed4395 | |||
42bbb3b023 | |||
4309a30dfe | |||
9362287938 | |||
78bd901339 | |||
c8e6d44f7b | |||
f75433e0e6 | |||
fa607141e4 | |||
b3fdd6fd4a | |||
86c08e7af6 | |||
1c5796eb96 | |||
f7f92e7906 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -4,3 +4,7 @@
|
||||
# D
|
||||
.dub
|
||||
__test__*__
|
||||
__test__*__.core
|
||||
|
||||
/docs/
|
||||
/docs.json
|
||||
|
17
.travis.yml
Normal file
17
.travis.yml
Normal file
@ -0,0 +1,17 @@
|
||||
sudo: false
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
language: d
|
||||
|
||||
d:
|
||||
- dmd-2.072.2
|
||||
- dmd-2.071.2
|
||||
|
||||
env:
|
||||
matrix:
|
||||
- ARCH=x86_64
|
||||
|
||||
script:
|
||||
- dub test --arch=$ARCH
|
71
README.md
71
README.md
@ -1,4 +1,69 @@
|
||||
# tanya
|
||||
# Tanya
|
||||
|
||||
tanya's mission is to provide a GC-free, general purpose library for D
|
||||
programming language for developing servers.
|
||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://code.dlang.org/packages/tanya)
|
||||
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||
|
||||
Tanya is a general purpose library for D programming language.
|
||||
|
||||
Its aim is to simplify the manual memory management in D and to provide a
|
||||
guarantee with @nogc attribute that there are no hidden allocations on the
|
||||
Garbage Collector heap. Everything in the library is usable in @nogc code.
|
||||
Tanya extends Phobos functionality and provides alternative implementations for
|
||||
data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||
|
||||
## Overview
|
||||
|
||||
Tanya consists of the following packages:
|
||||
|
||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||
* `container`: Queue, Vector, Singly linked list, buffers.
|
||||
* `crypto`: Work in progress TLS implementation.
|
||||
* `math`: Multiple precision integer and a set of functions.
|
||||
* `memory`: Tools for manual memory management (allocator, reference counting,
|
||||
helper functions).
|
||||
* `network`: URL-Parsing, sockets.
|
||||
|
||||
### Supported compilers
|
||||
|
||||
* dmd 2.072.2
|
||||
* dmd 2.071.2
|
||||
|
||||
### Current status
|
||||
|
||||
The library is currently under development, but some parts of it can already be
|
||||
used.
|
||||
|
||||
`network` and `async` exist for quite some time and could be better tested than
|
||||
other components.
|
||||
|
||||
Containers were newly reworked and the API won't change significantly, but will
|
||||
be only extended. The same is true for the `memory` package.
|
||||
|
||||
`math` package contains an arbitrary precision integer implementation that has
|
||||
a stable API (that mostly consists of operator overloads), but still needs
|
||||
testing and work on its performance.
|
||||
|
||||
I'm currently mostly working on `crypto` that is not a complete cryptographic
|
||||
suite, but contains (will contain) algorithm implementations required by TLS.
|
||||
|
||||
### Further characteristics
|
||||
|
||||
* Tanya is a native D library.
|
||||
|
||||
* Documentation and usage examples can be found in the source code.
|
||||
Online documentation will be published soon.
|
||||
|
||||
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
|
||||
is being tested on Windows and FreeBSD as well.
|
||||
|
||||
* The library isn't thread-safe. Thread-safity should be added later.
|
||||
|
||||
## Contributing
|
||||
|
||||
Since I'm mostly busy writing new code and implementing new features I would
|
||||
appreciate, if anyone uses the library. It would help me to improve the
|
||||
codebase and fix issues.
|
||||
|
||||
Feel free to contact me if you have any questions.
|
||||
|
@ -26,6 +26,13 @@ import core.sys.posix.unistd;
|
||||
import core.time;
|
||||
import std.algorithm.comparison;
|
||||
|
||||
extern (C) nothrow @nogc
|
||||
{
|
||||
int epoll_create1(int flags);
|
||||
int epoll_ctl (int epfd, int op, int fd, epoll_event *event);
|
||||
int epoll_wait (int epfd, epoll_event *events, int maxevents, int timeout);
|
||||
}
|
||||
|
||||
class EpollLoop : SelectorLoop
|
||||
{
|
||||
protected int fd;
|
||||
@ -34,20 +41,20 @@ class EpollLoop : SelectorLoop
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
this() @nogc
|
||||
{
|
||||
if ((fd = epoll_create1(EPOLL_CLOEXEC)) < 0)
|
||||
{
|
||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
||||
}
|
||||
super();
|
||||
events = MmapPool.instance.makeArray!epoll_event(maxEvents);
|
||||
MmapPool.instance.resizeArray(events, maxEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free loop internals.
|
||||
*/
|
||||
~this()
|
||||
~this() @nogc
|
||||
{
|
||||
MmapPool.instance.dispose(events);
|
||||
close(fd);
|
||||
@ -63,7 +70,9 @@ class EpollLoop : SelectorLoop
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if the operation was successful.
|
||||
*/
|
||||
protected override bool reify(ConnectionWatcher watcher, EventMask oldEvents, EventMask events)
|
||||
protected override bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events) @nogc
|
||||
in
|
||||
{
|
||||
assert(watcher !is null);
|
||||
@ -97,7 +106,7 @@ class EpollLoop : SelectorLoop
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected override void poll()
|
||||
protected override void poll() @nogc
|
||||
{
|
||||
// Don't block
|
||||
immutable timeout = cast(immutable int) blockTime.total!"msecs";
|
||||
@ -107,7 +116,7 @@ class EpollLoop : SelectorLoop
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw theAllocator.make!BadLoopException();
|
||||
throw defaultAllocator.make!BadLoopException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -150,7 +159,7 @@ class EpollLoop : SelectorLoop
|
||||
}
|
||||
else if (io.output.length)
|
||||
{
|
||||
swapPendings.insertBack(io);
|
||||
swapPendings.enqueue(io);
|
||||
}
|
||||
}
|
||||
else if (events[i].events & EPOLLOUT)
|
||||
|
@ -30,14 +30,14 @@ class IOCPStreamTransport : StreamTransport
|
||||
{
|
||||
private OverlappedConnectedSocket socket_;
|
||||
|
||||
private WriteBuffer input;
|
||||
private WriteBuffer!ubyte input;
|
||||
|
||||
/**
|
||||
* Creates new completion port transport.
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(OverlappedConnectedSocket socket)
|
||||
this(OverlappedConnectedSocket socket) @nogc
|
||||
in
|
||||
{
|
||||
assert(socket !is null);
|
||||
@ -45,15 +45,11 @@ class IOCPStreamTransport : StreamTransport
|
||||
body
|
||||
{
|
||||
socket_ = socket;
|
||||
input = MmapPool.instance.make!WriteBuffer();
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(input);
|
||||
}
|
||||
|
||||
@property inout(OverlappedConnectedSocket) socket() inout pure nothrow @safe @nogc
|
||||
@property inout(OverlappedConnectedSocket) socket()
|
||||
inout pure nothrow @safe @nogc
|
||||
{
|
||||
return socket_;
|
||||
}
|
||||
@ -64,7 +60,7 @@ class IOCPStreamTransport : StreamTransport
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data)
|
||||
void write(ubyte[] data) @nogc
|
||||
{
|
||||
immutable empty = input.length == 0;
|
||||
input ~= data;
|
||||
@ -94,14 +90,15 @@ class IOCPLoop : Loop
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
|
||||
completionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
||||
if (!completionPort)
|
||||
{
|
||||
throw theAllocator.make!BadLoopException("Creating completion port failed");
|
||||
throw make!BadLoopException(defaultAllocator,
|
||||
"Creating completion port failed");
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,7 +114,7 @@ class IOCPLoop : Loop
|
||||
*/
|
||||
override protected bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events)
|
||||
EventMask events) @nogc
|
||||
{
|
||||
SocketState overlapped;
|
||||
if (!(oldEvents & Event.accept) && (events & Event.accept))
|
||||
@ -141,7 +138,7 @@ class IOCPLoop : Loop
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
theAllocator.dispose(e);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -173,7 +170,7 @@ class IOCPLoop : Loop
|
||||
catch (SocketException e)
|
||||
{
|
||||
MmapPool.instance.dispose(overlapped);
|
||||
theAllocator.dispose(e);
|
||||
defaultAllocator.dispose(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -184,7 +181,7 @@ class IOCPLoop : Loop
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
override protected void poll()
|
||||
override protected void poll() @nogc
|
||||
{
|
||||
DWORD lpNumberOfBytes;
|
||||
ULONG_PTR key;
|
||||
@ -221,11 +218,11 @@ class IOCPLoop : Loop
|
||||
auto transport = MmapPool.instance.make!IOCPStreamTransport(socket);
|
||||
auto io = MmapPool.instance.make!IOWatcher(transport, connection.protocol);
|
||||
|
||||
connection.incoming.insertBack(io);
|
||||
connection.incoming.enqueue(io);
|
||||
|
||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
|
||||
swapPendings.insertBack(connection);
|
||||
swapPendings.enqueue(connection);
|
||||
listener.beginAccept(overlapped);
|
||||
break;
|
||||
case OverlappedSocketEvent.read:
|
||||
@ -267,7 +264,7 @@ class IOCPLoop : Loop
|
||||
{
|
||||
transport.socket.beginReceive(io.output[], overlapped);
|
||||
}
|
||||
swapPendings.insertBack(io);
|
||||
swapPendings.enqueue(io);
|
||||
}
|
||||
break;
|
||||
case OverlappedSocketEvent.write:
|
||||
|
@ -6,46 +6,51 @@
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.async.event.kqueue;
|
||||
|
||||
version (OSX)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (TVOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (WatchOS)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = MissingKevent;
|
||||
version = MacBSD;
|
||||
}
|
||||
|
||||
version (MissingKevent)
|
||||
version (MacBSD):
|
||||
|
||||
import core.stdc.stdint; // intptr_t, uintptr_t
|
||||
import core.sys.posix.time; // timespec
|
||||
|
||||
void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) pure nothrow @nogc
|
||||
{
|
||||
extern (C):
|
||||
nothrow:
|
||||
@nogc:
|
||||
*kevp = kevent_t(args);
|
||||
}
|
||||
|
||||
import core.stdc.stdint; // intptr_t, uintptr_t
|
||||
import core.sys.posix.time; // timespec
|
||||
|
||||
enum : short
|
||||
{
|
||||
enum : short
|
||||
{
|
||||
EVFILT_READ = -1,
|
||||
EVFILT_WRITE = -2,
|
||||
EVFILT_AIO = -3, /* attached to aio requests */
|
||||
@ -58,25 +63,20 @@ version (MissingKevent)
|
||||
EVFILT_USER = -10, /* User events */
|
||||
EVFILT_VM = -12, /* virtual memory events */
|
||||
EVFILT_SYSCOUNT = 11
|
||||
}
|
||||
}
|
||||
|
||||
extern(D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args)
|
||||
{
|
||||
*kevp = kevent_t(args);
|
||||
}
|
||||
|
||||
struct kevent_t
|
||||
{
|
||||
struct kevent_t
|
||||
{
|
||||
uintptr_t ident; /* identifier for this event */
|
||||
short filter; /* filter for event */
|
||||
ushort flags;
|
||||
uint fflags;
|
||||
intptr_t data;
|
||||
void *udata; /* opaque user data identifier */
|
||||
}
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
enum
|
||||
{
|
||||
/* actions */
|
||||
EV_ADD = 0x0001, /* add event to kq (implies enable) */
|
||||
EV_DELETE = 0x0002, /* delete event from kq */
|
||||
@ -95,37 +95,12 @@ version (MissingKevent)
|
||||
/* returned values */
|
||||
EV_EOF = 0x8000, /* EOF detected */
|
||||
EV_ERROR = 0x4000, /* error, data contains errno */
|
||||
}
|
||||
|
||||
int kqueue();
|
||||
int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||
kevent_t *eventlist, int nevents,
|
||||
const timespec *timeout);
|
||||
}
|
||||
|
||||
version (OSX)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (iOS)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (FreeBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
public import core.sys.freebsd.sys.event;
|
||||
}
|
||||
else version (OpenBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
else version (DragonFlyBSD)
|
||||
{
|
||||
version = MacBSD;
|
||||
}
|
||||
|
||||
version (MacBSD):
|
||||
extern(C) int kqueue() nothrow @nogc;
|
||||
extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
||||
kevent_t *eventlist, int nevents, const timespec *timeout)
|
||||
nothrow @nogc;
|
||||
|
||||
import tanya.async.event.selector;
|
||||
import tanya.async.loop;
|
||||
@ -151,12 +126,13 @@ class KqueueLoop : SelectorLoop
|
||||
* Returns: Maximal event count can be got at a time
|
||||
* (should be supported by the backend).
|
||||
*/
|
||||
override protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
||||
override protected @property inout(uint) maxEvents()
|
||||
inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return cast(uint) events.length;
|
||||
}
|
||||
|
||||
this()
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
|
||||
@ -164,21 +140,21 @@ class KqueueLoop : SelectorLoop
|
||||
{
|
||||
throw MmapPool.instance.make!BadLoopException("epoll initialization failed");
|
||||
}
|
||||
events = MmapPool.instance.makeArray!kevent_t(64);
|
||||
changes = MmapPool.instance.makeArray!kevent_t(64);
|
||||
MmapPool.instance.resizeArray(events, 64);
|
||||
MmapPool.instance.resizeArray(changes, 64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Free loop internals.
|
||||
*/
|
||||
~this()
|
||||
~this() @nogc
|
||||
{
|
||||
MmapPool.instance.dispose(events);
|
||||
MmapPool.instance.dispose(changes);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
private void set(socket_t socket, short filter, ushort flags)
|
||||
private void set(socket_t socket, short filter, ushort flags) @nogc
|
||||
{
|
||||
if (changes.length <= changeCount)
|
||||
{
|
||||
@ -206,7 +182,7 @@ class KqueueLoop : SelectorLoop
|
||||
*/
|
||||
override protected bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events)
|
||||
EventMask events) @nogc
|
||||
{
|
||||
if (events != oldEvents)
|
||||
{
|
||||
@ -233,7 +209,7 @@ class KqueueLoop : SelectorLoop
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
protected override void poll()
|
||||
protected override void poll() @nogc
|
||||
{
|
||||
timespec ts;
|
||||
blockTime.split!("seconds", "nsecs")(ts.tv_sec, ts.tv_nsec);
|
||||
@ -250,7 +226,7 @@ class KqueueLoop : SelectorLoop
|
||||
{
|
||||
if (errno != EINTR)
|
||||
{
|
||||
throw theAllocator.make!BadLoopException();
|
||||
throw defaultAllocator.make!BadLoopException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -295,7 +271,7 @@ class KqueueLoop : SelectorLoop
|
||||
}
|
||||
else if (io.output.length)
|
||||
{
|
||||
swapPendings.insertBack(io);
|
||||
swapPendings.enqueue(io);
|
||||
}
|
||||
}
|
||||
else if (events[i].filter == EVFILT_WRITE)
|
||||
@ -316,7 +292,7 @@ class KqueueLoop : SelectorLoop
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
override protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
inout @nogc @safe pure nothrow
|
||||
{
|
||||
return min(super.blockTime, 1.dur!"seconds");
|
||||
}
|
||||
@ -333,7 +309,8 @@ class KqueueLoop : SelectorLoop
|
||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||
* transport is be destroyed then).
|
||||
*/
|
||||
protected override bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
||||
protected override bool feed(SelectorStreamTransport transport,
|
||||
SocketException exception = null) @nogc
|
||||
{
|
||||
if (!super.feed(transport, exception))
|
||||
{
|
||||
|
@ -30,7 +30,7 @@ class SelectorStreamTransport : StreamTransport
|
||||
private ConnectedSocket socket_;
|
||||
|
||||
/// Input buffer.
|
||||
package WriteBuffer input;
|
||||
package WriteBuffer!ubyte input;
|
||||
|
||||
private SelectorLoop loop;
|
||||
|
||||
@ -42,19 +42,11 @@ class SelectorStreamTransport : StreamTransport
|
||||
* loop = Event loop.
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(SelectorLoop loop, ConnectedSocket socket)
|
||||
this(SelectorLoop loop, ConnectedSocket socket) @nogc
|
||||
{
|
||||
socket_ = socket;
|
||||
this.loop = loop;
|
||||
input = MmapPool.instance.make!WriteBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the transport and deallocate the data buffers.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
MmapPool.instance.dispose(input);
|
||||
input = WriteBuffer!ubyte(8192, MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +63,7 @@ class SelectorStreamTransport : StreamTransport
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data)
|
||||
void write(ubyte[] data) @nogc
|
||||
{
|
||||
if (!data.length)
|
||||
{
|
||||
@ -113,13 +105,13 @@ abstract class SelectorLoop : Loop
|
||||
/// Pending connections.
|
||||
protected ConnectionWatcher[] connections;
|
||||
|
||||
this()
|
||||
this() @nogc
|
||||
{
|
||||
super();
|
||||
connections = MmapPool.instance.makeArray!ConnectionWatcher(maxEvents);
|
||||
MmapPool.instance.resizeArray(connections, maxEvents);
|
||||
}
|
||||
|
||||
~this()
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (ref connection; connections)
|
||||
{
|
||||
@ -147,7 +139,8 @@ abstract class SelectorLoop : Loop
|
||||
* completed or scheduled, $(D_KEYWORD false) otherwise (the
|
||||
* transport will be destroyed then).
|
||||
*/
|
||||
protected bool feed(SelectorStreamTransport transport, SocketException exception = null)
|
||||
protected bool feed(SelectorStreamTransport transport,
|
||||
SocketException exception = null) @nogc
|
||||
{
|
||||
while (transport.input.length && transport.writeReady)
|
||||
{
|
||||
@ -186,7 +179,7 @@ abstract class SelectorLoop : Loop
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
override void start(ConnectionWatcher watcher)
|
||||
override void start(ConnectionWatcher watcher) @nogc
|
||||
{
|
||||
if (watcher.active)
|
||||
{
|
||||
@ -208,7 +201,7 @@ abstract class SelectorLoop : Loop
|
||||
* Params:
|
||||
* connection = Connection watcher ready to accept.
|
||||
*/
|
||||
package void acceptConnections(ConnectionWatcher connection)
|
||||
package void acceptConnections(ConnectionWatcher connection) @nogc
|
||||
in
|
||||
{
|
||||
assert(connection !is null);
|
||||
@ -224,7 +217,7 @@ abstract class SelectorLoop : Loop
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
theAllocator.dispose(e);
|
||||
defaultAllocator.dispose(e);
|
||||
break;
|
||||
}
|
||||
if (client is null)
|
||||
@ -255,12 +248,12 @@ abstract class SelectorLoop : Loop
|
||||
}
|
||||
|
||||
reify(io, EventMask(Event.none), EventMask(Event.read, Event.write));
|
||||
connection.incoming.insertBack(io);
|
||||
connection.incoming.enqueue(io);
|
||||
}
|
||||
|
||||
if (!connection.incoming.empty)
|
||||
{
|
||||
swapPendings.insertBack(connection);
|
||||
swapPendings.enqueue(connection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,67 +10,72 @@
|
||||
*
|
||||
* ---
|
||||
* import tanya.async;
|
||||
* import tanya.memory;
|
||||
* import tanya.network.socket;
|
||||
*
|
||||
* class EchoProtocol : TransmissionControlProtocol
|
||||
* {
|
||||
* private DuplexTransport transport;
|
||||
*
|
||||
* void received(ubyte[] data)
|
||||
* void received(ubyte[] data) @nogc
|
||||
* {
|
||||
* transport.write(data);
|
||||
* }
|
||||
*
|
||||
* void connected(DuplexTransport transport)
|
||||
* void connected(DuplexTransport transport) @nogc
|
||||
* {
|
||||
* this.transport = transport;
|
||||
* }
|
||||
*
|
||||
* void disconnected(SocketException e = null)
|
||||
* void disconnected(SocketException e = null) @nogc
|
||||
* {
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void main()
|
||||
* {
|
||||
* auto address = new InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||
* auto address = defaultAllocator.make!InternetAddress("127.0.0.1", cast(ushort) 8192);
|
||||
*
|
||||
* version (Windows)
|
||||
* {
|
||||
* auto sock = new OverlappedStreamSocket(AddressFamily.INET);
|
||||
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* auto sock = new StreamSocket(AddressFamily.INET);
|
||||
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
||||
* sock.blocking = false;
|
||||
* }
|
||||
*
|
||||
* sock.bind(address);
|
||||
* sock.listen(5);
|
||||
*
|
||||
* auto io = new ConnectionWatcher(sock);
|
||||
* auto io = defaultAllocator.make!ConnectionWatcher(sock);
|
||||
* io.setProtocol!EchoProtocol;
|
||||
*
|
||||
* defaultLoop.start(io);
|
||||
* defaultLoop.run();
|
||||
*
|
||||
* sock.shutdown();
|
||||
* defaultAllocator.dispose(io);
|
||||
* defaultAllocator.dispose(sock);
|
||||
* defaultAllocator.dispose(address);
|
||||
* }
|
||||
* ---
|
||||
*/
|
||||
module tanya.async.loop;
|
||||
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import core.time;
|
||||
import std.algorithm.iteration;
|
||||
import std.algorithm.mutation;
|
||||
import std.typecons;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.async.watcher;
|
||||
import tanya.container.buffer;
|
||||
import tanya.container.queue;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
|
||||
version (DisableBackends)
|
||||
{
|
||||
@ -126,15 +131,17 @@ alias EventMask = BitFlags!Event;
|
||||
abstract class Loop
|
||||
{
|
||||
/// Pending watchers.
|
||||
protected PendingQueue!Watcher pendings;
|
||||
protected Queue!Watcher* pendings;
|
||||
|
||||
protected PendingQueue!Watcher swapPendings;
|
||||
/// Ditto.
|
||||
protected Queue!Watcher* swapPendings;
|
||||
|
||||
/**
|
||||
* Returns: Maximal event count can be got at a time
|
||||
* (should be supported by the backend).
|
||||
*/
|
||||
protected @property inout(uint) maxEvents() inout const pure nothrow @safe @nogc
|
||||
protected @property inout(uint) maxEvents()
|
||||
inout const pure nothrow @safe @nogc
|
||||
{
|
||||
return 128U;
|
||||
}
|
||||
@ -142,25 +149,34 @@ abstract class Loop
|
||||
/**
|
||||
* Initializes the loop.
|
||||
*/
|
||||
this()
|
||||
this() @nogc
|
||||
{
|
||||
pendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
||||
swapPendings = MmapPool.instance.make!(PendingQueue!Watcher);
|
||||
pendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
|
||||
swapPendings = MmapPool.instance.make!(Queue!Watcher)(MmapPool.instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees loop internals.
|
||||
*/
|
||||
~this()
|
||||
~this() @nogc
|
||||
{
|
||||
foreach (w; *pendings)
|
||||
{
|
||||
MmapPool.instance.dispose(w);
|
||||
}
|
||||
MmapPool.instance.dispose(pendings);
|
||||
|
||||
foreach (w; *swapPendings)
|
||||
{
|
||||
MmapPool.instance.dispose(w);
|
||||
}
|
||||
MmapPool.instance.dispose(swapPendings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the loop.
|
||||
*/
|
||||
void run()
|
||||
void run() @nogc
|
||||
{
|
||||
done_ = false;
|
||||
do
|
||||
@ -168,8 +184,10 @@ abstract class Loop
|
||||
poll();
|
||||
|
||||
// Invoke pendings
|
||||
swapPendings.each!((ref p) => p.invoke());
|
||||
|
||||
foreach (ref w; *swapPendings)
|
||||
{
|
||||
w.invoke();
|
||||
}
|
||||
swap(pendings, swapPendings);
|
||||
}
|
||||
while (!done_);
|
||||
@ -178,7 +196,7 @@ abstract class Loop
|
||||
/**
|
||||
* Break out of the loop.
|
||||
*/
|
||||
void unloop() @safe pure nothrow
|
||||
void unloop() @safe pure nothrow @nogc
|
||||
{
|
||||
done_ = true;
|
||||
}
|
||||
@ -189,7 +207,7 @@ abstract class Loop
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void start(ConnectionWatcher watcher)
|
||||
void start(ConnectionWatcher watcher) @nogc
|
||||
{
|
||||
if (watcher.active)
|
||||
{
|
||||
@ -205,7 +223,7 @@ abstract class Loop
|
||||
* Params:
|
||||
* watcher = Watcher.
|
||||
*/
|
||||
void stop(ConnectionWatcher watcher)
|
||||
void stop(ConnectionWatcher watcher) @nogc
|
||||
{
|
||||
if (!watcher.active)
|
||||
{
|
||||
@ -228,13 +246,13 @@ abstract class Loop
|
||||
*/
|
||||
abstract protected bool reify(ConnectionWatcher watcher,
|
||||
EventMask oldEvents,
|
||||
EventMask events);
|
||||
EventMask events) @nogc;
|
||||
|
||||
/**
|
||||
* Returns: The blocking time.
|
||||
*/
|
||||
protected @property inout(Duration) blockTime()
|
||||
inout @safe pure nothrow
|
||||
inout @safe pure nothrow @nogc
|
||||
{
|
||||
// Don't block if we have to do.
|
||||
return swapPendings.empty ? blockTime_ : Duration.zero;
|
||||
@ -247,7 +265,7 @@ abstract class Loop
|
||||
* blockTime = The blocking time. Cannot be larger than
|
||||
* $(D_PSYMBOL maxBlockTime).
|
||||
*/
|
||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow
|
||||
protected @property void blockTime(in Duration blockTime) @safe pure nothrow @nogc
|
||||
in
|
||||
{
|
||||
assert(blockTime <= 1.dur!"hours", "Too long to wait.");
|
||||
@ -261,19 +279,19 @@ abstract class Loop
|
||||
/**
|
||||
* Kills the watcher and closes the connection.
|
||||
*/
|
||||
protected void kill(IOWatcher watcher, SocketException exception)
|
||||
protected void kill(IOWatcher watcher, SocketException exception) @nogc
|
||||
{
|
||||
watcher.socket.shutdown();
|
||||
theAllocator.dispose(watcher.socket);
|
||||
defaultAllocator.dispose(watcher.socket);
|
||||
MmapPool.instance.dispose(watcher.transport);
|
||||
watcher.exception = exception;
|
||||
swapPendings.insertBack(watcher);
|
||||
swapPendings.enqueue(watcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the actual polling.
|
||||
*/
|
||||
abstract protected void poll();
|
||||
abstract protected void poll() @nogc;
|
||||
|
||||
/// Whether the event loop should be stopped.
|
||||
private bool done_;
|
||||
@ -287,7 +305,6 @@ abstract class Loop
|
||||
*/
|
||||
class BadLoopException : Exception
|
||||
{
|
||||
@nogc:
|
||||
/**
|
||||
* Params:
|
||||
* file = The file where the exception occurred.
|
||||
@ -295,7 +312,7 @@ class BadLoopException : Exception
|
||||
* next = The previous exception in the chain of exceptions, if any.
|
||||
*/
|
||||
this(string file = __FILE__, size_t line = __LINE__, Throwable next = null)
|
||||
pure @safe nothrow const
|
||||
pure nothrow const @safe @nogc
|
||||
{
|
||||
super("Event loop cannot be initialized.", file, line, next);
|
||||
}
|
||||
@ -308,7 +325,7 @@ class BadLoopException : Exception
|
||||
*
|
||||
* Returns: The default event loop.
|
||||
*/
|
||||
@property Loop defaultLoop()
|
||||
@property Loop defaultLoop() @nogc
|
||||
{
|
||||
if (defaultLoop_ !is null)
|
||||
{
|
||||
@ -341,7 +358,7 @@ class BadLoopException : Exception
|
||||
* Params:
|
||||
* loop = The event loop.
|
||||
*/
|
||||
@property void defaultLoop(Loop loop)
|
||||
@property void defaultLoop(Loop loop) @nogc
|
||||
in
|
||||
{
|
||||
assert(loop !is null);
|
||||
@ -352,132 +369,3 @@ body
|
||||
}
|
||||
|
||||
private Loop defaultLoop_;
|
||||
|
||||
/**
|
||||
* Queue.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class PendingQueue(T)
|
||||
{
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Queue).
|
||||
*/
|
||||
this()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
foreach (e; this)
|
||||
{
|
||||
MmapPool.instance.dispose(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: First element.
|
||||
*/
|
||||
@property ref T front()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
return first.next.content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) insertBack(T x)
|
||||
{
|
||||
Entry* temp = MmapPool.instance.make!Entry;
|
||||
|
||||
temp.content = x;
|
||||
|
||||
if (empty)
|
||||
{
|
||||
first.next = rear = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
rear.next = temp;
|
||||
rear = rear.next;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
alias insert = insertBack;
|
||||
|
||||
/**
|
||||
* Inserts a new element.
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) opOpAssign(string Op)(ref T x)
|
||||
if (Op == "~")
|
||||
{
|
||||
return insertBack(x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() const @safe pure nothrow
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move position to the next element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
typeof(this) popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
|
||||
MmapPool.instance.dispose(first.next);
|
||||
first.next = n;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue entry.
|
||||
*/
|
||||
protected struct Entry
|
||||
{
|
||||
/// Queue item content.
|
||||
T content;
|
||||
|
||||
/// Next list item.
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
/// The first element of the list.
|
||||
protected Entry first;
|
||||
|
||||
/// The last element of the list.
|
||||
protected Entry* rear;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ interface Protocol
|
||||
* Params:
|
||||
* data = Read data.
|
||||
*/
|
||||
void received(ubyte[] data);
|
||||
void received(ubyte[] data) @nogc;
|
||||
|
||||
/**
|
||||
* Called when a connection is made.
|
||||
@ -30,7 +30,7 @@ interface Protocol
|
||||
* Params:
|
||||
* transport = Protocol transport.
|
||||
*/
|
||||
void connected(DuplexTransport transport);
|
||||
void connected(DuplexTransport transport) @nogc;
|
||||
|
||||
/**
|
||||
* Called when a connection is lost.
|
||||
@ -39,7 +39,7 @@ interface Protocol
|
||||
* exception = $(D_PSYMBOL Exception) if an error caused
|
||||
* the disconnect, $(D_KEYWORD null) otherwise.
|
||||
*/
|
||||
void disconnected(SocketException exception = null);
|
||||
void disconnected(SocketException exception = null) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,7 +37,7 @@ interface WriteTransport : Transport
|
||||
* Params:
|
||||
* data = Data to send.
|
||||
*/
|
||||
void write(ubyte[] data);
|
||||
void write(ubyte[] data) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -10,15 +10,16 @@
|
||||
*/
|
||||
module tanya.async.watcher;
|
||||
|
||||
import std.functional;
|
||||
import std.exception;
|
||||
import tanya.async.loop;
|
||||
import tanya.async.protocol;
|
||||
import tanya.async.transport;
|
||||
import tanya.container.buffer;
|
||||
import tanya.container.queue;
|
||||
import tanya.memory;
|
||||
import tanya.memory.mmappool;
|
||||
import tanya.network.socket;
|
||||
import std.functional;
|
||||
import std.exception;
|
||||
|
||||
version (Windows)
|
||||
{
|
||||
@ -41,7 +42,7 @@ abstract class Watcher
|
||||
/**
|
||||
* Invoke some action on event.
|
||||
*/
|
||||
void invoke();
|
||||
void invoke() @nogc;
|
||||
}
|
||||
|
||||
class ConnectionWatcher : Watcher
|
||||
@ -50,37 +51,39 @@ class ConnectionWatcher : Watcher
|
||||
private Socket socket_;
|
||||
|
||||
/// Protocol factory.
|
||||
protected Protocol delegate() protocolFactory;
|
||||
protected Protocol delegate() @nogc protocolFactory;
|
||||
|
||||
package PendingQueue!IOWatcher incoming;
|
||||
package Queue!IOWatcher incoming;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* socket = Socket.
|
||||
*/
|
||||
this(Socket socket)
|
||||
this(Socket socket) @nogc
|
||||
{
|
||||
socket_ = socket;
|
||||
incoming = MmapPool.instance.make!(PendingQueue!IOWatcher);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
protected this()
|
||||
protected this() pure nothrow @safe @nogc
|
||||
{
|
||||
}
|
||||
|
||||
~this()
|
||||
~this() @nogc
|
||||
{
|
||||
MmapPool.instance.dispose(incoming);
|
||||
foreach (w; incoming)
|
||||
{
|
||||
MmapPool.instance.dispose(w);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Params:
|
||||
* P = Protocol should be used.
|
||||
*/
|
||||
void setProtocol(P : Protocol)()
|
||||
void setProtocol(P : Protocol)() @nogc
|
||||
{
|
||||
this.protocolFactory = () => cast(Protocol) MmapPool.instance.make!P;
|
||||
this.protocolFactory = () @nogc => cast(Protocol) MmapPool.instance.make!P;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,7 +97,7 @@ class ConnectionWatcher : Watcher
|
||||
/**
|
||||
* Returns: New protocol instance.
|
||||
*/
|
||||
@property Protocol protocol()
|
||||
@property Protocol protocol() @nogc
|
||||
in
|
||||
{
|
||||
assert(protocolFactory !is null, "Protocol isn't set.");
|
||||
@ -107,7 +110,7 @@ class ConnectionWatcher : Watcher
|
||||
/**
|
||||
* Invokes new connection callback.
|
||||
*/
|
||||
override void invoke()
|
||||
override void invoke() @nogc
|
||||
{
|
||||
foreach (io; incoming)
|
||||
{
|
||||
@ -134,14 +137,14 @@ class IOWatcher : ConnectionWatcher
|
||||
/**
|
||||
* Returns: Underlying output buffer.
|
||||
*/
|
||||
package ReadBuffer output;
|
||||
package ReadBuffer!ubyte output;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* transport = Transport.
|
||||
* protocol = New instance of the application protocol.
|
||||
*/
|
||||
this(StreamTransport transport, Protocol protocol)
|
||||
this(StreamTransport transport, Protocol protocol) @nogc
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
@ -152,16 +155,15 @@ class IOWatcher : ConnectionWatcher
|
||||
super();
|
||||
transport_ = transport;
|
||||
protocol_ = protocol;
|
||||
output = MmapPool.instance.make!ReadBuffer();
|
||||
output = ReadBuffer!ubyte(8192, 1024, MmapPool.instance);
|
||||
active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the watcher.
|
||||
*/
|
||||
protected ~this()
|
||||
~this() @nogc
|
||||
{
|
||||
MmapPool.instance.dispose(output);
|
||||
MmapPool.instance.dispose(protocol_);
|
||||
}
|
||||
|
||||
@ -174,7 +176,8 @@ class IOWatcher : ConnectionWatcher
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
IOWatcher opCall(StreamTransport transport, Protocol protocol) pure nothrow @nogc
|
||||
IOWatcher opCall(StreamTransport transport, Protocol protocol)
|
||||
pure nothrow @nogc
|
||||
in
|
||||
{
|
||||
assert(transport !is null);
|
||||
@ -226,7 +229,7 @@ class IOWatcher : ConnectionWatcher
|
||||
/**
|
||||
* Invokes the watcher callback.
|
||||
*/
|
||||
override void invoke()
|
||||
override void invoke() @nogc
|
||||
{
|
||||
if (output.length)
|
||||
{
|
||||
|
@ -10,6 +10,7 @@
|
||||
*/
|
||||
module tanya.container.buffer;
|
||||
|
||||
import std.traits;
|
||||
import tanya.memory;
|
||||
|
||||
version (unittest)
|
||||
@ -33,51 +34,6 @@ version (unittest)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for implemeting input/output buffers.
|
||||
*/
|
||||
interface Buffer
|
||||
{
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @nogc @safe pure nothrow;
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* start = Start position.
|
||||
* end = End position.
|
||||
*
|
||||
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
||||
*/
|
||||
@property ubyte[] opSlice(size_t start, size_t end)
|
||||
in
|
||||
{
|
||||
assert(start <= end);
|
||||
assert(end <= length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Returns: Data chunk.
|
||||
*/
|
||||
@property ubyte[] opIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Self-expanding buffer, that can be used with functions returning the number
|
||||
* of the read bytes.
|
||||
@ -87,26 +43,30 @@ interface Buffer
|
||||
* available data. But only one asynchronous call at a time is supported. Be
|
||||
* sure to call $(D_PSYMBOL ReadBuffer.clear()) before you append the result
|
||||
* of the pended asynchronous call.
|
||||
*
|
||||
* Params:
|
||||
* T = Buffer type.
|
||||
*/
|
||||
class ReadBuffer : Buffer
|
||||
struct ReadBuffer(T = ubyte)
|
||||
if (isScalarType!T)
|
||||
{
|
||||
/// Internal buffer.
|
||||
protected ubyte[] buffer_;
|
||||
private T[] buffer_;
|
||||
|
||||
/// Filled buffer length.
|
||||
protected size_t length_;
|
||||
private size_t length_;
|
||||
|
||||
/// Start of available data.
|
||||
protected size_t start;
|
||||
private size_t start;
|
||||
|
||||
/// Last position returned with $(D_KEYWORD []).
|
||||
protected size_t ring;
|
||||
private size_t ring;
|
||||
|
||||
/// Available space.
|
||||
protected immutable size_t minAvailable;
|
||||
private immutable size_t minAvailable = 1024;
|
||||
|
||||
/// Size by which the buffer will grow.
|
||||
protected immutable size_t blockSize;
|
||||
private immutable size_t blockSize = 8192;
|
||||
|
||||
invariant
|
||||
{
|
||||
@ -123,39 +83,50 @@ class ReadBuffer : Buffer
|
||||
* will grow.
|
||||
* minAvailable = minimal size should be always available to fill.
|
||||
* So it will reallocate if $(D_INLINECODE
|
||||
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)
|
||||
* ).
|
||||
* $(D_PSYMBOL free) < $(D_PARAM minAvailable)).
|
||||
* allocator = Allocator.
|
||||
*/
|
||||
this(size_t size = 8192,
|
||||
size_t minAvailable = 1024)
|
||||
this(in size_t size,
|
||||
in size_t minAvailable = 1024,
|
||||
shared Allocator allocator = defaultAllocator) @trusted
|
||||
{
|
||||
this(allocator);
|
||||
this.minAvailable = minAvailable;
|
||||
this.blockSize = size;
|
||||
theAllocator.resizeArray!ubyte(buffer_, size);
|
||||
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator_ is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator_ = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deallocates the internal buffer.
|
||||
*/
|
||||
~this()
|
||||
~this() @trusted
|
||||
{
|
||||
theAllocator.dispose(buffer_);
|
||||
allocator.deallocate(buffer_);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!ReadBuffer;
|
||||
assert(b.capacity == 8192);
|
||||
ReadBuffer!ubyte b;
|
||||
assert(b.capacity == 0);
|
||||
assert(b.length == 0);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @nogc @safe pure nothrow
|
||||
@property size_t capacity() const
|
||||
{
|
||||
return buffer_.length;
|
||||
}
|
||||
@ -163,26 +134,28 @@ class ReadBuffer : Buffer
|
||||
/**
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @nogc @safe pure nothrow
|
||||
@property size_t length() const
|
||||
{
|
||||
return length_ - start;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
alias opDollar = length;
|
||||
|
||||
/**
|
||||
* Clears the buffer.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ReadBuffer clear() pure nothrow @safe @nogc
|
||||
void clear()
|
||||
{
|
||||
start = length_ = ring;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const pure nothrow @safe @nogc
|
||||
@property size_t free() const
|
||||
{
|
||||
return length > ring ? capacity - length : capacity - ring;
|
||||
}
|
||||
@ -190,19 +163,17 @@ class ReadBuffer : Buffer
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!ReadBuffer;
|
||||
ReadBuffer!ubyte b;
|
||||
size_t numberRead;
|
||||
|
||||
// Fills the buffer with values 0..10
|
||||
assert(b.free == b.blockSize);
|
||||
assert(b.free == 0);
|
||||
|
||||
// Fills the buffer with values 0..10
|
||||
numberRead = fillBuffer(b[], b.free, 0, 10);
|
||||
b += numberRead;
|
||||
assert(b.free == b.blockSize - numberRead);
|
||||
b.clear();
|
||||
assert(b.free == b.blockSize);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,7 +184,7 @@ class ReadBuffer : Buffer
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
ReadBuffer opOpAssign(string op)(size_t length)
|
||||
ref ReadBuffer opOpAssign(string op)(in size_t length)
|
||||
if (op == "+")
|
||||
{
|
||||
length_ += length;
|
||||
@ -224,7 +195,7 @@ class ReadBuffer : Buffer
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!ReadBuffer;
|
||||
ReadBuffer!ubyte b;
|
||||
size_t numberRead;
|
||||
ubyte[] result;
|
||||
|
||||
@ -251,16 +222,6 @@ class ReadBuffer : Buffer
|
||||
assert(result[9] == 9);
|
||||
assert(result[10] == 20);
|
||||
assert(result[14] == 24);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,7 +231,7 @@ class ReadBuffer : Buffer
|
||||
*
|
||||
* Returns: Array between $(D_PARAM start) and $(D_PARAM end).
|
||||
*/
|
||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
||||
T[] opSlice(in size_t start, in size_t end)
|
||||
{
|
||||
return buffer_[this.start + start .. this.start + end];
|
||||
}
|
||||
@ -282,11 +243,11 @@ class ReadBuffer : Buffer
|
||||
*
|
||||
* Returns: A free chunk of the buffer.
|
||||
*/
|
||||
ubyte[] opIndex()
|
||||
T[] opIndex()
|
||||
{
|
||||
if (start > 0)
|
||||
{
|
||||
auto ret = buffer_[0..start];
|
||||
auto ret = buffer_[0 .. start];
|
||||
ring = 0;
|
||||
return ret;
|
||||
}
|
||||
@ -294,17 +255,23 @@ class ReadBuffer : Buffer
|
||||
{
|
||||
if (capacity - length < minAvailable)
|
||||
{
|
||||
theAllocator.resizeArray!ubyte(buffer_, capacity + blockSize);
|
||||
void[] buf = buffer_;
|
||||
immutable cap = capacity;
|
||||
() @trusted {
|
||||
allocator.reallocate(buf, (cap + blockSize) * T.sizeof);
|
||||
buffer_ = cast(T[]) buf;
|
||||
}();
|
||||
buffer_[cap .. $] = T.init;
|
||||
}
|
||||
ring = length_;
|
||||
return buffer_[length_..$];
|
||||
return buffer_[length_ .. $];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!ReadBuffer;
|
||||
ReadBuffer!ubyte b;
|
||||
size_t numberRead;
|
||||
ubyte[] result;
|
||||
|
||||
@ -318,54 +285,83 @@ class ReadBuffer : Buffer
|
||||
assert(result[9] == 9);
|
||||
b.clear();
|
||||
assert(b.length == 0);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
mixin DefaultAllocator;
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
static assert(is(ReadBuffer!int));
|
||||
}
|
||||
|
||||
/**
|
||||
* Circular, self-expanding buffer with overflow support. Can be used with
|
||||
* functions returning returning the number of the transferred bytes.
|
||||
* functions returning the number of the transferred bytes.
|
||||
*
|
||||
* The buffer is optimized for situations where you read all the data from it
|
||||
* at once (without writing to it occasionally). It can become ineffective if
|
||||
* you permanently keep some data in the buffer and alternate writing and
|
||||
* reading, because it may allocate and move elements.
|
||||
*
|
||||
* Params:
|
||||
* T = Buffer type.
|
||||
*/
|
||||
class WriteBuffer : Buffer
|
||||
struct WriteBuffer(T = ubyte)
|
||||
if (isScalarType!T)
|
||||
{
|
||||
/// Internal buffer.
|
||||
protected ubyte[] buffer_;
|
||||
private T[] buffer_;
|
||||
|
||||
/// Buffer start position.
|
||||
protected size_t start;
|
||||
private size_t start;
|
||||
|
||||
/// Buffer ring area size. After this position begins buffer overflow area.
|
||||
protected size_t ring;
|
||||
private size_t ring;
|
||||
|
||||
/// Size by which the buffer will grow.
|
||||
protected immutable size_t blockSize;
|
||||
private immutable size_t blockSize;
|
||||
|
||||
/// The position of the free area in the buffer.
|
||||
protected size_t position;
|
||||
private size_t position;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(blockSize > 0);
|
||||
// position can refer to an element outside the buffer if the buffer is full.
|
||||
// Position can refer to an element outside the buffer if the buffer is full.
|
||||
assert(position <= buffer_.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Initial buffer size and the size by which the buffer
|
||||
* will grow.
|
||||
* size = Initial buffer size and the size by which the buffer will
|
||||
* grow.
|
||||
* allocator = Allocator.
|
||||
*/
|
||||
this(size_t size = 8192)
|
||||
this(in size_t size, shared Allocator allocator = defaultAllocator) @trusted
|
||||
in
|
||||
{
|
||||
assert(size > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
blockSize = size;
|
||||
ring = size - 1;
|
||||
theAllocator.resizeArray!ubyte(buffer_, size);
|
||||
this(allocator);
|
||||
buffer_ = cast(T[]) allocator_.allocate(size * T.sizeof);
|
||||
}
|
||||
|
||||
@disable this();
|
||||
|
||||
/// Ditto.
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
allocator_ = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -373,26 +369,26 @@ class WriteBuffer : Buffer
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
theAllocator.dispose(buffer_);
|
||||
allocator.deallocate(buffer_);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The size of the internal buffer.
|
||||
*/
|
||||
@property size_t capacity() const @nogc @safe pure nothrow
|
||||
@property size_t capacity() const
|
||||
{
|
||||
return buffer_.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that $(D_PSYMBOL length) doesn't return the real length of the data,
|
||||
* but only the array length that will be returned with $(D_PSYMBOL buffer)
|
||||
* next time. Be sure to call $(D_PSYMBOL buffer) and set $(D_KEYWORD +=)
|
||||
* but only the array length that will be returned with $(D_PSYMBOL opIndex)
|
||||
* next time. Be sure to call $(D_PSYMBOL opIndex) and set $(D_KEYWORD +=)
|
||||
* until $(D_PSYMBOL length) returns 0.
|
||||
*
|
||||
* Returns: Data size.
|
||||
*/
|
||||
@property size_t length() const @nogc @safe pure nothrow
|
||||
@property size_t length() const
|
||||
{
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
@ -404,18 +400,13 @@ class WriteBuffer : Buffer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Length of available data.
|
||||
*/
|
||||
@property size_t opDollar() const pure nothrow @safe @nogc
|
||||
{
|
||||
return length;
|
||||
}
|
||||
/// Ditto.
|
||||
alias opDollar = length;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!WriteBuffer(4);
|
||||
auto b = WriteBuffer!ubyte(4);
|
||||
ubyte[3] buf = [48, 23, 255];
|
||||
|
||||
b ~= buf;
|
||||
@ -432,14 +423,12 @@ class WriteBuffer : Buffer
|
||||
assert(b.length == 5);
|
||||
b += b.length;
|
||||
assert(b.length == 0);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Available space.
|
||||
*/
|
||||
@property size_t free() const @nogc @safe pure nothrow
|
||||
@property size_t free() const
|
||||
{
|
||||
return capacity - length;
|
||||
}
|
||||
@ -448,9 +437,9 @@ class WriteBuffer : Buffer
|
||||
* Appends data to the buffer.
|
||||
*
|
||||
* Params:
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL buffer).
|
||||
* buffer = Buffer chunk got with $(D_PSYMBOL opIndex).
|
||||
*/
|
||||
WriteBuffer opOpAssign(string op)(ubyte[] buffer)
|
||||
ref WriteBuffer opOpAssign(string op)(in T[] buffer)
|
||||
if (op == "~")
|
||||
{
|
||||
size_t end, start;
|
||||
@ -465,7 +454,7 @@ class WriteBuffer : Buffer
|
||||
end = afterRing;
|
||||
}
|
||||
start = end - position;
|
||||
buffer_[position..end] = buffer[0..start];
|
||||
buffer_[position .. end] = buffer[0 .. start];
|
||||
if (end == afterRing)
|
||||
{
|
||||
position = this.start == 0 ? afterRing : 0;
|
||||
@ -485,7 +474,7 @@ class WriteBuffer : Buffer
|
||||
end = this.start;
|
||||
}
|
||||
auto areaEnd = end - position + start;
|
||||
buffer_[position..end] = buffer[start..areaEnd];
|
||||
buffer_[position .. end] = buffer[start .. areaEnd];
|
||||
position = end == this.start ? ring + 1 : end - position;
|
||||
start = areaEnd;
|
||||
}
|
||||
@ -496,11 +485,14 @@ class WriteBuffer : Buffer
|
||||
end = position + buffer.length - start;
|
||||
if (end > capacity)
|
||||
{
|
||||
auto newSize = end / blockSize * blockSize + blockSize;
|
||||
|
||||
theAllocator.resizeArray!ubyte(buffer_, newSize);
|
||||
auto newSize = (end / blockSize * blockSize + blockSize) * T.sizeof;
|
||||
() @trusted {
|
||||
void[] buf = buffer_;
|
||||
allocator.reallocate(buf, newSize);
|
||||
buffer_ = cast(T[]) buf;
|
||||
}();
|
||||
}
|
||||
buffer_[position..end] = buffer[start..$];
|
||||
buffer_[position .. end] = buffer[start .. $];
|
||||
position = end;
|
||||
if (this.start == 0)
|
||||
{
|
||||
@ -514,7 +506,7 @@ class WriteBuffer : Buffer
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!WriteBuffer(4);
|
||||
auto b = WriteBuffer!ubyte(4);
|
||||
ubyte[3] buf = [48, 23, 255];
|
||||
|
||||
b ~= buf;
|
||||
@ -532,31 +524,31 @@ class WriteBuffer : Buffer
|
||||
assert(b.capacity == 8);
|
||||
assert(b.buffer_[0] == 23 && b.buffer_[1] == 255
|
||||
&& b.buffer_[2] == 48 && b.buffer_[3] == 23 && b.buffer_[4] == 255);
|
||||
}
|
||||
|
||||
theAllocator.dispose(b);
|
||||
|
||||
b = make!WriteBuffer(theAllocator, 2);
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = WriteBuffer!ubyte(2);
|
||||
ubyte[3] buf = [48, 23, 255];
|
||||
|
||||
b ~= buf;
|
||||
assert(b.start == 0);
|
||||
assert(b.capacity == 4);
|
||||
assert(b.ring == 3);
|
||||
assert(b.position == 3);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets how many bytes were written. It will shrink the buffer
|
||||
* appropriately. Always set this property after calling
|
||||
* $(D_PSYMBOL buffer).
|
||||
* appropriately. Always call it after $(D_PSYMBOL opIndex).
|
||||
*
|
||||
* Params:
|
||||
* length = Length of the written data.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
@property WriteBuffer opOpAssign(string op)(size_t length) pure nothrow @safe @nogc
|
||||
ref WriteBuffer opOpAssign(string op)(in size_t length)
|
||||
if (op == "+")
|
||||
in
|
||||
{
|
||||
@ -583,19 +575,21 @@ class WriteBuffer : Buffer
|
||||
{
|
||||
auto overflow = position - afterRing;
|
||||
|
||||
if (overflow > length) {
|
||||
buffer_[start.. start + length] = buffer_[afterRing.. afterRing + length];
|
||||
buffer_[afterRing.. afterRing + length] = buffer_[afterRing + length ..position];
|
||||
if (overflow > length)
|
||||
{
|
||||
immutable afterLength = afterRing + length;
|
||||
buffer_[start .. start + length] = buffer_[afterRing .. afterLength];
|
||||
buffer_[afterRing .. afterLength] = buffer_[afterLength .. position];
|
||||
position -= length;
|
||||
}
|
||||
else if (overflow == length)
|
||||
{
|
||||
buffer_[start.. start + overflow] = buffer_[afterRing..position];
|
||||
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
|
||||
position -= overflow;
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer_[start.. start + overflow] = buffer_[afterRing..position];
|
||||
buffer_[start .. start + overflow] = buffer_[afterRing .. position];
|
||||
position = overflow;
|
||||
}
|
||||
start += length;
|
||||
@ -620,7 +614,7 @@ class WriteBuffer : Buffer
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!WriteBuffer;
|
||||
auto b = WriteBuffer!ubyte(6);
|
||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||
|
||||
b ~= buf;
|
||||
@ -629,8 +623,6 @@ class WriteBuffer : Buffer
|
||||
assert(b.length == 4);
|
||||
b += 4;
|
||||
assert(b.length == 0);
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -639,62 +631,67 @@ class WriteBuffer : Buffer
|
||||
* After calling it, set $(D_KEYWORD +=) to the length could be
|
||||
* written.
|
||||
*
|
||||
* $(D_PSYMBOL buffer) may return only part of the data. You may need
|
||||
* to call it (and set $(D_KEYWORD +=) several times until
|
||||
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
||||
* to call it and set $(D_KEYWORD +=) several times until
|
||||
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
||||
* maximally 3 calls are required.
|
||||
*
|
||||
* Returns: A chunk of data buffer.
|
||||
*/
|
||||
@property ubyte[] opSlice(size_t start, size_t end) pure nothrow @safe @nogc
|
||||
T[] opSlice(in size_t start, in size_t end)
|
||||
{
|
||||
immutable internStart = this.start + start;
|
||||
|
||||
if (position > ring || position < start) // Buffer overflowed
|
||||
{
|
||||
return buffer_[this.start.. ring + 1 - length + end];
|
||||
return buffer_[this.start .. ring + 1 - length + end];
|
||||
}
|
||||
else
|
||||
{
|
||||
return buffer_[this.start.. this.start + end];
|
||||
return buffer_[this.start .. this.start + end];
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto b = theAllocator.make!WriteBuffer(6);
|
||||
auto b = WriteBuffer!ubyte(6);
|
||||
ubyte[6] buf = [23, 23, 255, 128, 127, 9];
|
||||
|
||||
b ~= buf;
|
||||
assert(b[0..$] == buf[0..6]);
|
||||
assert(b[0 .. $] == buf[0 .. 6]);
|
||||
b += 2;
|
||||
|
||||
assert(b[0..$] == buf[2..6]);
|
||||
assert(b[0 .. $] == buf[2 .. 6]);
|
||||
|
||||
b ~= buf;
|
||||
assert(b[0..$] == buf[2..6]);
|
||||
assert(b[0 .. $] == buf[2 .. 6]);
|
||||
b += b.length;
|
||||
|
||||
assert(b[0..$] == buf[0..6]);
|
||||
assert(b[0 .. $] == buf[0 .. 6]);
|
||||
b += b.length;
|
||||
|
||||
theAllocator.dispose(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* After calling it, set $(D_KEYWORD +=) to the length could be
|
||||
* written.
|
||||
*
|
||||
* $(D_PSYMBOL buffer) may return only part of the data. You may need
|
||||
* to call it (and set $(D_KEYWORD +=) several times until
|
||||
* $(D_PSYMBOL opIndex) may return only part of the data. You may need
|
||||
* to call it and set $(D_KEYWORD +=) several times until
|
||||
* $(D_PSYMBOL length) is 0. If all the data can be written,
|
||||
* maximally 3 calls are required.
|
||||
*
|
||||
* Returns: A chunk of data buffer.
|
||||
*/
|
||||
@property ubyte[] opIndex() pure nothrow @safe @nogc
|
||||
T[] opIndex()
|
||||
{
|
||||
return opSlice(0, length);
|
||||
}
|
||||
|
||||
mixin DefaultAllocator;
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
static assert(is(typeof(WriteBuffer!int(5))));
|
||||
}
|
||||
|
45
source/tanya/container/entry.d
Normal file
45
source/tanya/container/entry.d
Normal file
@ -0,0 +1,45 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/*
|
||||
* Internal package used by containers that rely on entries/nodes.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.container.entry;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
package struct ConstEqualsStruct
|
||||
{
|
||||
int opEquals(typeof(this) that) const @nogc
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
package struct MutableEqualsStruct
|
||||
{
|
||||
int opEquals(typeof(this) that) @nogc
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
package struct NoEqualsStruct
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
package struct Entry(T)
|
||||
{
|
||||
/// Item content.
|
||||
T content;
|
||||
|
||||
/// Next item.
|
||||
Entry* next;
|
||||
}
|
@ -10,28 +10,17 @@
|
||||
*/
|
||||
module tanya.container.list;
|
||||
|
||||
import tanya.container.entry;
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Singly linked list.
|
||||
* Singly-linked list.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class SList(T)
|
||||
struct SList(T)
|
||||
{
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL SList).
|
||||
*
|
||||
* Params:
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(IAllocator allocator = theAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the list.
|
||||
*/
|
||||
@ -41,7 +30,7 @@ class SList(T)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all contents from the $(D_PSYMBOL SList).
|
||||
* Removes all contents from the list.
|
||||
*/
|
||||
void clear()
|
||||
{
|
||||
@ -54,14 +43,12 @@ class SList(T)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
|
||||
l.insertFront(8);
|
||||
l.insertFront(5);
|
||||
l.clear();
|
||||
assert(l.empty);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -83,35 +70,39 @@ class SList(T)
|
||||
* Params:
|
||||
* x = New element.
|
||||
*/
|
||||
void insertFront(T x)
|
||||
void insertFront(ref T x)
|
||||
{
|
||||
Entry* temp = make!Entry(allocator);
|
||||
auto temp = allocator.make!(Entry!T);
|
||||
|
||||
temp.content = x;
|
||||
temp.next = first.next;
|
||||
first.next = temp;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void insertFront(T x)
|
||||
{
|
||||
insertFront(x);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
alias insert = insertFront;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
|
||||
l.insertFront(8);
|
||||
assert(l.front == 8);
|
||||
l.insertFront(9);
|
||||
assert(l.front == 9);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the list is empty.
|
||||
*/
|
||||
@property bool empty() inout const
|
||||
@property bool empty() const
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
@ -121,7 +112,7 @@ class SList(T)
|
||||
*
|
||||
* Returns: The first element.
|
||||
*/
|
||||
T popFront()
|
||||
void popFront()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
@ -129,26 +120,21 @@ class SList(T)
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
auto content = first.next.content;
|
||||
|
||||
dispose(allocator, first.next);
|
||||
allocator.dispose(first.next);
|
||||
first.next = n;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
|
||||
l.insertFront(8);
|
||||
l.insertFront(9);
|
||||
assert(l.front == 9);
|
||||
l.popFront();
|
||||
assert(l.front == 8);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,7 +165,7 @@ class SList(T)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
|
||||
l.insertFront(8);
|
||||
l.insertFront(5);
|
||||
@ -188,8 +174,6 @@ class SList(T)
|
||||
assert(l.removeFront(2) == 2);
|
||||
assert(l.removeFront(3) == 1);
|
||||
assert(l.removeFront(3) == 0);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,7 +219,7 @@ class SList(T)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
|
||||
l.insertFront(5);
|
||||
l.insertFront(4);
|
||||
@ -246,32 +230,18 @@ class SList(T)
|
||||
assert(i != 1 || e == 4);
|
||||
assert(i != 2 || e == 5);
|
||||
}
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
/**
|
||||
* List entry.
|
||||
*/
|
||||
protected struct Entry
|
||||
{
|
||||
/// List item content.
|
||||
T content;
|
||||
|
||||
/// Next list item.
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
/// 0th element of the list.
|
||||
protected Entry first;
|
||||
private Entry!T first;
|
||||
|
||||
/// Allocator.
|
||||
protected IAllocator allocator;
|
||||
mixin DefaultAllocator;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto l = make!(SList!int)(theAllocator);
|
||||
SList!int l;
|
||||
size_t i;
|
||||
|
||||
l.insertFront(5);
|
||||
@ -285,8 +255,6 @@ unittest
|
||||
++i;
|
||||
}
|
||||
assert(i == 3);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
}
|
||||
|
||||
private unittest
|
||||
@ -294,8 +262,5 @@ private unittest
|
||||
interface Stuff
|
||||
{
|
||||
}
|
||||
|
||||
auto l = make!(SList!Stuff)(theAllocator);
|
||||
|
||||
dispose(theAllocator, l);
|
||||
static assert(is(SList!Stuff));
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
*/
|
||||
module tanya.container;
|
||||
|
||||
public import tanya.container.bit;
|
||||
public import tanya.container.buffer;
|
||||
public import tanya.container.list;
|
||||
public import tanya.container.vector;
|
||||
|
@ -6,68 +6,153 @@
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.container.queue;
|
||||
|
||||
import std.traits;
|
||||
import std.algorithm.mutation;
|
||||
import tanya.container.entry;
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Queue.
|
||||
* FIFO queue.
|
||||
*
|
||||
* Params:
|
||||
* T = Content type.
|
||||
*/
|
||||
class Queue(T)
|
||||
struct Queue(T)
|
||||
{
|
||||
/**
|
||||
* Creates a new $(D_PSYMBOL Queue).
|
||||
*
|
||||
* Params:
|
||||
* allocator = The allocator should be used for the element
|
||||
* allocations.
|
||||
*/
|
||||
this(IAllocator allocator = theAllocator)
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
clear();
|
||||
while (!empty)
|
||||
{
|
||||
dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all elements from the queue.
|
||||
*/
|
||||
deprecated
|
||||
void clear()
|
||||
{
|
||||
while (!empty)
|
||||
{
|
||||
popFront();
|
||||
dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns how many elements are in the queue. It iterates through the queue
|
||||
* to count the elements.
|
||||
*
|
||||
* Returns: How many elements are in the queue.
|
||||
*/
|
||||
size_t length() const
|
||||
{
|
||||
size_t len;
|
||||
for (const(Entry!T)* i = first.next; i !is null; i = i.next)
|
||||
{
|
||||
++len;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = theAllocator.make!(Queue!int);
|
||||
Queue!int q;
|
||||
|
||||
assert(q.empty);
|
||||
q.insertBack(8);
|
||||
q.insertBack(9);
|
||||
q.clear();
|
||||
assert(q.empty);
|
||||
assert(q.length == 0);
|
||||
q.enqueue(5);
|
||||
assert(q.length == 1);
|
||||
q.enqueue(4);
|
||||
assert(q.length == 2);
|
||||
q.enqueue(9);
|
||||
assert(q.length == 3);
|
||||
|
||||
theAllocator.dispose(q);
|
||||
q.dequeue();
|
||||
assert(q.length == 2);
|
||||
q.dequeue();
|
||||
assert(q.length == 1);
|
||||
q.dequeue();
|
||||
assert(q.length == 0);
|
||||
}
|
||||
|
||||
version (D_Ddoc)
|
||||
{
|
||||
/**
|
||||
* Compares two queues. Checks if all elements of the both queues are equal.
|
||||
*
|
||||
* Returns: Whether $(D_KEYWORD this) and $(D_PARAM that) are equal.
|
||||
*/
|
||||
deprecated
|
||||
int opEquals(ref typeof(this) that);
|
||||
|
||||
/// Ditto.
|
||||
deprecated
|
||||
int opEquals(typeof(this) that);
|
||||
}
|
||||
else static if (!hasMember!(T, "opEquals")
|
||||
|| (functionAttributes!(T.opEquals) & FunctionAttribute.const_))
|
||||
{
|
||||
deprecated
|
||||
bool opEquals(in ref typeof(this) that) const
|
||||
{
|
||||
const(Entry!T)* i = first.next;
|
||||
const(Entry!T)* j = that.first.next;
|
||||
while (i !is null && j !is null)
|
||||
{
|
||||
if (i.content != j.content)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
i = i.next;
|
||||
j = j.next;
|
||||
}
|
||||
return i is null && j is null;
|
||||
}
|
||||
|
||||
deprecated
|
||||
bool opEquals(in typeof(this) that) const
|
||||
{
|
||||
return opEquals(that);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
deprecated
|
||||
bool opEquals(ref typeof(this) that)
|
||||
{
|
||||
Entry!T* i = first.next;
|
||||
Entry!T* j = that.first.next;
|
||||
while (i !is null && j !is null)
|
||||
{
|
||||
if (i.content != j.content)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
i = i.next;
|
||||
j = j.next;
|
||||
}
|
||||
return i is null && j is null;
|
||||
}
|
||||
|
||||
deprecated
|
||||
bool opEquals(typeof(this) that)
|
||||
{
|
||||
return opEquals(that);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: First element.
|
||||
*/
|
||||
deprecated("Use dequeue instead.")
|
||||
@property ref inout(T) front() inout
|
||||
in
|
||||
{
|
||||
@ -83,13 +168,12 @@ class Queue(T)
|
||||
*
|
||||
* Params:
|
||||
* x = New element.
|
||||
*
|
||||
* Returns: $(D_KEYWORD this).
|
||||
*/
|
||||
void insertBack(T x)
|
||||
ref typeof(this) enqueue(ref T x)
|
||||
{
|
||||
Entry* temp = make!Entry(allocator);
|
||||
|
||||
temp.content = x;
|
||||
|
||||
auto temp = allocator.make!(Entry!T)(x);
|
||||
if (empty)
|
||||
{
|
||||
first.next = rear = temp;
|
||||
@ -99,29 +183,36 @@ class Queue(T)
|
||||
rear.next = temp;
|
||||
rear = rear.next;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
alias insert = insertBack;
|
||||
ref typeof(this) enqueue(T x)
|
||||
{
|
||||
return enqueue(x);
|
||||
}
|
||||
|
||||
deprecated("Use enqueue instead.")
|
||||
alias insert = enqueue;
|
||||
|
||||
deprecated("Use enqueue instead.")
|
||||
alias insertBack = enqueue;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(theAllocator);
|
||||
Queue!int q;
|
||||
|
||||
assert(q.empty);
|
||||
q.insertBack(8);
|
||||
assert(q.front == 8);
|
||||
q.insertBack(9);
|
||||
assert(q.front == 8);
|
||||
|
||||
dispose(theAllocator, q);
|
||||
q.enqueue(8).enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: $(D_KEYWORD true) if the queue is empty.
|
||||
*/
|
||||
@property bool empty() inout const
|
||||
@property bool empty() const
|
||||
{
|
||||
return first.next is null;
|
||||
}
|
||||
@ -129,79 +220,82 @@ class Queue(T)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(theAllocator);
|
||||
Queue!int q;
|
||||
int value = 7;
|
||||
|
||||
assert(q.empty);
|
||||
q.insertBack(value);
|
||||
q.enqueue(value);
|
||||
assert(!q.empty);
|
||||
|
||||
dispose(theAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the position to the next element.
|
||||
*
|
||||
* Returns: Dequeued element.
|
||||
*/
|
||||
void popFront()
|
||||
T dequeue()
|
||||
in
|
||||
{
|
||||
assert(!empty);
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
auto n = first.next.next;
|
||||
T ret = move(first.next.content);
|
||||
|
||||
dispose(allocator, first.next);
|
||||
first.next = n;
|
||||
return ret;
|
||||
}
|
||||
|
||||
deprecated("Use dequeue instead.")
|
||||
alias popFront = dequeue;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = make!(Queue!int)(theAllocator);
|
||||
Queue!int q;
|
||||
|
||||
q.insertBack(8);
|
||||
q.insertBack(9);
|
||||
assert(q.front == 8);
|
||||
q.popFront();
|
||||
assert(q.front == 9);
|
||||
|
||||
dispose(theAllocator, q);
|
||||
q.enqueue(8).enqueue(9);
|
||||
assert(q.dequeue() == 8);
|
||||
assert(q.dequeue() == 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* $(D_KEYWORD foreach) iteration.
|
||||
* $(D_KEYWORD foreach) iteration. The elements will be automatically
|
||||
* dequeued.
|
||||
*
|
||||
* Params:
|
||||
* dg = $(D_KEYWORD foreach) body.
|
||||
*/
|
||||
int opApply(scope int delegate(ref size_t i, ref T) dg)
|
||||
int opApply(scope int delegate(ref size_t i, ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
for (size_t i = 0; !empty; ++i)
|
||||
{
|
||||
if ((result = dg(i, front)) != 0)
|
||||
auto e = dequeue();
|
||||
if ((result = dg(i, e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
popFront();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int opApply(scope int delegate(ref T) dg)
|
||||
int opApply(scope int delegate(ref T) @nogc dg)
|
||||
{
|
||||
int result;
|
||||
|
||||
while (!empty)
|
||||
{
|
||||
if ((result = dg(front)) != 0)
|
||||
auto e = dequeue();
|
||||
if ((result = dg(e)) != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
popFront();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -209,12 +303,10 @@ class Queue(T)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = theAllocator.make!(Queue!int);
|
||||
Queue!int q;
|
||||
|
||||
size_t j;
|
||||
q.insertBack(5);
|
||||
q.insertBack(4);
|
||||
q.insertBack(9);
|
||||
q.enqueue(5).enqueue(4).enqueue(9);
|
||||
foreach (i, e; q)
|
||||
{
|
||||
assert(i != 2 || e == 9);
|
||||
@ -226,9 +318,7 @@ class Queue(T)
|
||||
assert(q.empty);
|
||||
|
||||
j = 0;
|
||||
q.insertBack(5);
|
||||
q.insertBack(4);
|
||||
q.insertBack(9);
|
||||
q.enqueue(5).enqueue(4).enqueue(9);
|
||||
foreach (e; q)
|
||||
{
|
||||
assert(j != 2 || e == 9);
|
||||
@ -238,48 +328,28 @@ class Queue(T)
|
||||
}
|
||||
assert(j == 3);
|
||||
assert(q.empty);
|
||||
|
||||
dispose(theAllocator, q);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue entry.
|
||||
*/
|
||||
protected struct Entry
|
||||
{
|
||||
/// Queue item content.
|
||||
T content;
|
||||
|
||||
/// Next list item.
|
||||
Entry* next;
|
||||
}
|
||||
|
||||
/// The first element of the list.
|
||||
protected Entry first;
|
||||
private Entry!T first;
|
||||
|
||||
/// The last element of the list.
|
||||
protected Entry* rear;
|
||||
private Entry!T* rear;
|
||||
|
||||
/// The allocator.
|
||||
protected IAllocator allocator;
|
||||
mixin DefaultAllocator;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto q = theAllocator.make!(Queue!int);
|
||||
Queue!int q;
|
||||
|
||||
q.insertBack(5);
|
||||
q.enqueue(5);
|
||||
assert(!q.empty);
|
||||
|
||||
q.insertBack(4);
|
||||
assert(q.front == 5);
|
||||
q.enqueue(4).enqueue(9);
|
||||
|
||||
q.insertBack(9);
|
||||
assert(q.front == 5);
|
||||
|
||||
q.popFront();
|
||||
assert(q.front == 4);
|
||||
assert(q.dequeue() == 5);
|
||||
|
||||
foreach (i, ref e; q)
|
||||
{
|
||||
@ -287,6 +357,4 @@ unittest
|
||||
assert(i != 1 || e == 9);
|
||||
}
|
||||
assert(q.empty);
|
||||
|
||||
theAllocator.dispose(q);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.container.bit;
|
||||
module tanya.crypto.bit;
|
||||
|
||||
/**
|
||||
* Wrapper that allows bit manipulation on $(D_KEYWORD ubyte[]) array.
|
@ -10,7 +10,7 @@
|
||||
*/
|
||||
module tanya.crypto.des;
|
||||
|
||||
import tanya.container.bit;
|
||||
import tanya.crypto.bit;
|
||||
import tanya.crypto.symmetric;
|
||||
|
||||
/// Initial permutation table.
|
||||
|
@ -44,7 +44,7 @@ enum PaddingMode
|
||||
ubyte[] pad(ref ubyte[] input,
|
||||
in PaddingMode mode,
|
||||
in ushort blockSize,
|
||||
IAllocator allocator = theAllocator)
|
||||
shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(blockSize > 0 && blockSize <= 256);
|
||||
@ -60,21 +60,21 @@ body
|
||||
final switch (mode) with (PaddingMode)
|
||||
{
|
||||
case zero:
|
||||
allocator.expandArray(input, needed);
|
||||
allocator.resizeArray(input, input.length + needed);
|
||||
break;
|
||||
case pkcs7:
|
||||
if (needed)
|
||||
{
|
||||
allocator.expandArray(input, needed);
|
||||
allocator.resizeArray(input, input.length + needed);
|
||||
input[input.length - needed ..$].each!((ref e) => e = needed);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.expandArray(input, blockSize);
|
||||
allocator.resizeArray(input, input.length + blockSize);
|
||||
}
|
||||
break;
|
||||
case ansiX923:
|
||||
allocator.expandArray(input, needed ? needed : blockSize);
|
||||
allocator.resizeArray(input, input.length + (needed ? needed : blockSize));
|
||||
input[$ - 1] = needed;
|
||||
break;
|
||||
}
|
||||
@ -86,7 +86,7 @@ body
|
||||
unittest
|
||||
{
|
||||
{ // Zeros
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
|
||||
pad(input, PaddingMode.zero, 64);
|
||||
assert(input.length == 64);
|
||||
@ -95,10 +95,10 @@ unittest
|
||||
assert(input.length == 64);
|
||||
assert(input[63] == 0);
|
||||
|
||||
theAllocator.dispose(input);
|
||||
defaultAllocator.dispose(input);
|
||||
}
|
||||
{ // PKCS#7
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
@ -140,10 +140,10 @@ unittest
|
||||
}
|
||||
}
|
||||
|
||||
theAllocator.dispose(input);
|
||||
defaultAllocator.dispose(input);
|
||||
}
|
||||
{ // ANSI X.923
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
@ -185,7 +185,7 @@ unittest
|
||||
}
|
||||
}
|
||||
|
||||
theAllocator.dispose(input);
|
||||
defaultAllocator.dispose(input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ unittest
|
||||
ref ubyte[] unpad(ref ubyte[] input,
|
||||
in PaddingMode mode,
|
||||
in ushort blockSize,
|
||||
IAllocator allocator = theAllocator)
|
||||
shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(input.length != 0);
|
||||
@ -220,7 +220,7 @@ body
|
||||
case ansiX923:
|
||||
immutable last = input[$ - 1];
|
||||
|
||||
allocator.shrinkArray(input, last ? last : blockSize);
|
||||
allocator.resizeArray(input, input.length - (last ? last : blockSize));
|
||||
break;
|
||||
}
|
||||
|
||||
@ -231,8 +231,8 @@ body
|
||||
unittest
|
||||
{
|
||||
{ // Zeros
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
|
||||
pad(input, PaddingMode.zero, 64);
|
||||
pad(inputDup, PaddingMode.zero, 64);
|
||||
@ -240,13 +240,13 @@ unittest
|
||||
unpad(input, PaddingMode.zero, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
theAllocator.dispose(input);
|
||||
theAllocator.dispose(inputDup);
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
|
||||
}
|
||||
{ // PKCS#7
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
@ -257,12 +257,12 @@ unittest
|
||||
unpad(input, PaddingMode.pkcs7, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
theAllocator.dispose(input);
|
||||
theAllocator.dispose(inputDup);
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
}
|
||||
{ // ANSI X.923
|
||||
auto input = theAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = theAllocator.makeArray!ubyte(50);
|
||||
auto input = defaultAllocator.makeArray!ubyte(50);
|
||||
auto inputDup = defaultAllocator.makeArray!ubyte(50);
|
||||
for (ubyte i; i < 40; ++i)
|
||||
{
|
||||
input[i] = i;
|
||||
@ -273,7 +273,7 @@ unittest
|
||||
unpad(input, PaddingMode.pkcs7, 64);
|
||||
assert(input == inputDup);
|
||||
|
||||
theAllocator.dispose(input);
|
||||
theAllocator.dispose(inputDup);
|
||||
defaultAllocator.dispose(input);
|
||||
defaultAllocator.dispose(inputDup);
|
||||
}
|
||||
}
|
||||
|
@ -10,9 +10,7 @@
|
||||
*/
|
||||
module tanya.crypto;
|
||||
|
||||
public
|
||||
{
|
||||
import tanya.crypto.des;
|
||||
import tanya.crypto.mode;
|
||||
import tanya.crypto.symmetric;
|
||||
}
|
||||
public import tanya.crypto.bit;
|
||||
public import tanya.crypto.des;
|
||||
public import tanya.crypto.mode;
|
||||
public import tanya.crypto.symmetric;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,7 +10,9 @@
|
||||
*/
|
||||
module tanya.math;
|
||||
|
||||
import std.traits;
|
||||
public import tanya.math.mp;
|
||||
public import tanya.math.random;
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
@ -20,26 +22,32 @@ version (unittest)
|
||||
/**
|
||||
* Computes $(D_PARAM x) to the power $(D_PARAM y) modulo $(D_PARAM z).
|
||||
*
|
||||
* If $(D_PARAM I) is an $(D_PSYMBOL Integer), the allocator of $(D_PARAM x)
|
||||
* is used to allocate the result.
|
||||
*
|
||||
* Params:
|
||||
* I = Base type.
|
||||
* G = Exponent type.
|
||||
* H = Divisor type:
|
||||
* x = Base.
|
||||
* y = Exponent.
|
||||
* z = Divisor.
|
||||
*
|
||||
* Returns: Reminder of the division of $(D_PARAM x) to the power $(D_PARAM y)
|
||||
* by $(D_PARAM z).
|
||||
*
|
||||
* Precondition: $(D_INLINECODE z > 0)
|
||||
*/
|
||||
ulong pow(ulong x, ulong y, ulong z) nothrow pure @safe @nogc
|
||||
H pow(I, G, H)(in auto ref I x, in auto ref G y, in auto ref H z)
|
||||
if (isIntegral!I && isIntegral!G && isIntegral!H)
|
||||
in
|
||||
{
|
||||
assert(z > 0);
|
||||
}
|
||||
out (result)
|
||||
{
|
||||
assert(result >= 0);
|
||||
assert(z > 0, "Division by zero.");
|
||||
}
|
||||
body
|
||||
{
|
||||
ulong mask = ulong.max / 2 + 1, result;
|
||||
G mask = G.max / 2 + 1;
|
||||
H result;
|
||||
|
||||
if (y == 0)
|
||||
{
|
||||
@ -51,7 +59,7 @@ body
|
||||
}
|
||||
do
|
||||
{
|
||||
auto bit = y & mask;
|
||||
immutable bit = y & mask;
|
||||
if (!result && bit)
|
||||
{
|
||||
result = x;
|
||||
@ -64,15 +72,53 @@ body
|
||||
result *= x;
|
||||
}
|
||||
result %= z;
|
||||
|
||||
}
|
||||
while (mask >>= 1);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
I pow(I)(in auto ref I x, in auto ref I y, in auto ref I z)
|
||||
if (is(I == Integer))
|
||||
in
|
||||
{
|
||||
assert(z.length > 0, "Division by zero.");
|
||||
}
|
||||
body
|
||||
{
|
||||
size_t i = y.length;
|
||||
auto tmp2 = Integer(x.allocator), tmp1 = Integer(x, x.allocator);
|
||||
Integer result = Integer(x.allocator);
|
||||
|
||||
if (x.length == 0 && i != 0)
|
||||
{
|
||||
i = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = 1;
|
||||
}
|
||||
while (i)
|
||||
{
|
||||
--i;
|
||||
for (ubyte mask = 0x01; mask; mask <<= 1)
|
||||
{
|
||||
if (y.rep[i] & mask)
|
||||
{
|
||||
result *= tmp1;
|
||||
result %= z;
|
||||
}
|
||||
tmp2 = tmp1;
|
||||
tmp1 *= tmp2;
|
||||
tmp1 %= z;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
pure nothrow @safe @nogc unittest
|
||||
{
|
||||
assert(pow(3, 5, 7) == 5);
|
||||
assert(pow(2, 2, 1) == 0);
|
||||
@ -85,6 +131,20 @@ unittest
|
||||
assert(pow(0, 5, 5) == 0);
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
assert(cast(long) pow(Integer(3), Integer(5), Integer(7)) == 5);
|
||||
assert(cast(long) pow(Integer(2), Integer(2), Integer(1)) == 0);
|
||||
assert(cast(long) pow(Integer(3), Integer(3), Integer(3)) == 0);
|
||||
assert(cast(long) pow(Integer(7), Integer(4), Integer(2)) == 1);
|
||||
assert(cast(long) pow(Integer(53), Integer(0), Integer(2)) == 1);
|
||||
assert(cast(long) pow(Integer(53), Integer(1), Integer(3)) == 2);
|
||||
assert(cast(long) pow(Integer(53), Integer(2), Integer(5)) == 4);
|
||||
assert(cast(long) pow(Integer(0), Integer(0), Integer(5)) == 1);
|
||||
assert(cast(long) pow(Integer(0), Integer(5), Integer(5)) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $(D_PARAM x) is a prime.
|
||||
*
|
||||
|
@ -3,16 +3,18 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Random number generator.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.random;
|
||||
module tanya.math.random;
|
||||
|
||||
import tanya.memory;
|
||||
import std.digest.sha;
|
||||
import std.typecons;
|
||||
import tanya.memory;
|
||||
|
||||
/// Block size of entropy accumulator (SHA-512).
|
||||
enum blockSize = 64;
|
||||
@ -148,13 +150,13 @@ version (linux)
|
||||
/**
|
||||
* Pseudorandom number generator.
|
||||
* ---
|
||||
* auto entropy = theAllocator.make!Entropy;
|
||||
* auto entropy = defaultAllocator.make!Entropy();
|
||||
*
|
||||
* ubyte[blockSize] output;
|
||||
*
|
||||
* output = entropy.random;
|
||||
*
|
||||
* theAllocator.finalize(entropy);
|
||||
* defaultAllocator.finalize(entropy);
|
||||
* ---
|
||||
*/
|
||||
class Entropy
|
||||
@ -164,7 +166,7 @@ class Entropy
|
||||
|
||||
private ubyte sourceCount_;
|
||||
|
||||
private IAllocator allocator;
|
||||
private shared Allocator allocator;
|
||||
|
||||
/// Entropy accumulator.
|
||||
protected SHA!(maxGather * 8, 512) accumulator;
|
||||
@ -175,7 +177,7 @@ class Entropy
|
||||
* allocator = Allocator to allocate entropy sources available on the
|
||||
* system.
|
||||
*/
|
||||
this(size_t maxSources = 20, IAllocator allocator = theAllocator)
|
||||
this(size_t maxSources = 20, shared Allocator allocator = defaultAllocator)
|
||||
in
|
||||
{
|
||||
assert(maxSources > 0 && maxSources <= ubyte.max);
|
@ -10,173 +10,118 @@
|
||||
*/
|
||||
module tanya.memory.allocator;
|
||||
|
||||
import std.experimental.allocator;
|
||||
import std.traits;
|
||||
import std.typecons;
|
||||
|
||||
/**
|
||||
* Abstract class implementing a basic allocator.
|
||||
*/
|
||||
abstract class Allocator : IAllocator
|
||||
interface Allocator
|
||||
{
|
||||
/**
|
||||
* Not supported.
|
||||
*
|
||||
* Returns: $(D_KEYWORD false).
|
||||
* Returns: Alignment offered.
|
||||
*/
|
||||
bool deallocateAll() const @nogc @safe pure nothrow
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@property uint alignment() const shared pure nothrow @safe @nogc;
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Returns $(D_PSYMBOL Ternary.unknown).
|
||||
*/
|
||||
Ternary empty() const @nogc @safe pure nothrow
|
||||
{
|
||||
return Ternary.unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
*
|
||||
* Params:
|
||||
* b = Memory block.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL Ternary.unknown).
|
||||
*/
|
||||
Ternary owns(void[] b) const @nogc @safe pure nothrow
|
||||
{
|
||||
return Ternary.unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
*
|
||||
* Params:
|
||||
* p = Pointer to a memory block.
|
||||
* result = Full block allocated.
|
||||
*
|
||||
* Returns: $(D_PSYMBOL Ternary.unknown).
|
||||
*/
|
||||
Ternary resolveInternalPointer(void* p, ref void[] result)
|
||||
const @nogc @safe pure nothrow
|
||||
{
|
||||
return Ternary.unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The good allocation size that guarantees zero internal
|
||||
* fragmentation.
|
||||
* Returns: Pointer to the new allocated memory.
|
||||
*/
|
||||
size_t goodAllocSize(size_t s)
|
||||
{
|
||||
auto rem = s % alignment;
|
||||
return rem ? s + alignment - rem : s;
|
||||
}
|
||||
void[] allocate(in size_t size) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
*
|
||||
* Returns: $(D_KEYWORD null).
|
||||
*
|
||||
*/
|
||||
void[] allocateAll() const @nogc @safe pure nothrow
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
* Deallocates a memory block.
|
||||
*
|
||||
* Params:
|
||||
* b = Block to be expanded.
|
||||
* s = New size.
|
||||
* p = A pointer to the memory block to be freed.
|
||||
*
|
||||
* Returns: $(D_KEYWORD false).
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool expand(ref void[] b, size_t s) const @nogc @safe pure nothrow
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool deallocate(void[] p) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
* Params:
|
||||
* n = Amount of memory to allocate.
|
||||
* a = Alignment.
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD null).
|
||||
* Returns: Pointer to the allocated memory.
|
||||
*/
|
||||
void[] alignedAllocate(size_t n, uint a) const @nogc @safe pure nothrow
|
||||
{
|
||||
return null;
|
||||
}
|
||||
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
|
||||
/**
|
||||
* Not supported.
|
||||
* Expands a memory block in place.
|
||||
*
|
||||
* Params:
|
||||
* n = Amount of memory to allocate.
|
||||
* a = Alignment.
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD false).
|
||||
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool alignedReallocate(ref void[] b, size_t size, uint alignment)
|
||||
const @nogc @safe pure nothrow
|
||||
{
|
||||
return false;
|
||||
}
|
||||
bool expand(ref void[] p, in size_t size) shared nothrow @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* T = Element type of the array being created.
|
||||
* allocator = The allocator used for getting memory.
|
||||
* array = A reference to the array being changed.
|
||||
* length = New array length.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
|
||||
* not be reallocated. In the latter
|
||||
* The mixin generates common methods for classes and structs using
|
||||
* allocators. It provides a protected member, constructor and a read-only property,
|
||||
* that checks if an allocator was already set and sets it to the default
|
||||
* one, if not (useful for structs which don't have a default constructor).
|
||||
*/
|
||||
bool resizeArray(T)(IAllocator allocator,
|
||||
ref T[] array,
|
||||
in size_t length)
|
||||
mixin template DefaultAllocator()
|
||||
{
|
||||
void[] buf = array;
|
||||
/// Allocator.
|
||||
protected shared Allocator allocator_;
|
||||
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
/**
|
||||
* Params:
|
||||
* allocator = The allocator should be used.
|
||||
*/
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
return false;
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator_ = allocator;
|
||||
}
|
||||
array = cast(T[]) buf;
|
||||
|
||||
return true;
|
||||
/**
|
||||
* This property checks if the allocator was set in the constructor
|
||||
* and sets it to the default one, if not.
|
||||
*
|
||||
* Returns: Used allocator.
|
||||
*
|
||||
* Postcondition: $(D_INLINECODE allocator_ !is null)
|
||||
*/
|
||||
protected @property shared(Allocator) allocator() nothrow @safe @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
allocator_ = defaultAllocator;
|
||||
}
|
||||
return allocator_;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
@property shared(Allocator) allocator() const nothrow @trusted @nogc
|
||||
out (allocator)
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
if (allocator_ is null)
|
||||
{
|
||||
return defaultAllocator;
|
||||
}
|
||||
return cast(shared Allocator) allocator_;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
int[] p;
|
||||
|
||||
theAllocator.resizeArray(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
theAllocator.resizeArray(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
theAllocator.resizeArray(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
theAllocator.resizeArray(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
enum bool isFinalizable(T) = is(T == class) || is(T == interface)
|
||||
|| hasElaborateDestructor!T || isDynamicArray!T;
|
||||
|
@ -10,9 +10,9 @@
|
||||
*/
|
||||
module tanya.memory.mmappool;
|
||||
|
||||
import core.stdc.string;
|
||||
import std.algorithm.comparison;
|
||||
import tanya.memory.allocator;
|
||||
import core.atomic;
|
||||
import core.exception;
|
||||
|
||||
version (Posix)
|
||||
{
|
||||
@ -27,7 +27,7 @@ else version (Windows)
|
||||
}
|
||||
|
||||
/**
|
||||
* This allocator allocates memory in regions (multiple of 4 KB for example).
|
||||
* This allocator allocates memory in regions (multiple of 64 KB for example).
|
||||
* Each region is then splitted in blocks. So it doesn't request the memory
|
||||
* from the operating system on each call, but only if there are no large
|
||||
* enough free blocks in the available regions.
|
||||
@ -36,7 +36,7 @@ else version (Windows)
|
||||
* block as free and only if all blocks in the region are free, the complete
|
||||
* region is deallocated.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
* | | | | | || | | |
|
||||
* | |prev <----------- | || | | |
|
||||
* | R | B | | B | || R | B | |
|
||||
@ -46,44 +46,19 @@ else version (Windows)
|
||||
* | O | K | | K | prev O | K | |
|
||||
* | N | -----------> next| || N | | |
|
||||
* | | | | | || | | |
|
||||
* --------------------------------------------------- ------------------------
|
||||
*
|
||||
* TODO:
|
||||
* $(UL
|
||||
* $(LI Thread safety (core.atomic.cas))
|
||||
* $(LI If two neighbour blocks are free, they can be merged)
|
||||
* $(LI Reallocation shoud check if there is enough free space in the
|
||||
* next block instead of always moving the memory)
|
||||
* $(LI Make 64 KB regions mininmal region size on Linux)
|
||||
* )
|
||||
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
*/
|
||||
class MmapPool : Allocator
|
||||
final class MmapPool : Allocator
|
||||
{
|
||||
@disable this();
|
||||
|
||||
shared static this()
|
||||
{
|
||||
version (Posix)
|
||||
{
|
||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
pageSize = si.dwPageSize;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocates $(D_PARAM size) bytes of memory.
|
||||
*
|
||||
* Params:
|
||||
* size = Amount of memory to allocate.
|
||||
*
|
||||
* Returns: The pointer to the new allocated memory.
|
||||
* Returns: Pointer to the new allocated memory.
|
||||
*/
|
||||
void[] allocate(size_t size, TypeInfo ti = null) @nogc @trusted nothrow
|
||||
void[] allocate(in size_t size) shared nothrow @nogc
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
@ -97,11 +72,11 @@ class MmapPool : Allocator
|
||||
data = initializeRegion(dataSize);
|
||||
}
|
||||
|
||||
return data is null ? null : data[0..size];
|
||||
return data is null ? null : data[0 .. size];
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
nothrow unittest
|
||||
{
|
||||
auto p = MmapPool.instance.allocate(20);
|
||||
|
||||
@ -115,16 +90,16 @@ class MmapPool : Allocator
|
||||
* into two blocks if the block is too large.
|
||||
*
|
||||
* Params:
|
||||
* size = Minimum size the block should have.
|
||||
* size = Minimum size the block should have (aligned).
|
||||
*
|
||||
* Returns: Data the block points to or $(D_KEYWORD null).
|
||||
*/
|
||||
private void* findBlock(size_t size) @nogc nothrow
|
||||
private void* findBlock(in ref size_t size) shared nothrow @nogc
|
||||
{
|
||||
Block block1;
|
||||
RegionLoop: for (auto r = head; r !is null; r = r.next)
|
||||
{
|
||||
block1 = cast(Block) (cast(void*) r + regionEntrySize);
|
||||
block1 = cast(Block) (cast(void*) r + RegionEntry.sizeof);
|
||||
do
|
||||
{
|
||||
if (block1.free && block1.size >= size)
|
||||
@ -138,35 +113,37 @@ class MmapPool : Allocator
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (block1.size >= size + alignment + blockEntrySize)
|
||||
else if (block1.size >= size + alignment_ + BlockEntry.sizeof)
|
||||
{ // Split the block if needed
|
||||
Block block2 = cast(Block) (cast(void*) block1 + blockEntrySize + size);
|
||||
Block block2 = cast(Block) (cast(void*) block1 + BlockEntry.sizeof + size);
|
||||
block2.prev = block1;
|
||||
if (block1.next is null)
|
||||
block2.next = block1.next;
|
||||
block2.free = true;
|
||||
block2.size = block1.size - BlockEntry.sizeof - size;
|
||||
block2.region = block1.region;
|
||||
|
||||
if (block1.next !is null)
|
||||
{
|
||||
block2.next = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
block2.next = block1.next.next;
|
||||
block1.next.prev = block2;
|
||||
}
|
||||
block1.next = block2;
|
||||
|
||||
block1.free = false;
|
||||
block2.free = true;
|
||||
|
||||
block2.size = block1.size - blockEntrySize - size;
|
||||
block1.size = size;
|
||||
|
||||
block2.region = block1.region;
|
||||
atomicOp!"+="(block1.region.blocks, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
block1.free = false;
|
||||
atomicOp!"+="(block1.region.blocks, 1);
|
||||
block1.region.blocks = block1.region.blocks + 1;
|
||||
|
||||
return cast(void*) block1 + BlockEntry.sizeof;
|
||||
}
|
||||
return cast(void*) block1 + blockEntrySize;
|
||||
|
||||
// Merge block with the next one.
|
||||
private void mergeNext(Block block) shared const pure nothrow @safe @nogc
|
||||
{
|
||||
block.size = block.size + BlockEntry.sizeof + block.next.size;
|
||||
if (block.next.next !is null)
|
||||
{
|
||||
block.next.next.prev = block;
|
||||
}
|
||||
block.next = block.next.next;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -177,14 +154,14 @@ class MmapPool : Allocator
|
||||
*
|
||||
* Returns: Whether the deallocation was successful.
|
||||
*/
|
||||
bool deallocate(void[] p) @nogc @trusted nothrow
|
||||
bool deallocate(void[] p) shared nothrow @nogc
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Block block = cast(Block) (p.ptr - blockEntrySize);
|
||||
Block block = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||
if (block.region.blocks <= 1)
|
||||
{
|
||||
if (block.region.prev !is null)
|
||||
@ -208,22 +185,129 @@ class MmapPool : Allocator
|
||||
return VirtualFree(cast(void*) block.region, 0, MEM_RELEASE) == 0;
|
||||
}
|
||||
}
|
||||
// Merge blocks if neigbours are free.
|
||||
if (block.next !is null && block.next.free)
|
||||
{
|
||||
mergeNext(block);
|
||||
}
|
||||
if (block.prev !is null && block.prev.free)
|
||||
{
|
||||
block.prev.size = block.prev.size + BlockEntry.sizeof + block.size;
|
||||
if (block.next !is null)
|
||||
{
|
||||
block.next.prev = block.prev;
|
||||
}
|
||||
block.prev.next = block.next;
|
||||
}
|
||||
else
|
||||
{
|
||||
block.free = true;
|
||||
atomicOp!"-="(block.region.blocks, 1);
|
||||
return true;
|
||||
}
|
||||
block.region.blocks = block.region.blocks - 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
nothrow unittest
|
||||
{
|
||||
auto p = MmapPool.instance.allocate(20);
|
||||
|
||||
assert(MmapPool.instance.deallocate(p));
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a memory block in place.
|
||||
*
|
||||
* Params:
|
||||
* p = A pointer to the memory block.
|
||||
* size = Size of the reallocated block.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) if successful, $(D_KEYWORD false) otherwise.
|
||||
*/
|
||||
bool expand(ref void[] p, in size_t size) shared nothrow @nogc
|
||||
{
|
||||
if (size <= p.length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (p is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Block block1 = cast(Block) (p.ptr - BlockEntry.sizeof);
|
||||
|
||||
if (block1.size >= size)
|
||||
{
|
||||
// Enough space in the current block. Can happen because of the alignment.
|
||||
p = p.ptr[0 .. size];
|
||||
return true;
|
||||
}
|
||||
immutable dataSize = addAlignment(size);
|
||||
immutable delta = dataSize - p.length;
|
||||
|
||||
if (block1.next is null
|
||||
|| !block1.next.free
|
||||
|| block1.next.size + BlockEntry.sizeof < delta)
|
||||
{
|
||||
// It is the last block in the region or the next block is too small or not
|
||||
// free.
|
||||
return false;
|
||||
}
|
||||
if (block1.next.size >= delta + alignment_)
|
||||
{
|
||||
// We should move the start position of the next block. The order may be
|
||||
// important because the old block and the new one can overlap.
|
||||
auto block2 = cast(Block) (p.ptr + dataSize);
|
||||
block2.size = block1.next.size - delta;
|
||||
block2.free = true;
|
||||
block2.region = block1.region;
|
||||
block2.next = block1.next.next;
|
||||
block2.prev = block1;
|
||||
|
||||
block1.size = block1.size + delta;
|
||||
|
||||
if (block1.next.next !is null)
|
||||
{
|
||||
block1.next.next.prev = block2;
|
||||
}
|
||||
block1.next = block2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The next block has enough space, but is too small for further
|
||||
// allocations. Merge it with the current block.
|
||||
mergeNext(block1);
|
||||
}
|
||||
|
||||
p = p.ptr[0 .. size];
|
||||
return true;
|
||||
}
|
||||
|
||||
///
|
||||
nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
assert(!MmapPool.instance.expand(p, 5));
|
||||
assert(p is null);
|
||||
|
||||
p = MmapPool.instance.allocate(1);
|
||||
auto orig = p.ptr;
|
||||
|
||||
assert(MmapPool.instance.expand(p, 2));
|
||||
assert(p.length == 2);
|
||||
assert(p.ptr == orig);
|
||||
|
||||
assert(MmapPool.instance.expand(p, 4));
|
||||
assert(p.length == 4);
|
||||
assert(p.ptr == orig);
|
||||
|
||||
assert(MmapPool.instance.expand(p, 2));
|
||||
assert(p.length == 4);
|
||||
assert(p.ptr == orig);
|
||||
|
||||
MmapPool.instance.deallocate(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases or decreases the size of a memory block.
|
||||
*
|
||||
@ -233,33 +317,36 @@ class MmapPool : Allocator
|
||||
*
|
||||
* Returns: Whether the reallocation was successful.
|
||||
*/
|
||||
bool reallocate(ref void[] p, size_t size) @nogc @trusted nothrow
|
||||
bool reallocate(ref void[] p, in size_t size) shared nothrow @nogc
|
||||
{
|
||||
void[] reallocP;
|
||||
|
||||
if (size == p.length)
|
||||
if (size == 0)
|
||||
{
|
||||
if (deallocate(p))
|
||||
{
|
||||
p = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else if (size <= p.length)
|
||||
{
|
||||
// Leave the block as is.
|
||||
p = p.ptr[0 .. size];
|
||||
return true;
|
||||
}
|
||||
else if (expand(p, size))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (size > 0)
|
||||
{
|
||||
reallocP = allocate(size);
|
||||
// Can't extend, allocate a new block, copy and delete the previous.
|
||||
void[] reallocP = allocate(size);
|
||||
if (reallocP is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (p !is null)
|
||||
{
|
||||
if (size > p.length)
|
||||
{
|
||||
reallocP[0..p.length] = p[0..$];
|
||||
}
|
||||
else if (size > 0)
|
||||
{
|
||||
reallocP[0..size] = p[0..size];
|
||||
}
|
||||
memcpy(reallocP.ptr, p.ptr, min(p.length, size));
|
||||
deallocate(p);
|
||||
}
|
||||
p = reallocP;
|
||||
@ -268,7 +355,7 @@ class MmapPool : Allocator
|
||||
}
|
||||
|
||||
///
|
||||
@nogc nothrow unittest
|
||||
nothrow unittest
|
||||
{
|
||||
void[] p;
|
||||
MmapPool.instance.reallocate(p, 10 * int.sizeof);
|
||||
@ -301,18 +388,34 @@ class MmapPool : Allocator
|
||||
*
|
||||
* Returns: Global $(D_PSYMBOL MmapPool) instance.
|
||||
*/
|
||||
static @property ref MmapPool instance() @nogc @trusted nothrow
|
||||
static @property ref shared(MmapPool) instance() nothrow @nogc
|
||||
{
|
||||
if (instance_ is null)
|
||||
{
|
||||
// Get system dependend page size.
|
||||
version (Posix)
|
||||
{
|
||||
pageSize = sysconf(_SC_PAGE_SIZE);
|
||||
if (pageSize < 65536)
|
||||
{
|
||||
pageSize = pageSize * 65536 / pageSize;
|
||||
}
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
pageSize = si.dwPageSize;
|
||||
}
|
||||
|
||||
immutable instanceSize = addAlignment(__traits(classInstanceSize, MmapPool));
|
||||
|
||||
Region head; // Will become soon our region list head
|
||||
void* data = initializeRegion(instanceSize, head);
|
||||
if (data !is null)
|
||||
{
|
||||
data[0..instanceSize] = typeid(MmapPool).initializer[];
|
||||
instance_ = cast(MmapPool) data;
|
||||
memcpy(data, typeid(MmapPool).initializer.ptr, instanceSize);
|
||||
instance_ = cast(shared MmapPool) data;
|
||||
instance_.head = head;
|
||||
}
|
||||
}
|
||||
@ -320,12 +423,12 @@ class MmapPool : Allocator
|
||||
}
|
||||
|
||||
///
|
||||
@nogc @safe nothrow unittest
|
||||
nothrow unittest
|
||||
{
|
||||
assert(instance is instance);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Initializes a region for one element.
|
||||
*
|
||||
* Params:
|
||||
@ -334,8 +437,8 @@ class MmapPool : Allocator
|
||||
*
|
||||
* Returns: A pointer to the data.
|
||||
*/
|
||||
private static void* initializeRegion(size_t size,
|
||||
ref Region head) @nogc nothrow
|
||||
private static void* initializeRegion(size_t size, ref Region head)
|
||||
nothrow @nogc
|
||||
{
|
||||
immutable regionSize = calculateRegionSize(size);
|
||||
|
||||
@ -349,10 +452,6 @@ class MmapPool : Allocator
|
||||
0);
|
||||
if (p is MAP_FAILED)
|
||||
{
|
||||
if (errno == ENOMEM)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -364,10 +463,6 @@ class MmapPool : Allocator
|
||||
PAGE_READWRITE);
|
||||
if (p is null)
|
||||
{
|
||||
if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -386,13 +481,13 @@ class MmapPool : Allocator
|
||||
head = region;
|
||||
|
||||
// Initialize the data block
|
||||
void* memoryPointer = p + regionEntrySize;
|
||||
void* memoryPointer = p + RegionEntry.sizeof;
|
||||
Block block1 = cast(Block) memoryPointer;
|
||||
block1.size = size;
|
||||
block1.free = false;
|
||||
|
||||
// It is what we want to return
|
||||
void* data = memoryPointer + blockEntrySize;
|
||||
void* data = memoryPointer + BlockEntry.sizeof;
|
||||
|
||||
// Free block after data
|
||||
memoryPointer = data + size;
|
||||
@ -400,28 +495,26 @@ class MmapPool : Allocator
|
||||
block1.prev = block2.next = null;
|
||||
block1.next = block2;
|
||||
block2.prev = block1;
|
||||
block2.size = regionSize - size - regionEntrySize - blockEntrySize * 2;
|
||||
block2.size = regionSize - size - RegionEntry.sizeof - BlockEntry.sizeof * 2;
|
||||
block2.free = true;
|
||||
block1.region = block2.region = region;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
private void* initializeRegion(size_t size) @nogc nothrow
|
||||
private void* initializeRegion(size_t size) shared nothrow @nogc
|
||||
{
|
||||
return initializeRegion(size, head);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Params:
|
||||
* x = Space to be aligned.
|
||||
*
|
||||
* Returns: Aligned size of $(D_PARAM x).
|
||||
*/
|
||||
pragma(inline)
|
||||
private static immutable(size_t) addAlignment(size_t x)
|
||||
@nogc @safe pure nothrow
|
||||
pure nothrow @safe @nogc
|
||||
out (result)
|
||||
{
|
||||
assert(result > 0);
|
||||
@ -431,34 +524,35 @@ class MmapPool : Allocator
|
||||
return (x - 1) / alignment_ * alignment_ + alignment_;
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Params:
|
||||
* x = Required space.
|
||||
*
|
||||
* Returns: Minimum region size (a multiple of $(D_PSYMBOL pageSize)).
|
||||
*/
|
||||
pragma(inline)
|
||||
private static immutable(size_t) calculateRegionSize(size_t x)
|
||||
@nogc @safe pure nothrow
|
||||
nothrow @safe @nogc
|
||||
out (result)
|
||||
{
|
||||
assert(result > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
x += regionEntrySize + blockEntrySize * 2;
|
||||
x += RegionEntry.sizeof + BlockEntry.sizeof * 2;
|
||||
return x / pageSize * pageSize + pageSize;
|
||||
}
|
||||
|
||||
@property uint alignment() const @nogc @safe pure nothrow
|
||||
/**
|
||||
* Returns: Alignment offered.
|
||||
*/
|
||||
@property uint alignment() shared const pure nothrow @safe @nogc
|
||||
{
|
||||
return alignment_;
|
||||
}
|
||||
private enum alignment_ = 8;
|
||||
|
||||
private static MmapPool instance_;
|
||||
|
||||
private shared static immutable size_t pageSize;
|
||||
private shared static MmapPool instance_;
|
||||
private shared static size_t pageSize;
|
||||
|
||||
private shared struct RegionEntry
|
||||
{
|
||||
@ -468,18 +562,15 @@ class MmapPool : Allocator
|
||||
size_t size;
|
||||
}
|
||||
private alias Region = shared RegionEntry*;
|
||||
private enum regionEntrySize = 32;
|
||||
|
||||
private shared Region head;
|
||||
|
||||
private shared struct BlockEntry
|
||||
{
|
||||
Block prev;
|
||||
Block next;
|
||||
bool free;
|
||||
size_t size;
|
||||
Region region;
|
||||
size_t size;
|
||||
bool free;
|
||||
}
|
||||
private alias Block = shared BlockEntry*;
|
||||
private enum blockEntrySize = 40;
|
||||
}
|
||||
|
@ -10,5 +10,225 @@
|
||||
*/
|
||||
module tanya.memory;
|
||||
|
||||
import core.exception;
|
||||
public import std.experimental.allocator : make, makeArray;
|
||||
import std.traits;
|
||||
public import tanya.memory.allocator;
|
||||
public import std.experimental.allocator;
|
||||
public import tanya.memory.types;
|
||||
|
||||
// From druntime
|
||||
private extern (C) void _d_monitordelete(Object h, bool det) nothrow @nogc;
|
||||
|
||||
shared Allocator allocator;
|
||||
|
||||
shared static this() nothrow @trusted @nogc
|
||||
{
|
||||
import tanya.memory.mmappool;
|
||||
allocator = MmapPool.instance;
|
||||
}
|
||||
|
||||
@property ref shared(Allocator) defaultAllocator() nothrow @safe @nogc
|
||||
{
|
||||
return allocator;
|
||||
}
|
||||
|
||||
@property void defaultAllocator(shared(Allocator) allocator) nothrow @safe @nogc
|
||||
{
|
||||
.allocator = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bytes of the state that needs to be allocated to hold an
|
||||
* object of type $(D_PARAM T).
|
||||
*
|
||||
* Params:
|
||||
* T = Object type.
|
||||
*/
|
||||
template stateSize(T)
|
||||
{
|
||||
static if (is(T == class) || is(T == interface))
|
||||
{
|
||||
enum stateSize = __traits(classInstanceSize, T);
|
||||
}
|
||||
else
|
||||
{
|
||||
enum stateSize = T.sizeof;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* size = Raw size.
|
||||
* alignment = Alignment.
|
||||
*
|
||||
* Returns: Aligned size.
|
||||
*/
|
||||
size_t alignedSize(in size_t size, in size_t alignment = 8) pure nothrow @safe @nogc
|
||||
{
|
||||
return (size - 1) / alignment * alignment + alignment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal function used to create, resize or destroy a dynamic array. It
|
||||
* throws $(D_PSYMBOL OutOfMemoryError) if $(D_PARAM Throws) is set. The new
|
||||
* allocated part of the array is initialized only if $(D_PARAM Init)
|
||||
* is set. This function can be trusted only in the data structures that
|
||||
* can ensure that the array is allocated/rellocated/deallocated with the
|
||||
* same allocator.
|
||||
*
|
||||
* Params:
|
||||
* T = Element type of the array being created.
|
||||
* Init = If should be initialized.
|
||||
* Throws = If $(D_PSYMBOL OutOfMemoryError) should be throwsn.
|
||||
* allocator = The allocator used for getting memory.
|
||||
* array = A reference to the array being changed.
|
||||
* length = New array length.
|
||||
*
|
||||
* Returns: $(D_KEYWORD true) upon success, $(D_KEYWORD false) if memory could
|
||||
* not be reallocated. In the latter
|
||||
*/
|
||||
package(tanya) bool resize(T,
|
||||
bool Init = true,
|
||||
bool Throws = true)
|
||||
(shared Allocator allocator,
|
||||
ref T[] array,
|
||||
in size_t length) @trusted
|
||||
{
|
||||
void[] buf = array;
|
||||
static if (Init)
|
||||
{
|
||||
immutable oldLength = array.length;
|
||||
}
|
||||
if (!allocator.reallocate(buf, length * T.sizeof))
|
||||
{
|
||||
static if (Throws)
|
||||
{
|
||||
onOutOfMemoryError;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// Casting from void[] is unsafe, but we know we cast to the original type.
|
||||
array = cast(T[]) buf;
|
||||
|
||||
static if (Init)
|
||||
{
|
||||
if (oldLength < length)
|
||||
{
|
||||
array[oldLength .. $] = T.init;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
package(tanya) alias resizeArray = resize;
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
int[] p;
|
||||
|
||||
defaultAllocator.resizeArray(p, 20);
|
||||
assert(p.length == 20);
|
||||
|
||||
defaultAllocator.resizeArray(p, 30);
|
||||
assert(p.length == 30);
|
||||
|
||||
defaultAllocator.resizeArray(p, 10);
|
||||
assert(p.length == 10);
|
||||
|
||||
defaultAllocator.resizeArray(p, 0);
|
||||
assert(p is null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys and deallocates $(D_PARAM p) of type $(D_PARAM T).
|
||||
* It is assumed the respective entities had been allocated with the same
|
||||
* allocator.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of $(D_PARAM p).
|
||||
* allocator = Allocator the $(D_PARAM p) was allocated with.
|
||||
* p = Object or array to be destroyed.
|
||||
*/
|
||||
void dispose(T)(shared Allocator allocator, auto ref T* p)
|
||||
{
|
||||
static if (hasElaborateDestructor!T)
|
||||
{
|
||||
destroy(*p);
|
||||
}
|
||||
() @trusted { allocator.deallocate((cast(void*) p)[0 .. T.sizeof]); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T p)
|
||||
if (is(T == class) || is(T == interface))
|
||||
{
|
||||
if (p is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
static if (is(T == interface))
|
||||
{
|
||||
version(Windows)
|
||||
{
|
||||
import core.sys.windows.unknwn : IUnknown;
|
||||
static assert(!is(T: IUnknown), "COM interfaces can't be destroyed in "
|
||||
~ __PRETTY_FUNCTION__);
|
||||
}
|
||||
auto ob = cast(Object) p;
|
||||
}
|
||||
else
|
||||
{
|
||||
alias ob = p;
|
||||
}
|
||||
auto ptr = cast(void *) ob;
|
||||
|
||||
auto support = ptr[0 .. typeid(ob).initializer.length];
|
||||
scope (success)
|
||||
{
|
||||
() @trusted { allocator.deallocate(support); }();
|
||||
p = null;
|
||||
}
|
||||
|
||||
auto ppv = cast(void**) ptr;
|
||||
if (!*ppv)
|
||||
{
|
||||
return;
|
||||
}
|
||||
auto pc = cast(ClassInfo*) *ppv;
|
||||
scope (exit)
|
||||
{
|
||||
*ppv = null;
|
||||
}
|
||||
|
||||
auto c = *pc;
|
||||
do
|
||||
{
|
||||
// Assume the destructor is @nogc. Leave it nothrow since the destructor
|
||||
// shouldn't throw and if it does, it is an error anyway.
|
||||
if (c.destructor)
|
||||
{
|
||||
(cast(void function (Object) nothrow @safe @nogc) c.destructor)(ob);
|
||||
}
|
||||
}
|
||||
while ((c = c.base) !is null);
|
||||
|
||||
if (ppv[1]) // if monitor is not null
|
||||
{
|
||||
_d_monitordelete(cast(Object) ptr, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void dispose(T)(shared Allocator allocator, auto ref T[] array)
|
||||
{
|
||||
static if (hasElaborateDestructor!(typeof(array[0])))
|
||||
{
|
||||
foreach (ref e; array)
|
||||
{
|
||||
destroy(e);
|
||||
}
|
||||
}
|
||||
() @trusted { allocator.deallocate(array); }();
|
||||
array = null;
|
||||
}
|
||||
|
458
source/tanya/memory/types.d
Normal file
458
source/tanya/memory/types.d
Normal file
@ -0,0 +1,458 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.memory.types;
|
||||
|
||||
import core.exception;
|
||||
import std.algorithm.comparison;
|
||||
import std.algorithm.mutation;
|
||||
import std.conv;
|
||||
import std.traits;
|
||||
import tanya.memory;
|
||||
|
||||
/**
|
||||
* Reference-counted object containing a $(D_PARAM T) value as payload.
|
||||
* $(D_PSYMBOL RefCounted) keeps track of all references of an object, and
|
||||
* when the reference count goes down to zero, frees the underlying store.
|
||||
*
|
||||
* Params:
|
||||
* T = Type of the reference-counted value.
|
||||
*/
|
||||
struct RefCounted(T)
|
||||
{
|
||||
static if (is(T == class) || is(T == interface))
|
||||
{
|
||||
private alias Payload = T;
|
||||
}
|
||||
else
|
||||
{
|
||||
private alias Payload = T*;
|
||||
}
|
||||
|
||||
private class Storage
|
||||
{
|
||||
private Payload payload;
|
||||
private size_t counter = 1;
|
||||
|
||||
private final size_t opUnary(string op)() pure nothrow @safe @nogc
|
||||
if (op == "--" || op == "++")
|
||||
in
|
||||
{
|
||||
assert(counter > 0);
|
||||
}
|
||||
body
|
||||
{
|
||||
mixin("return " ~ op ~ "counter;");
|
||||
}
|
||||
|
||||
private final int opCmp(size_t counter) const pure nothrow @safe @nogc
|
||||
{
|
||||
if (this.counter > counter)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
else if (this.counter < counter)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private final int opEquals(size_t counter) const pure nothrow @safe @nogc
|
||||
{
|
||||
return this.counter == counter;
|
||||
}
|
||||
}
|
||||
|
||||
private final class RefCountedStorage : Storage
|
||||
{
|
||||
private shared Allocator allocator;
|
||||
|
||||
this(shared Allocator allocator) pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator = allocator;
|
||||
}
|
||||
|
||||
~this() nothrow @nogc
|
||||
{
|
||||
allocator.dispose(payload);
|
||||
}
|
||||
}
|
||||
|
||||
private Storage storage;
|
||||
|
||||
invariant
|
||||
{
|
||||
assert(storage is null || allocator_ !is null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
||||
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
||||
*
|
||||
* Params:
|
||||
* value = Value whose ownership is taken over.
|
||||
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
||||
* allocate/deallocate internal storage.
|
||||
*
|
||||
* Precondition: $(D_INLINECODE allocator !is null)
|
||||
*/
|
||||
this(Payload value, shared Allocator allocator = defaultAllocator)
|
||||
{
|
||||
this(allocator);
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
move(value, storage.payload);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
this(shared Allocator allocator)
|
||||
in
|
||||
{
|
||||
assert(allocator !is null);
|
||||
}
|
||||
body
|
||||
{
|
||||
this.allocator_ = allocator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases the reference counter by one.
|
||||
*/
|
||||
this(this)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
++storage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decreases the reference counter by one.
|
||||
*
|
||||
* If the counter reaches 0, destroys the owned object.
|
||||
*/
|
||||
~this()
|
||||
{
|
||||
if (storage !is null && !(storage.counter && --storage))
|
||||
{
|
||||
allocator_.dispose(storage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes ownership over $(D_PARAM rhs). Initializes this
|
||||
* $(D_PSYMBOL RefCounted) if needed.
|
||||
*
|
||||
* If it is the last reference of the previously owned object,
|
||||
* it will be destroyed.
|
||||
*
|
||||
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
||||
*
|
||||
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||
* be used. If you need a different allocator, create a new
|
||||
* $(D_PSYMBOL RefCounted) and assign it.
|
||||
*
|
||||
* Params:
|
||||
* rhs = $(D_KEYWORD this).
|
||||
*/
|
||||
ref typeof(this) opAssign(Payload rhs)
|
||||
{
|
||||
if (storage is null)
|
||||
{
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
}
|
||||
else if (storage > 1)
|
||||
{
|
||||
--storage;
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
}
|
||||
else if (cast(RefCountedStorage) storage is null)
|
||||
{
|
||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||
assert(storage.counter != 0);
|
||||
allocator.dispose(storage);
|
||||
storage = allocator.make!RefCountedStorage(allocator);
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.dispose(storage.payload);
|
||||
}
|
||||
move(rhs, storage.payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(null))
|
||||
{
|
||||
if (storage is null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
else if (storage > 1)
|
||||
{
|
||||
--storage;
|
||||
storage = null;
|
||||
}
|
||||
else if (cast(RefCountedStorage) storage is null)
|
||||
{
|
||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
||||
assert(storage.counter != 0);
|
||||
allocator.dispose(storage);
|
||||
return this;
|
||||
}
|
||||
else
|
||||
{
|
||||
allocator.dispose(storage.payload);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
ref typeof(this) opAssign(typeof(this) rhs)
|
||||
{
|
||||
swap(allocator_, rhs.allocator_);
|
||||
swap(storage, rhs.storage);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Reference to the owned object.
|
||||
*/
|
||||
inout(Payload) get() inout pure nothrow @safe @nogc
|
||||
in
|
||||
{
|
||||
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||
}
|
||||
body
|
||||
{
|
||||
return storage.payload;
|
||||
}
|
||||
|
||||
static if (isPointer!Payload)
|
||||
{
|
||||
/**
|
||||
* Params:
|
||||
* op = Operation.
|
||||
*
|
||||
* Dereferences the pointer. It is defined only for pointers, not for
|
||||
* reference types like classes, that can be accessed directly.
|
||||
*
|
||||
* Returns: Reference to the pointed value.
|
||||
*/
|
||||
ref T opUnary(string op)()
|
||||
if (op == "*")
|
||||
{
|
||||
return *storage.payload;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether this $(D_PSYMBOL RefCounted) already has an internal
|
||||
* storage.
|
||||
*/
|
||||
@property bool isInitialized() const
|
||||
{
|
||||
return storage !is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The number of $(D_PSYMBOL RefCounted) instances that share
|
||||
* ownership over the same pointer (including $(D_KEYWORD this)).
|
||||
* If this $(D_PSYMBOL RefCounted) isn't initialized, returns `0`.
|
||||
*/
|
||||
@property size_t count() const
|
||||
{
|
||||
return storage is null ? 0 : storage.counter;
|
||||
}
|
||||
|
||||
mixin DefaultAllocator;
|
||||
alias get this;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto rc = RefCounted!int(defaultAllocator.make!int(5), defaultAllocator);
|
||||
auto val = rc.get;
|
||||
|
||||
*val = 8;
|
||||
assert(*rc.storage.payload == 8);
|
||||
|
||||
val = null;
|
||||
assert(rc.storage.payload !is null);
|
||||
assert(*rc.storage.payload == 8);
|
||||
|
||||
*rc = 9;
|
||||
assert(*rc.storage.payload == 9);
|
||||
}
|
||||
|
||||
version (unittest)
|
||||
{
|
||||
private class A
|
||||
{
|
||||
uint *destroyed;
|
||||
|
||||
this(ref uint destroyed) @nogc
|
||||
{
|
||||
this.destroyed = &destroyed;
|
||||
}
|
||||
|
||||
~this() @nogc
|
||||
{
|
||||
++(*destroyed);
|
||||
}
|
||||
}
|
||||
|
||||
private struct B
|
||||
{
|
||||
int prop;
|
||||
@disable this();
|
||||
this(int param1) @nogc
|
||||
{
|
||||
prop = param1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
uint destroyed;
|
||||
auto a = defaultAllocator.make!A(destroyed);
|
||||
|
||||
assert(destroyed == 0);
|
||||
{
|
||||
auto rc = RefCounted!A(a, defaultAllocator);
|
||||
assert(rc.count == 1);
|
||||
|
||||
void func(RefCounted!A rc)
|
||||
{
|
||||
assert(rc.count == 2);
|
||||
}
|
||||
func(rc);
|
||||
|
||||
assert(rc.count == 1);
|
||||
}
|
||||
assert(destroyed == 1);
|
||||
|
||||
RefCounted!int rc;
|
||||
assert(rc.count == 0);
|
||||
rc = defaultAllocator.make!int(8);
|
||||
assert(rc.count == 1);
|
||||
}
|
||||
|
||||
private unittest
|
||||
{
|
||||
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||
static assert(is(typeof(RefCounted!A.storage.payload) == A));
|
||||
|
||||
static assert(is(RefCounted!B));
|
||||
static assert(is(RefCounted!A));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||
* $(D_PSYMBOL RefCounted) using $(D_PARAM args) as the parameter list for
|
||||
* the constructor of $(D_PARAM T).
|
||||
*
|
||||
* This function is more efficient than the using of $(D_PSYMBOL RefCounted)
|
||||
* directly, since it allocates only ones (the internal storage and the
|
||||
* object).
|
||||
*
|
||||
* Params:
|
||||
* T = Type of the constructed object.
|
||||
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
||||
* allocator = Allocator.
|
||||
* args = Constructor arguments of $(D_PARAM T).
|
||||
*
|
||||
* Returns: Newly created $(D_PSYMBOL RefCounted!T).
|
||||
*/
|
||||
RefCounted!T refCounted(T, A...)(shared Allocator allocator, auto ref A args)
|
||||
if (!is(T == interface) && !isAbstractClass!T
|
||||
&& !isArray!T && !isAssociativeArray!T)
|
||||
{
|
||||
auto rc = typeof(return)(allocator);
|
||||
|
||||
immutable storageSize = alignedSize(stateSize!(RefCounted!T.Storage));
|
||||
immutable size = alignedSize(stateSize!T + storageSize);
|
||||
|
||||
auto mem = (() @trusted => allocator.allocate(size))();
|
||||
if (mem is null)
|
||||
{
|
||||
onOutOfMemoryError();
|
||||
}
|
||||
scope (failure)
|
||||
{
|
||||
() @trusted { allocator.deallocate(mem); }();
|
||||
}
|
||||
rc.storage = emplace!(RefCounted!T.Storage)(mem[0 .. storageSize]);
|
||||
|
||||
static if (is(T == class))
|
||||
{
|
||||
rc.storage.payload = emplace!T(mem[storageSize .. $], args);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||
rc.storage.payload = emplace!T(ptr, args);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!int(5);
|
||||
assert(rc.count == 1);
|
||||
|
||||
void func(RefCounted!int param)
|
||||
{
|
||||
if (param.count == 2)
|
||||
{
|
||||
func(param);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(param.count == 3);
|
||||
}
|
||||
}
|
||||
func(rc);
|
||||
|
||||
assert(rc.count == 1);
|
||||
}
|
||||
|
||||
private @nogc unittest
|
||||
{
|
||||
struct E
|
||||
{
|
||||
}
|
||||
auto b = defaultAllocator.refCounted!B(15);
|
||||
static assert(is(typeof(b.storage.payload) == B*));
|
||||
static assert(is(typeof(b.prop) == int));
|
||||
static assert(!is(typeof(defaultAllocator.refCounted!B())));
|
||||
|
||||
static assert(is(typeof(defaultAllocator.refCounted!E())));
|
||||
static assert(!is(typeof(defaultAllocator.refCounted!E(5))));
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!B(3);
|
||||
assert(rc.get.prop == 3);
|
||||
}
|
||||
{
|
||||
auto rc = defaultAllocator.refCounted!E();
|
||||
assert(rc.count);
|
||||
}
|
||||
}
|
49
source/tanya/meta/gen.d
Normal file
49
source/tanya/meta/gen.d
Normal file
@ -0,0 +1,49 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Templates that generate values.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.meta.gen;
|
||||
|
||||
import std.traits;
|
||||
|
||||
/**
|
||||
* Initializer list.
|
||||
*
|
||||
* Generates a static array with elements from $(D_PARAM args). All elements
|
||||
* should have the same type. It can be used in constructors which accept a
|
||||
* list of the elements of the same type in the situations where variadic
|
||||
* functions and templates can't be used.
|
||||
*
|
||||
* Params:
|
||||
* Args = Argument type.
|
||||
* args = Arguments.
|
||||
*/
|
||||
enum IL(Args...)(Args args)
|
||||
if (Args.length > 0)
|
||||
{
|
||||
alias BaseType = typeof(args[0]);
|
||||
|
||||
BaseType[args.length] result;
|
||||
|
||||
foreach (i, a; args)
|
||||
{
|
||||
static assert(isImplicitlyConvertible!(typeof(a), BaseType));
|
||||
result[i] = a;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
///
|
||||
unittest
|
||||
{
|
||||
static assert(IL(1, 5, 8).length == 3);
|
||||
static assert(IL(1, 5, 8).sizeof == 3 * int.sizeof);
|
||||
}
|
15
source/tanya/meta/package.d
Normal file
15
source/tanya/meta/package.d
Normal file
@ -0,0 +1,15 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Metaprogramming.
|
||||
*
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.meta;
|
||||
|
||||
public import tanya.meta.gen;
|
@ -6,7 +6,7 @@
|
||||
* Copyright: Eugene Wissner 2016.
|
||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||
* Mozilla Public License, v. 2.0).
|
||||
* Authors: $(LINK2 mailto:belka@caraus.de, Eugene Wissner)
|
||||
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||
*/
|
||||
module tanya.network.socket;
|
||||
|
||||
@ -15,9 +15,8 @@ import core.stdc.errno;
|
||||
import core.time;
|
||||
import std.algorithm.comparison;
|
||||
import std.algorithm.searching;
|
||||
public import std.socket : AddressException, socket_t, Linger, SocketOptionLevel,
|
||||
SocketType, AddressFamily, AddressInfo,
|
||||
SocketOption;
|
||||
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
|
||||
SocketType, AddressFamily, AddressInfo;
|
||||
import std.traits;
|
||||
import std.typecons;
|
||||
|
||||
@ -154,6 +153,23 @@ else version (Windows)
|
||||
DWORD lpFlags,
|
||||
LPOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
int WSAIoctl(SOCKET s,
|
||||
uint dwIoControlCode,
|
||||
void* lpvInBuffer,
|
||||
uint cbInBuffer,
|
||||
void* lpvOutBuffer,
|
||||
uint cbOutBuffer,
|
||||
uint* lpcbBytesReturned,
|
||||
LPWSAOVERLAPPED lpOverlapped,
|
||||
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
|
||||
alias LPFN_ACCEPTEX = BOOL function(SOCKET,
|
||||
SOCKET,
|
||||
PVOID,
|
||||
DWORD,
|
||||
DWORD,
|
||||
DWORD,
|
||||
LPDWORD,
|
||||
LPOVERLAPPED);
|
||||
}
|
||||
alias WSASocket = WSASocketW;
|
||||
|
||||
@ -213,7 +229,7 @@ else version (Windows)
|
||||
* handle = Socket handle.
|
||||
* af = Address family.
|
||||
*/
|
||||
this(socket_t handle, AddressFamily af)
|
||||
this(socket_t handle, AddressFamily af) @nogc
|
||||
{
|
||||
super(handle, af);
|
||||
}
|
||||
@ -233,7 +249,7 @@ else version (Windows)
|
||||
*/
|
||||
bool beginReceive(ubyte[] buffer,
|
||||
SocketState overlapped,
|
||||
Flags flags = Flags(Flag.none)) @trusted
|
||||
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||
{
|
||||
auto receiveFlags = cast(DWORD) flags;
|
||||
|
||||
@ -252,7 +268,7 @@ else version (Windows)
|
||||
|
||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to receive");
|
||||
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
@ -267,7 +283,7 @@ else version (Windows)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||
*/
|
||||
int endReceive(SocketState overlapped) @trusted
|
||||
int endReceive(SocketState overlapped) @nogc @trusted
|
||||
out (count)
|
||||
{
|
||||
assert(count >= 0);
|
||||
@ -282,7 +298,7 @@ else version (Windows)
|
||||
if (result == FALSE && !wouldHaveBlocked)
|
||||
{
|
||||
disconnected_ = true;
|
||||
throw theAllocator.make!SocketException("Unable to receive");
|
||||
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||
}
|
||||
if (lpNumber == 0)
|
||||
{
|
||||
@ -306,7 +322,7 @@ else version (Windows)
|
||||
*/
|
||||
bool beginSend(ubyte[] buffer,
|
||||
SocketState overlapped,
|
||||
Flags flags = Flags(Flag.none)) @trusted
|
||||
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||
{
|
||||
overlapped.handle = cast(HANDLE) handle_;
|
||||
overlapped.event = OverlappedSocketEvent.write;
|
||||
@ -324,7 +340,7 @@ else version (Windows)
|
||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
||||
{
|
||||
disconnected_ = true;
|
||||
throw theAllocator.make!SocketException("Unable to send");
|
||||
throw defaultAllocator.make!SocketException("Unable to send");
|
||||
}
|
||||
return result == 0;
|
||||
}
|
||||
@ -339,7 +355,7 @@ else version (Windows)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||
*/
|
||||
int endSend(SocketState overlapped) @trusted
|
||||
int endSend(SocketState overlapped) @nogc @trusted
|
||||
out (count)
|
||||
{
|
||||
assert(count >= 0);
|
||||
@ -354,7 +370,7 @@ else version (Windows)
|
||||
if (result == FALSE && !wouldHaveBlocked)
|
||||
{
|
||||
disconnected_ = true;
|
||||
throw theAllocator.make!SocketException("Unable to receive");
|
||||
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||
}
|
||||
return lpNumber;
|
||||
}
|
||||
@ -373,7 +389,7 @@ else version (Windows)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) on errors.
|
||||
*/
|
||||
this(AddressFamily af) @trusted
|
||||
this(AddressFamily af) @nogc @trusted
|
||||
{
|
||||
super(af);
|
||||
scope (failure)
|
||||
@ -396,7 +412,8 @@ else version (Windows)
|
||||
NULL);
|
||||
if (!result == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to retrieve an accept extension function pointer");
|
||||
throw make!SocketException(defaultAllocator,
|
||||
"Unable to retrieve an accept extension function pointer");
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,12 +428,12 @@ else version (Windows)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) on accept errors.
|
||||
*/
|
||||
bool beginAccept(SocketState overlapped) @trusted
|
||||
bool beginAccept(SocketState overlapped) @nogc @trusted
|
||||
{
|
||||
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
|
||||
if (socket == socket_t.init)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to create socket");
|
||||
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||
}
|
||||
scope (failure)
|
||||
{
|
||||
@ -425,8 +442,10 @@ else version (Windows)
|
||||
DWORD dwBytes;
|
||||
overlapped.handle = cast(HANDLE) socket;
|
||||
overlapped.event = OverlappedSocketEvent.accept;
|
||||
overlapped.buffer.len = (sockaddr_in.sizeof + 16) * 2;
|
||||
overlapped.buffer.buf = theAllocator.makeArray!char(overlapped.buffer.len).ptr;
|
||||
|
||||
immutable len = (sockaddr_in.sizeof + 16) * 2;
|
||||
overlapped.buffer.len = len;
|
||||
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
|
||||
|
||||
// We don't want to get any data now, but only start to accept the connections
|
||||
BOOL result = acceptExtension(handle_,
|
||||
@ -439,7 +458,7 @@ else version (Windows)
|
||||
&overlapped.overlapped);
|
||||
if (result == FALSE && !wouldHaveBlocked)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to accept socket connection");
|
||||
throw defaultAllocator.make!SocketException("Unable to accept socket connection");
|
||||
}
|
||||
return result == TRUE;
|
||||
}
|
||||
@ -455,17 +474,18 @@ else version (Windows)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
||||
*/
|
||||
OverlappedConnectedSocket endAccept(SocketState overlapped) @trusted
|
||||
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
|
||||
{
|
||||
scope (exit)
|
||||
{
|
||||
theAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);
|
||||
defaultAllocator.dispose(overlapped.buffer.buf[0..overlapped.buffer.len]);
|
||||
}
|
||||
auto socket = theAllocator.make!OverlappedConnectedSocket(cast(socket_t) overlapped.handle,
|
||||
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
|
||||
cast(socket_t) overlapped.handle,
|
||||
addressFamily);
|
||||
scope (failure)
|
||||
{
|
||||
theAllocator.dispose(socket);
|
||||
defaultAllocator.dispose(socket);
|
||||
}
|
||||
socket.setOption(SocketOptionLevel.SOCKET,
|
||||
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
||||
@ -663,7 +683,7 @@ abstract class Socket
|
||||
/// Address family.
|
||||
protected AddressFamily family;
|
||||
|
||||
private @property void handle(socket_t handle)
|
||||
private @property void handle(socket_t handle) @nogc
|
||||
in
|
||||
{
|
||||
assert(handle != socket_t.init);
|
||||
@ -693,7 +713,7 @@ abstract class Socket
|
||||
* handle = Socket.
|
||||
* af = Address family.
|
||||
*/
|
||||
this(socket_t handle, AddressFamily af)
|
||||
this(socket_t handle, AddressFamily af) @nogc
|
||||
in
|
||||
{
|
||||
assert(handle != socket_t.init);
|
||||
@ -728,7 +748,9 @@ abstract class Socket
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) on error.
|
||||
*/
|
||||
protected int getOption(SocketOptionLevel level, SocketOption option, void[] result) const @trusted
|
||||
protected int getOption(SocketOptionLevel level,
|
||||
SocketOption option,
|
||||
void[] result) const @trusted @nogc
|
||||
{
|
||||
auto length = cast(socklen_t) result.length;
|
||||
if (getsockopt(handle_,
|
||||
@ -737,25 +759,31 @@ abstract class Socket
|
||||
result.ptr,
|
||||
&length) == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to get socket option");
|
||||
throw defaultAllocator.make!SocketException("Unable to get socket option");
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int getOption(SocketOptionLevel level, SocketOption option, out size_t result) const @trusted
|
||||
int getOption(SocketOptionLevel level,
|
||||
SocketOption option,
|
||||
out size_t result) const @trusted @nogc
|
||||
{
|
||||
return getOption(level, option, (&result)[0..1]);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int getOption(SocketOptionLevel level, SocketOption option, out Linger result) const @trusted
|
||||
int getOption(SocketOptionLevel level,
|
||||
SocketOption option,
|
||||
out Linger result) const @trusted @nogc
|
||||
{
|
||||
return getOption(level, option, (&result.clinger)[0..1]);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
int getOption(SocketOptionLevel level, SocketOption option, out Duration result) const @trusted
|
||||
int getOption(SocketOptionLevel level,
|
||||
SocketOption option,
|
||||
out Duration result) const @trusted @nogc
|
||||
{
|
||||
// WinSock returns the timeout values as a milliseconds DWORD,
|
||||
// while Linux and BSD return a timeval struct.
|
||||
@ -784,12 +812,13 @@ abstract class Socket
|
||||
* Params:
|
||||
* level = Protocol level at that the option exists.
|
||||
* option = Option.
|
||||
* result = Option value.
|
||||
* value = Option value.
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) on error.
|
||||
*/
|
||||
protected void setOption(SocketOptionLevel level, SocketOption option, void[] value)
|
||||
const @trusted
|
||||
protected void setOption(SocketOptionLevel level,
|
||||
SocketOption option,
|
||||
void[] value) const @trusted @nogc
|
||||
{
|
||||
if (setsockopt(handle_,
|
||||
cast(int)level,
|
||||
@ -797,24 +826,27 @@ abstract class Socket
|
||||
value.ptr,
|
||||
cast(uint) value.length) == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to set socket option");
|
||||
throw defaultAllocator.make!SocketException("Unable to set socket option");
|
||||
}
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void setOption(SocketOptionLevel level, SocketOption option, size_t value) const @trusted
|
||||
void setOption(SocketOptionLevel level, SocketOption option, size_t value)
|
||||
const @trusted @nogc
|
||||
{
|
||||
setOption(level, option, (&value)[0..1]);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void setOption(SocketOptionLevel level, SocketOption option, Linger value) const @trusted
|
||||
void setOption(SocketOptionLevel level, SocketOption option, Linger value)
|
||||
const @trusted @nogc
|
||||
{
|
||||
setOption(level, option, (&value.clinger)[0..1]);
|
||||
}
|
||||
|
||||
/// Ditto.
|
||||
void setOption(SocketOptionLevel level, SocketOption option, Duration value) const @trusted
|
||||
void setOption(SocketOptionLevel level, SocketOption option, Duration value)
|
||||
const @trusted @nogc
|
||||
{
|
||||
version (Posix)
|
||||
{
|
||||
@ -852,7 +884,7 @@ abstract class Socket
|
||||
* Params:
|
||||
* yes = Socket's blocking flag.
|
||||
*/
|
||||
@property void blocking(bool yes)
|
||||
@property void blocking(bool yes) @nogc
|
||||
{
|
||||
version (Posix)
|
||||
{
|
||||
@ -865,7 +897,8 @@ abstract class Socket
|
||||
}
|
||||
if (fl == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to set socket blocking");
|
||||
throw make!SocketException(defaultAllocator,
|
||||
"Unable to set socket blocking");
|
||||
}
|
||||
}
|
||||
else version (Windows)
|
||||
@ -873,7 +906,8 @@ abstract class Socket
|
||||
uint num = !yes;
|
||||
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to set socket blocking");
|
||||
throw make!SocketException(defaultAllocator,
|
||||
"Unable to set socket blocking");
|
||||
}
|
||||
blocking_ = yes;
|
||||
}
|
||||
@ -938,11 +972,11 @@ abstract class Socket
|
||||
* backlog = Request of how many pending incoming connections are
|
||||
* queued until $(D_PSYMBOL accept)ed.
|
||||
*/
|
||||
void listen(int backlog) const @trusted
|
||||
void listen(int backlog) const @trusted @nogc
|
||||
{
|
||||
if (.listen(handle_, backlog) == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to listen on socket");
|
||||
throw defaultAllocator.make!SocketException("Unable to listen on socket");
|
||||
}
|
||||
}
|
||||
|
||||
@ -987,12 +1021,12 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
* Params:
|
||||
* af = Address family.
|
||||
*/
|
||||
this(AddressFamily af) @trusted
|
||||
this(AddressFamily af) @trusted @nogc
|
||||
{
|
||||
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
|
||||
if (handle == socket_t.init)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to create socket");
|
||||
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||
}
|
||||
super(handle, af);
|
||||
}
|
||||
@ -1005,11 +1039,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to bind.
|
||||
*/
|
||||
void bind(Address address) @trusted const
|
||||
void bind(Address address) const @trusted @nogc
|
||||
{
|
||||
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
|
||||
{
|
||||
throw theAllocator.make!SocketException("Unable to bind socket");
|
||||
throw defaultAllocator.make!SocketException("Unable to bind socket");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1024,7 +1058,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
||||
*/
|
||||
ConnectedSocket accept() @trusted
|
||||
ConnectedSocket accept() @trusted @nogc
|
||||
{
|
||||
socket_t sock;
|
||||
|
||||
@ -1048,10 +1082,11 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
{
|
||||
return null;
|
||||
}
|
||||
throw theAllocator.make!SocketException("Unable to accept socket connection");
|
||||
throw make!SocketException(defaultAllocator,
|
||||
"Unable to accept socket connection");
|
||||
}
|
||||
|
||||
auto newSocket = theAllocator.make!ConnectedSocket(sock, addressFamily);
|
||||
auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
|
||||
|
||||
version (linux)
|
||||
{ // Blocking mode already set
|
||||
@ -1066,7 +1101,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
theAllocator.dispose(newSocket);
|
||||
defaultAllocator.dispose(newSocket);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
@ -1106,7 +1141,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
* handle = Socket.
|
||||
* af = Address family.
|
||||
*/
|
||||
this(socket_t handle, AddressFamily af)
|
||||
this(socket_t handle, AddressFamily af) @nogc
|
||||
{
|
||||
super(handle, af);
|
||||
}
|
||||
@ -1142,7 +1177,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||
*/
|
||||
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted
|
||||
ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
|
||||
{
|
||||
ptrdiff_t ret;
|
||||
if (!buf.length)
|
||||
@ -1162,7 +1197,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
return 0;
|
||||
}
|
||||
disconnected_ = true;
|
||||
throw theAllocator.make!SocketException("Unable to receive");
|
||||
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@ -1180,7 +1215,8 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
*
|
||||
* Throws: $(D_PSYMBOL SocketException) if unable to send.
|
||||
*/
|
||||
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none) const @trusted
|
||||
ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
|
||||
const @trusted @nogc
|
||||
{
|
||||
int sendFlags = cast(int) flags;
|
||||
ptrdiff_t sent;
|
||||
@ -1199,7 +1235,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
throw theAllocator.make!SocketException("Unable to send");
|
||||
throw defaultAllocator.make!SocketException("Unable to send");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1238,22 +1274,23 @@ class InternetAddress : Address
|
||||
anyPort = 0,
|
||||
}
|
||||
|
||||
this(in string host, ushort port = anyPort)
|
||||
this(in string host, ushort port = anyPort) @nogc
|
||||
{
|
||||
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
|
||||
{
|
||||
throw theAllocator.make!AddressException("Address info lookup is not available on this system");
|
||||
throw make!SocketException(defaultAllocator,
|
||||
"Address info lookup is not available on this system");
|
||||
}
|
||||
addrinfo* ai_res;
|
||||
port_ = port;
|
||||
|
||||
// Make C-string from host.
|
||||
char[] node = theAllocator.makeArray!char(host.length + 1);
|
||||
auto node = cast(char[]) allocator.allocate(host.length + 1);
|
||||
node[0.. $ - 1] = host;
|
||||
node[$ - 1] = '\0';
|
||||
scope (exit)
|
||||
{
|
||||
theAllocator.dispose(node);
|
||||
allocator.deallocate(node);
|
||||
}
|
||||
|
||||
// Convert port to a C-string.
|
||||
@ -1278,7 +1315,7 @@ class InternetAddress : Address
|
||||
auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
|
||||
if (ret)
|
||||
{
|
||||
throw theAllocator.make!AddressException("Address info lookup failed");
|
||||
throw defaultAllocator.make!SocketException("Address info lookup failed");
|
||||
}
|
||||
scope (exit)
|
||||
{
|
||||
@ -1292,7 +1329,7 @@ class InternetAddress : Address
|
||||
}
|
||||
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
|
||||
{
|
||||
throw theAllocator.make!AddressException("Wrong address family");
|
||||
throw defaultAllocator.make!SocketException("Wrong address family");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1352,7 +1389,8 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
|
||||
}
|
||||
else version (Windows)
|
||||
{
|
||||
return WSAGetLastError() == ERROR_IO_PENDING || WSAGetLastError() == EWOULDBLOCK
|
||||
return WSAGetLastError() == ERROR_IO_PENDING
|
||||
|| WSAGetLastError() == EWOULDBLOCK
|
||||
|| WSAGetLastError() == ERROR_IO_INCOMPLETE;
|
||||
}
|
||||
}
|
||||
|
@ -630,32 +630,31 @@ static this()
|
||||
/**
|
||||
* A Unique Resource Locator.
|
||||
*/
|
||||
struct URL(U = string)
|
||||
if (isSomeString!U)
|
||||
struct URL
|
||||
{
|
||||
/** The URL scheme. */
|
||||
U scheme;
|
||||
const(char)[] scheme;
|
||||
|
||||
/** The username. */
|
||||
U user;
|
||||
const(char)[] user;
|
||||
|
||||
/** The password. */
|
||||
U pass;
|
||||
const(char)[] pass;
|
||||
|
||||
/** The hostname. */
|
||||
U host;
|
||||
const(char)[] host;
|
||||
|
||||
/** The port number. */
|
||||
ushort port;
|
||||
|
||||
/** The path. */
|
||||
U path;
|
||||
const(char)[] path;
|
||||
|
||||
/** The query string. */
|
||||
U query;
|
||||
const(char)[] query;
|
||||
|
||||
/** The anchor. */
|
||||
U fragment;
|
||||
const(char)[] fragment;
|
||||
|
||||
/**
|
||||
* Attempts to parse an URL from a string.
|
||||
@ -666,7 +665,7 @@ struct URL(U = string)
|
||||
*
|
||||
* Throws: $(D_PSYMBOL URIException) if the URL is malformed.
|
||||
*/
|
||||
this(U source)
|
||||
this(in char[] source)
|
||||
{
|
||||
auto value = source;
|
||||
ptrdiff_t pos = -1, endPos = value.length, start;
|
||||
@ -702,7 +701,7 @@ struct URL(U = string)
|
||||
{
|
||||
if (!parsePort(value[pos..$]))
|
||||
{
|
||||
throw theAllocator.make!URIException("Failed to parse port");
|
||||
throw defaultAllocator.make!URIException("Failed to parse port");
|
||||
}
|
||||
}
|
||||
goto ParsePath;
|
||||
@ -751,7 +750,7 @@ struct URL(U = string)
|
||||
else if (pos == 0 && parsePort(value[pos..$]))
|
||||
{
|
||||
// An URL shouldn't begin with a port number
|
||||
throw theAllocator.make!URIException("URL begins with port");
|
||||
throw defaultAllocator.make!URIException("URL begins with port");
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -808,7 +807,7 @@ struct URL(U = string)
|
||||
{
|
||||
pass = null;
|
||||
}
|
||||
throw make!URIException(theAllocator,
|
||||
throw make!URIException(defaultAllocator,
|
||||
"Restricted characters in user information");
|
||||
}
|
||||
}
|
||||
@ -844,7 +843,7 @@ struct URL(U = string)
|
||||
{
|
||||
pass = null;
|
||||
}
|
||||
throw theAllocator.make!URIException("Invalid port");
|
||||
throw defaultAllocator.make!URIException("Invalid port");
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -866,7 +865,7 @@ struct URL(U = string)
|
||||
{
|
||||
pass = null;
|
||||
}
|
||||
throw theAllocator.make!URIException("Invalid host");
|
||||
throw defaultAllocator.make!URIException("Invalid host");
|
||||
}
|
||||
|
||||
host = value[start..pos];
|
||||
@ -912,8 +911,8 @@ struct URL(U = string)
|
||||
}
|
||||
}
|
||||
|
||||
~this()
|
||||
{
|
||||
~this()
|
||||
{
|
||||
if (scheme !is null)
|
||||
{
|
||||
scheme = null;
|
||||
@ -942,7 +941,7 @@ struct URL(U = string)
|
||||
{
|
||||
fragment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse and set the port.
|
||||
@ -954,7 +953,7 @@ struct URL(U = string)
|
||||
*
|
||||
* Returns: Whether the port could be found.
|
||||
*/
|
||||
private bool parsePort(U port) pure nothrow @safe @nogc
|
||||
private bool parsePort(in char[] port) pure nothrow @safe @nogc
|
||||
{
|
||||
ptrdiff_t i = 1;
|
||||
float lPort = 0;
|
||||
@ -984,14 +983,14 @@ struct URL(U = string)
|
||||
///
|
||||
unittest
|
||||
{
|
||||
auto u = URL!()("example.org");
|
||||
auto u = URL("example.org");
|
||||
assert(u.path == "example.org");
|
||||
|
||||
u = URL!()("relative/path");
|
||||
u = URL("relative/path");
|
||||
assert(u.path == "relative/path");
|
||||
|
||||
// Host and scheme
|
||||
u = URL!()("https://example.org");
|
||||
u = URL("https://example.org");
|
||||
assert(u.scheme == "https");
|
||||
assert(u.host == "example.org");
|
||||
assert(u.path is null);
|
||||
@ -999,7 +998,7 @@ unittest
|
||||
assert(u.fragment is null);
|
||||
|
||||
// With user and port and path
|
||||
u = URL!()("https://hilary:putnam@example.org:443/foo/bar");
|
||||
u = URL("https://hilary:putnam@example.org:443/foo/bar");
|
||||
assert(u.scheme == "https");
|
||||
assert(u.host == "example.org");
|
||||
assert(u.path == "/foo/bar");
|
||||
@ -1009,7 +1008,7 @@ unittest
|
||||
assert(u.fragment is null);
|
||||
|
||||
// With query string
|
||||
u = URL!()("https://example.org/?login=true");
|
||||
u = URL("https://example.org/?login=true");
|
||||
assert(u.scheme == "https");
|
||||
assert(u.host == "example.org");
|
||||
assert(u.path == "/");
|
||||
@ -1017,14 +1016,14 @@ unittest
|
||||
assert(u.fragment is null);
|
||||
|
||||
// With query string and fragment
|
||||
u = URL!()("https://example.org/?login=false#label");
|
||||
u = URL("https://example.org/?login=false#label");
|
||||
assert(u.scheme == "https");
|
||||
assert(u.host == "example.org");
|
||||
assert(u.path == "/");
|
||||
assert(u.query == "login=false");
|
||||
assert(u.fragment == "label");
|
||||
|
||||
u = URL!()("redis://root:password@localhost:2201/path?query=value#fragment");
|
||||
u = URL("redis://root:password@localhost:2201/path?query=value#fragment");
|
||||
assert(u.scheme == "redis");
|
||||
assert(u.user == "root");
|
||||
assert(u.pass == "password");
|
||||
@ -1043,7 +1042,7 @@ private unittest
|
||||
{
|
||||
try
|
||||
{
|
||||
URL!()(t[0]);
|
||||
URL(t[0]);
|
||||
assert(0);
|
||||
}
|
||||
catch (URIException e)
|
||||
@ -1053,7 +1052,7 @@ private unittest
|
||||
}
|
||||
else
|
||||
{
|
||||
auto u = URL!()(t[0]);
|
||||
auto u = URL(t[0]);
|
||||
assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null,
|
||||
t[0]);
|
||||
assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]);
|
||||
@ -1100,31 +1099,30 @@ enum Component : string
|
||||
*
|
||||
* Returns: Requested URL components.
|
||||
*/
|
||||
URL parseURL(U)(in U source)
|
||||
if (isSomeString!U)
|
||||
URL parseURL(typeof(null) T)(in char[] source)
|
||||
{
|
||||
return URL!U(source);
|
||||
return URL(source);
|
||||
}
|
||||
|
||||
/** ditto */
|
||||
string parseURL(string T, U)(in U source)
|
||||
if ((T == "scheme"
|
||||
/// Ditto.
|
||||
const(char)[] parseURL(immutable(char)[] T)(in char[] source)
|
||||
if (T == "scheme"
|
||||
|| T =="host"
|
||||
|| T == "user"
|
||||
|| T == "pass"
|
||||
|| T == "path"
|
||||
|| T == "query"
|
||||
|| T == "fragment") && isSomeString!U)
|
||||
|| T == "fragment")
|
||||
{
|
||||
auto ret = URL!U(source);
|
||||
auto ret = URL(source);
|
||||
return mixin("ret." ~ T);
|
||||
}
|
||||
|
||||
/** ditto */
|
||||
ushort parseURL(string T, U)(in U source)
|
||||
if (T == "port" && isSomeString!U)
|
||||
/// Ditto.
|
||||
ushort parseURL(immutable(char)[] T)(in char[] source)
|
||||
if (T == "port")
|
||||
{
|
||||
auto ret = URL!U(source);
|
||||
auto ret = URL(source);
|
||||
return ret.port;
|
||||
}
|
||||
|
||||
@ -1158,7 +1156,7 @@ private unittest
|
||||
else
|
||||
{
|
||||
ushort port = parseURL!(Component.port)(t[0]);
|
||||
string component = parseURL!(Component.scheme)(t[0]);
|
||||
auto component = parseURL!(Component.scheme)(t[0]);
|
||||
assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null,
|
||||
t[0]);
|
||||
component = parseURL!(Component.user)(t[0]);
|
||||
|
Reference in New Issue
Block a user