Compare commits
76 Commits
Author | SHA1 | Date | |
---|---|---|---|
278e851414 | |||
6f549df243 | |||
4633bcc680 | |||
dc39efd316 | |||
260937e4fb | |||
e17fff2881 | |||
bc32511254 | |||
f30972f948 | |||
ea33ca62c8 | |||
0f365758e1 | |||
2815b53a88 | |||
6c0588164a | |||
8ee1d647ce | |||
25791775e6 | |||
f013e2f1f4 | |||
ac3935d71b | |||
b1c217e272 | |||
d007aaa310 | |||
8aaf9e14be | |||
ae3e6b46f6 | |||
8687df1fbb | |||
ba0aff6737 | |||
a648e2120a | |||
bc61809050 | |||
8c42cbfd63 | |||
58664570f9 | |||
decb82f437 | |||
357c7e279d | |||
32e19c8b58 | |||
f5c6c5b483 | |||
ba2d086fb8 | |||
7a0241b484 | |||
36dad80e18 | |||
29d883150e | |||
e2bed0cfcb | |||
38afeac071 | |||
001c7c3e33 | |||
d4ab339feb | |||
8477312769 | |||
67f90e137d | |||
f264fd5597 | |||
9e75620f1b | |||
45825946c0 | |||
8afb552d59 | |||
e4091669f8 | |||
1cb9349226 | |||
06620dc5df | |||
708d95db49 | |||
85d9361bfb | |||
a6a6f496eb | |||
db12f03264 | |||
231aedb8ad | |||
c3b63ee40d | |||
6f405c5e08 | |||
16cf8478cf | |||
8915a0c7a7 | |||
e5c7edb72c | |||
64e0d666ed | |||
f2aac680c5 | |||
d629525a4b | |||
33d321f0d7 | |||
3d64d59ba9 | |||
4635835a99 | |||
8725ec5f20 | |||
9a4c8cea06 | |||
eb360bda38 | |||
4b1cd2cbfd | |||
cd944a61b7 | |||
47ef787353 | |||
6436ad49df | |||
e1964e47a5 | |||
43319e4e3a | |||
33dbf042c2 | |||
885fca9b5e | |||
074d027629 | |||
f4b90d8b51 |
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
@ -7,7 +7,7 @@ os:
|
|||||||
language: d
|
language: d
|
||||||
|
|
||||||
d:
|
d:
|
||||||
- dmd-2.074.0
|
- dmd-2.074.1
|
||||||
- dmd-2.073.2
|
- dmd-2.073.2
|
||||||
- dmd-2.072.2
|
- dmd-2.072.2
|
||||||
- dmd-2.071.2
|
- dmd-2.071.2
|
||||||
@ -17,4 +17,7 @@ env:
|
|||||||
- ARCH=x86_64
|
- ARCH=x86_64
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- dub test --arch=$ARCH
|
- dub test -b unittest-cov --arch=$ARCH
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
|
51
README.md
51
README.md
@ -1,6 +1,8 @@
|
|||||||
# Tanya
|
# Tanya
|
||||||
|
|
||||||
[](https://travis-ci.org/caraus-ecms/tanya)
|
[](https://travis-ci.org/caraus-ecms/tanya)
|
||||||
|
[](https://ci.appveyor.com/project/belka-ew/tanya/branch/master)
|
||||||
|
[](https://codecov.io/gh/caraus-ecms/tanya)
|
||||||
[](https://code.dlang.org/packages/tanya)
|
[](https://code.dlang.org/packages/tanya)
|
||||||
[](https://code.dlang.org/packages/tanya)
|
[](https://code.dlang.org/packages/tanya)
|
||||||
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
[](https://raw.githubusercontent.com/caraus-ecms/tanya/master/LICENSE)
|
||||||
@ -13,7 +15,7 @@ Garbage Collector heap. Everything in the library is usable in @nogc code.
|
|||||||
Tanya extends Phobos functionality and provides alternative implementations for
|
Tanya extends Phobos functionality and provides alternative implementations for
|
||||||
data structures and utilities that depend on the Garbage Collector in Phobos.
|
data structures and utilities that depend on the Garbage Collector in Phobos.
|
||||||
|
|
||||||
* [Bug tracker](https://issues.caraus.io/projects/tanya)
|
* [Bug tracker](https://issues.caraus.io/projects/tanya/issues)
|
||||||
* [Documentation](https://docs.caraus.io/tanya)
|
* [Documentation](https://docs.caraus.io/tanya)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
@ -21,51 +23,46 @@ data structures and utilities that depend on the Garbage Collector in Phobos.
|
|||||||
Tanya consists of the following packages:
|
Tanya consists of the following packages:
|
||||||
|
|
||||||
* `async`: Event loop (epoll, kqueue and IOCP).
|
* `async`: Event loop (epoll, kqueue and IOCP).
|
||||||
* `container`: Queue, Vector, Singly linked list, buffers.
|
* `container`: Queue, Array, Singly and doubly linked lists, Buffers, UTF-8
|
||||||
|
string, Hash set.
|
||||||
* `math`: Arbitrary precision integer and a set of functions.
|
* `math`: Arbitrary precision integer and a set of functions.
|
||||||
* `memory`: Tools for manual memory management (allocator, reference counting,
|
* `memory`: Tools for manual memory management (allocators, smart pointers).
|
||||||
helper functions).
|
* `network`: URL-Parsing, sockets, utilities.
|
||||||
* `network`: URL-Parsing, sockets.
|
|
||||||
|
|
||||||
### Supported compilers
|
### Supported compilers
|
||||||
|
|
||||||
| dmd |
|
| dmd |
|
||||||
|:-------:|
|
|:-------:|
|
||||||
| 2.074.0 |
|
| 2.074.1 |
|
||||||
| 2.073.2 |
|
| 2.073.2 |
|
||||||
| 2.072.2 |
|
| 2.072.2 |
|
||||||
| 2.071.2 |
|
| 2.071.2 |
|
||||||
|
|
||||||
### Current status
|
### Current status
|
||||||
|
|
||||||
The library is currently under development, but the API is becoming gradually
|
Following modules are under development:
|
||||||
stable.
|
|
||||||
|
|
||||||
Following modules are coming soon:
|
| Feature | Branch | Build status |
|
||||||
|
|----------|:---------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| Feature | Build status |
|
| BitArray | bitvector | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/bitvector) |
|
||||||
|--------------|:-----------------------------------------------------------------------------------------------------------------------:|
|
| TLS | crypto | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/crypto) |
|
||||||
| UTF-8 string | [](https://travis-ci.org/caraus-ecms/tanya) |
|
| File IO | io | [](https://travis-ci.org/caraus-ecms/tanya) [](https://ci.appveyor.com/project/belka-ew/tanya/branch/io) |
|
||||||
| BitVector | [](https://travis-ci.org/caraus-ecms/tanya) |
|
|
||||||
| Hash table | N/A |
|
|
||||||
|
|
||||||
`math` package contains an arbitrary precision integer implementation that
|
|
||||||
needs more test cases, better performance and some additional features
|
|
||||||
(constructing from a string and an ubyte array, and converting it back).
|
|
||||||
|
|
||||||
### Further characteristics
|
### Further characteristics
|
||||||
|
|
||||||
* Tanya is a native D library.
|
* Tanya is a native D library without any external dependencies.
|
||||||
|
|
||||||
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
|
* Tanya is cross-platform. The development happens on a 64-bit Linux, but it
|
||||||
is being tested on Windows and FreeBSD as well.
|
is being tested on Windows and FreeBSD as well.
|
||||||
|
|
||||||
* The library isn't thread-safe. Thread-safity should be added later.
|
* The library isn't thread-safe yet.
|
||||||
|
|
||||||
|
## Release management
|
||||||
|
|
||||||
|
3-week release cycle.
|
||||||
|
|
||||||
|
Deprecated features are removed after one release (in approximately 6 weeks after deprecating).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Since I'm mostly busy writing new code and implementing new features I would
|
Feel free to contact me if you have any questions: info@caraus.de.
|
||||||
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.
|
|
||||||
|
52
appveyor.yml
Normal file
52
appveyor.yml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
platform: x64
|
||||||
|
os: Visual Studio 2017
|
||||||
|
|
||||||
|
environment:
|
||||||
|
matrix:
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.074.1
|
||||||
|
arch: x86
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.073.2
|
||||||
|
arch: x86
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.072.2
|
||||||
|
arch: x86
|
||||||
|
- DC: dmd
|
||||||
|
DVersion: 2.071.2
|
||||||
|
arch: x86
|
||||||
|
|
||||||
|
skip_tags: true
|
||||||
|
|
||||||
|
install:
|
||||||
|
- ps: function SetUpDCompiler
|
||||||
|
{
|
||||||
|
$env:toolchain = "msvc";
|
||||||
|
$version = $env:DVersion;
|
||||||
|
Invoke-WebRequest "http://downloads.dlang.org/releases/2.x/$($version)/dmd.$($version).windows.7z" -OutFile "c:\dmd.7z";
|
||||||
|
echo "finished.";
|
||||||
|
pushd c:\\;
|
||||||
|
7z x dmd.7z > $null;
|
||||||
|
popd;
|
||||||
|
}
|
||||||
|
- ps: SetUpDCompiler
|
||||||
|
|
||||||
|
- ps: if($env:DVersion -eq "2.071.2"){
|
||||||
|
Invoke-WebRequest "http://code.dlang.org/files/dub-1.2.1-windows-x86.zip" -OutFile "dub.zip";
|
||||||
|
7z x dub.zip -odub > $null;
|
||||||
|
Move-Item "dub/dub.exe" "C:\dmd2\windows\bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
before_build:
|
||||||
|
- ps: $env:PATH += ";C:\dmd2\windows\bin;";
|
||||||
|
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat" -arch=%arch%
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- echo dummy build script - dont remove me
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- echo %DC%
|
||||||
|
- echo %PATH%
|
||||||
|
- 'dub --version'
|
||||||
|
- '%DC% --version'
|
||||||
|
- dub test --arch=x86 --compiler=%DC%
|
2
dub.json
2
dub.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "tanya",
|
"name": "tanya",
|
||||||
"description": "General purpose, @nogc library",
|
"description": "General purpose, @nogc library. Containers, networking, memory management, utilities",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
"copyright": "(c) Eugene Wissner <info@caraus.de>",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
@ -18,7 +18,7 @@ import tanya.async.event.selector;
|
|||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -37,7 +37,7 @@ extern (C) nothrow @nogc
|
|||||||
final class EpollLoop : SelectorLoop
|
final class EpollLoop : SelectorLoop
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private Vector!epoll_event events;
|
private Array!epoll_event events;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the loop.
|
* Initializes the loop.
|
||||||
@ -49,7 +49,7 @@ final class EpollLoop : SelectorLoop
|
|||||||
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
throw defaultAllocator.make!BadLoopException("epoll initialization failed");
|
||||||
}
|
}
|
||||||
super();
|
super();
|
||||||
events = Vector!epoll_event(maxEvents, MmapPool.instance);
|
events = Array!epoll_event(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,7 +50,7 @@ import tanya.async.event.selector;
|
|||||||
import tanya.async.loop;
|
import tanya.async.loop;
|
||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -116,8 +116,8 @@ extern(C) int kevent(int kq, const kevent_t *changelist, int nchanges,
|
|||||||
final class KqueueLoop : SelectorLoop
|
final class KqueueLoop : SelectorLoop
|
||||||
{
|
{
|
||||||
protected int fd;
|
protected int fd;
|
||||||
private Vector!kevent_t events;
|
private Array!kevent_t events;
|
||||||
private Vector!kevent_t changes;
|
private Array!kevent_t changes;
|
||||||
private size_t changeCount;
|
private size_t changeCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -139,8 +139,8 @@ final class KqueueLoop : SelectorLoop
|
|||||||
throw make!BadLoopException(defaultAllocator,
|
throw make!BadLoopException(defaultAllocator,
|
||||||
"kqueue initialization failed");
|
"kqueue initialization failed");
|
||||||
}
|
}
|
||||||
events = Vector!kevent_t(64, MmapPool.instance);
|
events = Array!kevent_t(64, MmapPool.instance);
|
||||||
changes = Vector!kevent_t(64, MmapPool.instance);
|
changes = Array!kevent_t(64, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,7 +151,7 @@ final class KqueueLoop : SelectorLoop
|
|||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set(socket_t socket, short filter, ushort flags) @nogc
|
private void set(SocketType socket, short filter, ushort flags) @nogc
|
||||||
{
|
{
|
||||||
if (changes.length <= changeCount)
|
if (changes.length <= changeCount)
|
||||||
{
|
{
|
||||||
|
@ -17,7 +17,7 @@ import tanya.async.protocol;
|
|||||||
import tanya.async.transport;
|
import tanya.async.transport;
|
||||||
import tanya.async.watcher;
|
import tanya.async.watcher;
|
||||||
import tanya.container.buffer;
|
import tanya.container.buffer;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
import tanya.memory.mmappool;
|
import tanya.memory.mmappool;
|
||||||
import tanya.network.socket;
|
import tanya.network.socket;
|
||||||
@ -78,7 +78,8 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
|||||||
return cast(ConnectedSocket) socket_;
|
return cast(ConnectedSocket) socket_;
|
||||||
}
|
}
|
||||||
|
|
||||||
private @property void socket(ConnectedSocket socket) pure nothrow @safe @nogc
|
private @property void socket(ConnectedSocket socket)
|
||||||
|
pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(socket !is null);
|
assert(socket !is null);
|
||||||
@ -133,7 +134,9 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
|||||||
void close() @nogc
|
void close() @nogc
|
||||||
{
|
{
|
||||||
closing = true;
|
closing = true;
|
||||||
loop.reify(this, EventMask(Event.read, Event.write), EventMask(Event.write));
|
loop.reify(this,
|
||||||
|
EventMask(Event.read, Event.write),
|
||||||
|
EventMask(Event.write));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -205,20 +208,20 @@ package class StreamTransport : SocketWatcher, DuplexTransport, SocketTransport
|
|||||||
abstract class SelectorLoop : Loop
|
abstract class SelectorLoop : Loop
|
||||||
{
|
{
|
||||||
/// Pending connections.
|
/// Pending connections.
|
||||||
protected Vector!SocketWatcher connections;
|
protected Array!SocketWatcher connections;
|
||||||
|
|
||||||
this() @nogc
|
this() @nogc
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
connections = Vector!SocketWatcher(maxEvents, MmapPool.instance);
|
connections = Array!SocketWatcher(maxEvents, MmapPool.instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
~this() @nogc
|
~this() @nogc
|
||||||
{
|
{
|
||||||
foreach (ref connection; connections)
|
foreach (ref connection; connections)
|
||||||
{
|
{
|
||||||
// We want to free only the transports. ConnectionWatcher are created by the
|
// We want to free only the transports. ConnectionWatcher are
|
||||||
// user and should be freed by himself.
|
// created by the user and should be freed by himself.
|
||||||
if (cast(StreamTransport) connection !is null)
|
if (cast(StreamTransport) connection !is null)
|
||||||
{
|
{
|
||||||
MmapPool.instance.dispose(connection);
|
MmapPool.instance.dispose(connection);
|
||||||
|
@ -38,11 +38,11 @@
|
|||||||
*
|
*
|
||||||
* version (Windows)
|
* version (Windows)
|
||||||
* {
|
* {
|
||||||
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!OverlappedStreamSocket(AddressFamily.inet);
|
||||||
* }
|
* }
|
||||||
* else
|
* else
|
||||||
* {
|
* {
|
||||||
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.INET);
|
* auto sock = defaultAllocator.make!StreamSocket(AddressFamily.inet);
|
||||||
* sock.blocking = false;
|
* sock.blocking = false;
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
|
1652
source/tanya/container/array.d
Normal file
1652
source/tanya/container/array.d
Normal file
File diff suppressed because it is too large
Load Diff
@ -12,11 +12,100 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.container.entry;
|
module tanya.container.entry;
|
||||||
|
|
||||||
|
import std.traits;
|
||||||
|
import tanya.typecons;
|
||||||
|
|
||||||
package struct SEntry(T)
|
package struct SEntry(T)
|
||||||
{
|
{
|
||||||
/// Item content.
|
// Item content.
|
||||||
T content;
|
T content;
|
||||||
|
|
||||||
/// Next item.
|
// Next item.
|
||||||
SEntry* next;
|
SEntry* next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package struct DEntry(T)
|
||||||
|
{
|
||||||
|
// Item content.
|
||||||
|
T content;
|
||||||
|
|
||||||
|
// Previous and next item.
|
||||||
|
DEntry* next, prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
package struct HashEntry(K, V)
|
||||||
|
{
|
||||||
|
this(ref K key, ref V value)
|
||||||
|
{
|
||||||
|
this.pair = Pair!(K, V)(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair!(K, V) pair;
|
||||||
|
HashEntry* next;
|
||||||
|
}
|
||||||
|
|
||||||
|
package enum BucketStatus : byte
|
||||||
|
{
|
||||||
|
deleted = -1,
|
||||||
|
empty = 0,
|
||||||
|
used = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
package struct Bucket(T)
|
||||||
|
{
|
||||||
|
this(ref T content)
|
||||||
|
{
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void content(ref T content)
|
||||||
|
{
|
||||||
|
this.content_ = content;
|
||||||
|
this.status = BucketStatus.used;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(T) content() inout
|
||||||
|
{
|
||||||
|
return this.content_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool opEquals(ref T content)
|
||||||
|
{
|
||||||
|
if (this.status == BucketStatus.used && this.content == content)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool opEquals(ref T content) const
|
||||||
|
{
|
||||||
|
if (this.status == BucketStatus.used && this.content == content)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool opEquals(ref typeof(this) that)
|
||||||
|
{
|
||||||
|
return this.content == that.content && this.status == that.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool opEquals(ref typeof(this) that) const
|
||||||
|
{
|
||||||
|
return this.content == that.content && this.status == that.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove()
|
||||||
|
{
|
||||||
|
static if (hasElaborateDestructor!T)
|
||||||
|
{
|
||||||
|
destroy(this.content);
|
||||||
|
}
|
||||||
|
this.status = BucketStatus.deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
T content_;
|
||||||
|
BucketStatus status = BucketStatus.empty;
|
||||||
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,31 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.container;
|
module tanya.container;
|
||||||
|
|
||||||
|
public import tanya.container.array;
|
||||||
public import tanya.container.buffer;
|
public import tanya.container.buffer;
|
||||||
|
public import tanya.container.set;
|
||||||
public import tanya.container.list;
|
public import tanya.container.list;
|
||||||
public import tanya.container.vector;
|
public import tanya.container.string;
|
||||||
public import tanya.container.queue;
|
public import tanya.container.queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown if $(D_PSYMBOL Set) cannot insert a new element because the container
|
||||||
|
* is full.
|
||||||
|
*/
|
||||||
|
class HashContainerFullException : Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* msg = The message for the exception.
|
||||||
|
* file = The file where the exception occurred.
|
||||||
|
* line = The line number where the exception occurred.
|
||||||
|
* next = The previous exception in the chain of exceptions, if any.
|
||||||
|
*/
|
||||||
|
this(string msg,
|
||||||
|
string file = __FILE__,
|
||||||
|
size_t line = __LINE__,
|
||||||
|
Throwable next = null) @nogc @safe pure nothrow
|
||||||
|
{
|
||||||
|
super(msg, file, line, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
710
source/tanya/container/set.d
Normal file
710
source/tanya/container/set.d
Normal file
@ -0,0 +1,710 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module implements a $(D_PSYMBOL Set) container that stores unique
|
||||||
|
* values without any particular order.
|
||||||
|
*
|
||||||
|
* Copyright: Eugene Wissner 2017.
|
||||||
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
|
* Mozilla Public License, v. 2.0).
|
||||||
|
* Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
|
||||||
|
*/
|
||||||
|
module tanya.container.set;
|
||||||
|
|
||||||
|
import std.algorithm.mutation;
|
||||||
|
import std.traits;
|
||||||
|
import tanya.container;
|
||||||
|
import tanya.container.entry;
|
||||||
|
import tanya.memory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* E = Element type.
|
||||||
|
*/
|
||||||
|
struct Range(E)
|
||||||
|
{
|
||||||
|
static if (isMutable!E)
|
||||||
|
{
|
||||||
|
private alias DataRange = Array!(Bucket!(Unqual!E)).Range;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
private alias DataRange = Array!(Bucket!(Unqual!E)).ConstRange;
|
||||||
|
}
|
||||||
|
private DataRange dataRange;
|
||||||
|
|
||||||
|
@disable this();
|
||||||
|
|
||||||
|
private this(DataRange dataRange)
|
||||||
|
{
|
||||||
|
while (!dataRange.empty && dataRange.front.status != BucketStatus.used)
|
||||||
|
{
|
||||||
|
dataRange.popFront();
|
||||||
|
}
|
||||||
|
while (!dataRange.empty && dataRange.back.status != BucketStatus.used)
|
||||||
|
{
|
||||||
|
dataRange.popBack();
|
||||||
|
}
|
||||||
|
this.dataRange = dataRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property Range save()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property bool empty() const
|
||||||
|
{
|
||||||
|
return this.dataRange.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void popFront()
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!this.dataRange.empty);
|
||||||
|
assert(this.dataRange.front.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
{
|
||||||
|
assert(this.dataRange.empty
|
||||||
|
|| this.dataRange.back.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dataRange.popFront();
|
||||||
|
}
|
||||||
|
while (!dataRange.empty && dataRange.front.status != BucketStatus.used);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property void popBack()
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!this.dataRange.empty);
|
||||||
|
assert(this.dataRange.back.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
out
|
||||||
|
{
|
||||||
|
assert(this.dataRange.empty
|
||||||
|
|| this.dataRange.back.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dataRange.popBack();
|
||||||
|
}
|
||||||
|
while (!dataRange.empty && dataRange.back.status != BucketStatus.used);
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(E) front() inout
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!this.dataRange.empty);
|
||||||
|
assert(this.dataRange.front.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return dataRange.front.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property ref inout(E) back() inout
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(!this.dataRange.empty);
|
||||||
|
assert(this.dataRange.back.status == BucketStatus.used);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return dataRange.back.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
Range opIndex()
|
||||||
|
{
|
||||||
|
return typeof(return)(this.dataRange[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Range!(const E) opIndex() const
|
||||||
|
{
|
||||||
|
return typeof(return)(this.dataRange[]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set is a data structure that stores unique values without any particular
|
||||||
|
* order.
|
||||||
|
*
|
||||||
|
* This $(D_PSYMBOL Set) is implemented using closed hashing. Hash collisions
|
||||||
|
* are resolved with linear probing.
|
||||||
|
*
|
||||||
|
* Currently works only with integral types.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Element type.
|
||||||
|
*/
|
||||||
|
struct Set(T)
|
||||||
|
if (isIntegral!T || is(Unqual!T == bool))
|
||||||
|
{
|
||||||
|
/// The range types for $(D_PSYMBOL Set).
|
||||||
|
alias Range = .Range!T;
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
alias ConstRange = .Range!(const T);
|
||||||
|
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(this.lengthIndex < primes.length);
|
||||||
|
assert(this.data.length == 0
|
||||||
|
|| this.data.length == primes[this.lengthIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* n = Minimum number of buckets.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null).
|
||||||
|
*/
|
||||||
|
this(const size_t n, shared Allocator allocator = defaultAllocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this(allocator);
|
||||||
|
rehash(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.data = typeof(this.data)(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto set = Set!int(defaultAllocator);
|
||||||
|
assert(set.capacity == 0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto set = Set!int(8);
|
||||||
|
assert(set.capacity == 13);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this $(D_PARAM Set) from another one.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM init) is passed by reference, it will be copied.
|
||||||
|
* If $(D_PARAM init) is passed by value, it will be moved.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* S = Source set type.
|
||||||
|
* init = Source set.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*/
|
||||||
|
this(S)(ref S init, shared Allocator allocator = defaultAllocator)
|
||||||
|
if (is(Unqual!S == Set))
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.data = typeof(this.data)(init.data, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(S)(S init, shared Allocator allocator = defaultAllocator)
|
||||||
|
if (is(S == Set))
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.data = typeof(this.data)(move(init.data), allocator);
|
||||||
|
this.lengthIndex = init.lengthIndex;
|
||||||
|
init.lengthIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assigns another set.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM that) is passed by reference, it will be copied.
|
||||||
|
* If $(D_PARAM that) is passed by value, it will be moved.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* S = Content type.
|
||||||
|
* that = The value should be assigned.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD this).
|
||||||
|
*/
|
||||||
|
ref typeof(this) opAssign(S)(ref S that)
|
||||||
|
if (is(Unqual!S == Set))
|
||||||
|
{
|
||||||
|
this.data = that.data;
|
||||||
|
this.lengthIndex = that.lengthIndex;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(S)(S that) @trusted
|
||||||
|
if (is(S == Set))
|
||||||
|
{
|
||||||
|
swap(this.data, that.data);
|
||||||
|
swap(this.lengthIndex, that.lengthIndex);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Used allocator.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
@property shared(Allocator) allocator() const
|
||||||
|
out (allocator)
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
return cast(shared Allocator) this.data.allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum amount of elements this $(D_PSYMBOL Set) can hold without
|
||||||
|
* resizing and rehashing. Note that it doesn't mean that the
|
||||||
|
* $(D_PSYMBOL Set) will hold $(I exactly) $(D_PSYMBOL capacity) elements.
|
||||||
|
* $(D_PSYMBOL capacity) tells the size of the container under a best-case
|
||||||
|
* distribution of elements.
|
||||||
|
*
|
||||||
|
* Returns: $(D_PSYMBOL Set) capacity.
|
||||||
|
*/
|
||||||
|
@property size_t capacity() const
|
||||||
|
{
|
||||||
|
return this.data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
assert(set.capacity == 0);
|
||||||
|
|
||||||
|
set.insert(8);
|
||||||
|
assert(set.capacity == 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Iterates over the $(D_PSYMBOL Set) and counts the elements.
|
||||||
|
*
|
||||||
|
* Returns: Count of elements within the $(D_PSYMBOL Set).
|
||||||
|
*/
|
||||||
|
@property size_t length() const
|
||||||
|
{
|
||||||
|
size_t count;
|
||||||
|
foreach (ref e; this.data[])
|
||||||
|
{
|
||||||
|
if (e.status == BucketStatus.used)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
assert(set.length == 0);
|
||||||
|
|
||||||
|
set.insert(8);
|
||||||
|
assert(set.length == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static const size_t[41] primes = [
|
||||||
|
3, 7, 13, 23, 29, 37, 53, 71, 97, 131, 163, 193, 239, 293, 389, 521,
|
||||||
|
769, 919, 1103, 1327, 1543, 2333, 3079, 4861, 6151, 12289, 24593,
|
||||||
|
49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
|
||||||
|
12582917, 25165843, 139022417, 282312799, 573292817, 1164186217,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// The maximum number of buckets the container can have.
|
||||||
|
enum size_t maxBucketCount = primes[$ - 1];
|
||||||
|
|
||||||
|
static private size_t calculateHash(ref T value)
|
||||||
|
{
|
||||||
|
static if (isIntegral!T || isSomeChar!T || is(T == bool))
|
||||||
|
{
|
||||||
|
return (cast(size_t) value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static private size_t locateBucket(ref const DataType buckets, size_t hash)
|
||||||
|
{
|
||||||
|
return hash % buckets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum InsertStatus : byte
|
||||||
|
{
|
||||||
|
found = -1,
|
||||||
|
failed = 0,
|
||||||
|
added = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inserts the value in an empty or deleted bucket. If the value is
|
||||||
|
* already in there, does nothing and returns InsertStatus.found. If the
|
||||||
|
* hash array is full returns InsertStatus.failed.
|
||||||
|
*/
|
||||||
|
private InsertStatus insertInUnusedBucket(ref T value)
|
||||||
|
{
|
||||||
|
auto bucketPosition = locateBucket(this.data, calculateHash(value));
|
||||||
|
|
||||||
|
foreach (ref e; this.data[bucketPosition .. $])
|
||||||
|
{
|
||||||
|
if (e == value) // Already in the set.
|
||||||
|
{
|
||||||
|
return InsertStatus.found;
|
||||||
|
}
|
||||||
|
else if (e.status != BucketStatus.used) // Insert the value.
|
||||||
|
{
|
||||||
|
e.content = value;
|
||||||
|
return InsertStatus.added;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return InsertStatus.failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts a new element.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Element value.
|
||||||
|
*
|
||||||
|
* Returns: Amount of new elements inserted.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL HashContainerFullException) if the insertion failed.
|
||||||
|
*/
|
||||||
|
size_t insert(T value)
|
||||||
|
{
|
||||||
|
if (this.data.length == 0)
|
||||||
|
{
|
||||||
|
this.data = DataType(primes[0], allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
InsertStatus status = insertInUnusedBucket(value);
|
||||||
|
for (; !status; status = insertInUnusedBucket(value))
|
||||||
|
{
|
||||||
|
if ((this.primes.length - 1) == this.lengthIndex)
|
||||||
|
{
|
||||||
|
throw make!HashContainerFullException(defaultAllocator,
|
||||||
|
"Set is full");
|
||||||
|
}
|
||||||
|
rehashToSize(this.lengthIndex + 1);
|
||||||
|
}
|
||||||
|
return status == InsertStatus.added;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
assert(8 !in set);
|
||||||
|
|
||||||
|
assert(set.insert(8) == 1);
|
||||||
|
assert(set.length == 1);
|
||||||
|
assert(8 in set);
|
||||||
|
|
||||||
|
assert(set.insert(8) == 0);
|
||||||
|
assert(set.length == 1);
|
||||||
|
assert(8 in set);
|
||||||
|
|
||||||
|
assert(set.remove(8));
|
||||||
|
assert(set.insert(8) == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an element.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Element value.
|
||||||
|
*
|
||||||
|
* Returns: Number of elements removed, which is in the container with
|
||||||
|
* unique values `1` if an element existed, and `0` otherwise.
|
||||||
|
*/
|
||||||
|
size_t remove(T value)
|
||||||
|
{
|
||||||
|
if (this.data.length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bucketPosition = locateBucket(this.data, calculateHash(value));
|
||||||
|
foreach (ref e; this.data[bucketPosition .. $])
|
||||||
|
{
|
||||||
|
if (e == value) // Found.
|
||||||
|
{
|
||||||
|
e.remove();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (e.status == BucketStatus.empty)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
assert(8 !in set);
|
||||||
|
|
||||||
|
set.insert(8);
|
||||||
|
assert(8 in set);
|
||||||
|
|
||||||
|
assert(set.remove(8) == 1);
|
||||||
|
assert(set.remove(8) == 0);
|
||||||
|
assert(8 !in set);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_KEYWORD in) operator.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Element to be searched for.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the given element exists in the container,
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*/
|
||||||
|
bool opBinaryRight(string op : "in")(auto ref T value)
|
||||||
|
{
|
||||||
|
if (this.data.length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bucketPosition = locateBucket(this.data, calculateHash(value));
|
||||||
|
foreach (ref e; this.data[bucketPosition .. $])
|
||||||
|
{
|
||||||
|
if (e == value) // Found.
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (e.status == BucketStatus.empty)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
bool opBinaryRight(string op : "in")(auto ref const T value) const
|
||||||
|
{
|
||||||
|
if (this.data.length == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bucketPosition = locateBucket(this.data, calculateHash(value));
|
||||||
|
foreach (ref e; this.data[bucketPosition .. $])
|
||||||
|
{
|
||||||
|
if (e.status == BucketStatus.used && e.content == value) // Found.
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (e.status == BucketStatus.empty)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
|
||||||
|
assert(5 !in set);
|
||||||
|
set.insert(5);
|
||||||
|
assert(5 in set);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the number of buckets in the container to at least $(D_PARAM n)
|
||||||
|
* and rearranges all the elements according to their hash values.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM n) is greater than the current $(D_PSYMBOL capacity)
|
||||||
|
* and lower than or equal to $(D_PSYMBOL maxBucketCount), a rehash is
|
||||||
|
* forced.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM n) is greater than $(D_PSYMBOL maxBucketCount),
|
||||||
|
* $(D_PSYMBOL maxBucketCount) is used instead as a new number of buckets.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM n) is equal to the current $(D_PSYMBOL capacity), rehashing
|
||||||
|
* is forced without resizing the container.
|
||||||
|
*
|
||||||
|
* If $(D_PARAM n) is lower than the current $(D_PSYMBOL capacity), the
|
||||||
|
* function may have no effect.
|
||||||
|
*
|
||||||
|
* Rehashing is automatically performed whenever the container needs space
|
||||||
|
* to insert new elements.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* n = Minimum number of buckets.
|
||||||
|
*/
|
||||||
|
void rehash(const size_t n)
|
||||||
|
{
|
||||||
|
size_t lengthIndex;
|
||||||
|
for (; lengthIndex < primes.length; ++lengthIndex)
|
||||||
|
{
|
||||||
|
if (primes[lengthIndex] >= n)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rehashToSize(lengthIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes an index in the primes array.
|
||||||
|
private void rehashToSize(const size_t n)
|
||||||
|
{
|
||||||
|
auto storage = DataType(primes[n], allocator);
|
||||||
|
DataLoop: foreach (ref e1; this.data[])
|
||||||
|
{
|
||||||
|
if (e1.status == BucketStatus.used)
|
||||||
|
{
|
||||||
|
auto bucketPosition = locateBucket(storage,
|
||||||
|
calculateHash(e1.content));
|
||||||
|
|
||||||
|
foreach (ref e2; storage[bucketPosition .. $])
|
||||||
|
{
|
||||||
|
if (e2.status != BucketStatus.used) // Insert the value.
|
||||||
|
{
|
||||||
|
e2.content = e1.content;
|
||||||
|
continue DataLoop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return; // Rehashing failed.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
move(storage, this.data);
|
||||||
|
this.lengthIndex = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: A bidirectional range that iterates over the $(D_PSYMBOL Set)'s
|
||||||
|
* elements.
|
||||||
|
*/
|
||||||
|
Range opIndex()
|
||||||
|
{
|
||||||
|
return typeof(return)(this.data[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ConstRange opIndex() const
|
||||||
|
{
|
||||||
|
return typeof(return)(this.data[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
assert(set[].empty);
|
||||||
|
|
||||||
|
set.insert(8);
|
||||||
|
assert(!set[].empty);
|
||||||
|
assert(set[].front == 8);
|
||||||
|
assert(set[].back == 8);
|
||||||
|
|
||||||
|
set.remove(8);
|
||||||
|
assert(set[].empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private alias DataType = Array!(Bucket!T);
|
||||||
|
private DataType data;
|
||||||
|
private size_t lengthIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic insertion logic.
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
Set!int set;
|
||||||
|
|
||||||
|
assert(set.insert(5) == 1);
|
||||||
|
assert(set.data[0].status == BucketStatus.empty);
|
||||||
|
assert(set.data[1].status == BucketStatus.empty);
|
||||||
|
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
|
||||||
|
assert(set.data.length == 3);
|
||||||
|
|
||||||
|
assert(set.insert(5) == 0);
|
||||||
|
assert(set.data[0].status == BucketStatus.empty);
|
||||||
|
assert(set.data[1].status == BucketStatus.empty);
|
||||||
|
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
|
||||||
|
assert(set.data.length == 3);
|
||||||
|
|
||||||
|
assert(set.insert(9) == 1);
|
||||||
|
assert(set.data[0].content == 9 && set.data[0].status == BucketStatus.used);
|
||||||
|
assert(set.data[1].status == BucketStatus.empty);
|
||||||
|
assert(set.data[2].content == 5 && set.data[2].status == BucketStatus.used);
|
||||||
|
assert(set.data.length == 3);
|
||||||
|
|
||||||
|
assert(set.insert(7) == 1);
|
||||||
|
assert(set.insert(8) == 1);
|
||||||
|
assert(set.data[0].content == 7);
|
||||||
|
assert(set.data[1].content == 8);
|
||||||
|
assert(set.data[2].content == 9);
|
||||||
|
assert(set.data[3].status == BucketStatus.empty);
|
||||||
|
assert(set.data[5].content == 5);
|
||||||
|
assert(set.data.length == 7);
|
||||||
|
|
||||||
|
assert(set.insert(16) == 1);
|
||||||
|
assert(set.data[2].content == 9);
|
||||||
|
assert(set.data[3].content == 16);
|
||||||
|
assert(set.data[4].status == BucketStatus.empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static checks.
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
import std.range.primitives;
|
||||||
|
|
||||||
|
static assert(isBidirectionalRange!(Set!int.ConstRange));
|
||||||
|
static assert(isBidirectionalRange!(Set!int.Range));
|
||||||
|
|
||||||
|
static assert(!isInfinite!(Set!int.Range));
|
||||||
|
static assert(!hasLength!(Set!int.Range));
|
||||||
|
|
||||||
|
static assert(is(Set!uint));
|
||||||
|
static assert(is(Set!long));
|
||||||
|
static assert(is(Set!ulong));
|
||||||
|
static assert(is(Set!short));
|
||||||
|
static assert(is(Set!ushort));
|
||||||
|
static assert(is(Set!bool));
|
||||||
|
}
|
1506
source/tanya/container/string.d
Normal file
1506
source/tanya/container/string.d
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ import std.algorithm;
|
|||||||
import std.ascii;
|
import std.ascii;
|
||||||
import std.range;
|
import std.range;
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import tanya.container.vector;
|
import tanya.container.array;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,6 +160,45 @@ struct Integer
|
|||||||
assert(integer == 7383520307673030126);
|
assert(integer == 7383520307673030126);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the integer from a two's complement representation.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* R = Range type.
|
||||||
|
* value = Range.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this(R)(R value,
|
||||||
|
shared Allocator allocator = defaultAllocator)
|
||||||
|
if (isBidirectionalRange!R && hasLength!R
|
||||||
|
&& is(Unqual!(ElementType!R) == ubyte))
|
||||||
|
{
|
||||||
|
this(Sign.positive, value, allocator);
|
||||||
|
|
||||||
|
if (!value.empty && ((value.front & 0x80) != 0))
|
||||||
|
{
|
||||||
|
// Negative number.
|
||||||
|
opOpAssign!"-"(exp2(countBits()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
nothrow @safe @nogc unittest
|
||||||
|
{
|
||||||
|
{
|
||||||
|
ubyte[8] range = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
auto integer = Integer(range[]);
|
||||||
|
assert(integer == 7383520307673030126);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
ubyte[8] range = [ 0xe6, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
auto integer = Integer(range[]);
|
||||||
|
assert(integer == -1839851729181745682);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the integer.
|
* Copies the integer.
|
||||||
*/
|
*/
|
||||||
@ -178,12 +217,64 @@ struct Integer
|
|||||||
allocator.resize(this.rep, 0);
|
allocator.resize(this.rep, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static private const short[16] bitCounts = [
|
||||||
|
4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0
|
||||||
|
];
|
||||||
|
|
||||||
|
// Counts the number of LSBs before the first non-zero bit.
|
||||||
|
private ptrdiff_t countLSBs() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
if (this.size == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrdiff_t bits;
|
||||||
|
for (bits = 0; (bits < this.size) && (this.rep[bits] == 0); ++bits)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
digit nonZero = this.rep[bits];
|
||||||
|
bits *= digitBitCount;
|
||||||
|
|
||||||
|
/* now scan this digit until a 1 is found */
|
||||||
|
if ((nonZero & 0x01) == 0)
|
||||||
|
{
|
||||||
|
digit bitCountsPos;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
bitCountsPos = nonZero & 0x0f;
|
||||||
|
bits += bitCounts[bitCountsPos];
|
||||||
|
nonZero >>= 4;
|
||||||
|
}
|
||||||
|
while (bitCountsPos == 0);
|
||||||
|
}
|
||||||
|
return bits;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Integer byte length.
|
* Returns: Number of bytes in the two's complement representation.
|
||||||
*/
|
*/
|
||||||
@property size_t length() const pure nothrow @safe @nogc
|
@property size_t length() const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return (countBits() + 7) / 8; // Round up.
|
if (this.sign)
|
||||||
|
{
|
||||||
|
const bc = countBits();
|
||||||
|
auto length = bc + (8 - (bc & 0x07));
|
||||||
|
|
||||||
|
if (((countLSBs() + 1) == bc) && ((bc & 0x07) == 0))
|
||||||
|
{
|
||||||
|
--length;
|
||||||
|
}
|
||||||
|
return length / 8;
|
||||||
|
}
|
||||||
|
else if (this.size == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (countBits() / 8) + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -244,17 +335,11 @@ struct Integer
|
|||||||
/// Ditto.
|
/// Ditto.
|
||||||
ref Integer opAssign(T)(T value) nothrow @safe @nogc
|
ref Integer opAssign(T)(T value) nothrow @safe @nogc
|
||||||
if (is(T == Integer))
|
if (is(T == Integer))
|
||||||
{
|
|
||||||
if (this.allocator is value.allocator)
|
|
||||||
{
|
{
|
||||||
swap(this.rep, value.rep);
|
swap(this.rep, value.rep);
|
||||||
swap(this.sign, value.sign);
|
swap(this.sign, value.sign);
|
||||||
swap(this.size, value.size);
|
swap(this.size, value.size);
|
||||||
}
|
swap(this.allocator_, value.allocator_);
|
||||||
else
|
|
||||||
{
|
|
||||||
this = value;
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1343,79 +1428,72 @@ struct Integer
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector!ubyte toVector() const nothrow @safe @nogc
|
// Returns 2^^n.
|
||||||
|
private Integer exp2(size_t n) const nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
Vector!ubyte vector;
|
auto ret = Integer(allocator);
|
||||||
bool firstBit;
|
const bytes = n / digitBitCount;
|
||||||
ubyte carry;
|
|
||||||
|
ret.grow(bytes + 1);
|
||||||
|
ret.size = bytes + 1;
|
||||||
|
ret.rep[bytes] = (cast(digit) 1) << (n % digitBitCount);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Two's complement representation of the integer.
|
||||||
|
*/
|
||||||
|
Array!ubyte toArray() const nothrow @safe @nogc
|
||||||
|
out (array)
|
||||||
|
{
|
||||||
|
assert(array.length == length);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
Array!ubyte array;
|
||||||
|
|
||||||
if (this.size == 0)
|
if (this.size == 0)
|
||||||
{
|
{
|
||||||
return vector;
|
return array;
|
||||||
}
|
}
|
||||||
vector.reserve(this.size * digit.sizeof);
|
const bc = countBits();
|
||||||
|
const remainingBits = bc & 0x07;
|
||||||
|
|
||||||
// The first digit needs extra handling since it can have leading
|
array.reserve(bc / 8);
|
||||||
// non significant zeros.
|
if (remainingBits == 0)
|
||||||
int digitCount = digitBitCount - 8;
|
{
|
||||||
const first = this.rep[this.size - 1];
|
array.insertBack(ubyte.init);
|
||||||
const prevBitCount = ((this.size - 1) * digitBitCount);
|
|
||||||
const fullBytesBitCount = ((prevBitCount - 1) / 8 + 1) * 8;
|
|
||||||
|
|
||||||
// Find out the right alignment of the first byte.
|
|
||||||
if ((fullBytesBitCount - prevBitCount) == 0)
|
|
||||||
{
|
|
||||||
digitCount -= digit.sizeof * 8 - digitBitCount;
|
|
||||||
}
|
}
|
||||||
for (; digitCount >= 0; digitCount -= 8)
|
|
||||||
|
Integer tmp;
|
||||||
|
if (this.sign)
|
||||||
{
|
{
|
||||||
if (firstBit || ((first >> digitCount) != 0))
|
auto length = bc + (8 - remainingBits);
|
||||||
|
|
||||||
|
if (((countLSBs() + 1) == bc) && (remainingBits == 0))
|
||||||
{
|
{
|
||||||
firstBit = true;
|
length -= 8;
|
||||||
vector.insertBack(cast(ubyte) (first >> digitCount));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (digitCount >= -8)
|
tmp = exp2(length) + this;
|
||||||
{
|
|
||||||
carry = (first << -digitCount) & 0xff;
|
|
||||||
digitCount += digitBitCount;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
carry = 0;
|
tmp = this;
|
||||||
digitCount = digitBitCount - 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach_reverse (d; this.rep[0 .. this.size - 1])
|
do
|
||||||
{
|
{
|
||||||
if (carry != 0) // Check the carry from the previous digit.
|
array.insertBack(cast(ubyte) (tmp.rep[0] & 0xff));
|
||||||
{
|
tmp >>= 8;
|
||||||
vector.insertBack(cast(ubyte) (carry | (d >> digitCount)));
|
|
||||||
digitCount -= 8;
|
|
||||||
}
|
|
||||||
// Write the digit by bytes.
|
|
||||||
for (; digitCount >= 0; digitCount -= 8)
|
|
||||||
{
|
|
||||||
vector.insertBack(cast(ubyte) (d >> digitCount));
|
|
||||||
}
|
|
||||||
// Check for an incomplete byte.
|
|
||||||
if (digitCount >= -8)
|
|
||||||
{
|
|
||||||
carry = (d << -digitCount) & 0xff;
|
|
||||||
digitCount += digitBitCount;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
carry = 0;
|
|
||||||
digitCount = digitBitCount - 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (carry != 0)
|
|
||||||
{
|
|
||||||
vector.insertBack(cast(ubyte) (carry >> (digitBitCount - digitCount)));
|
|
||||||
}
|
}
|
||||||
|
while (tmp != 0);
|
||||||
|
|
||||||
return vector;
|
array[].reverse();
|
||||||
|
|
||||||
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -1425,15 +1503,15 @@ struct Integer
|
|||||||
auto integer = Integer(0x66778899aabbddee);
|
auto integer = Integer(0x66778899aabbddee);
|
||||||
ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
ubyte[8] expected = [ 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xdd, 0xee ];
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto integer = Integer(0x03);
|
auto integer = Integer(0x03);
|
||||||
ubyte[1] expected = [ 0x03 ];
|
ubyte[1] expected = [ 0x03 ];
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ubyte[63] expected = [
|
ubyte[63] expected = [
|
||||||
@ -1448,8 +1526,8 @@ struct Integer
|
|||||||
];
|
];
|
||||||
auto integer = Integer(Sign.positive, expected[]);
|
auto integer = Integer(Sign.positive, expected[]);
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
ubyte[14] expected = [
|
ubyte[14] expected = [
|
||||||
@ -1458,8 +1536,8 @@ struct Integer
|
|||||||
];
|
];
|
||||||
auto integer = Integer(Sign.positive, expected[]);
|
auto integer = Integer(Sign.positive, expected[]);
|
||||||
|
|
||||||
auto vector = integer.toVector();
|
auto array = integer.toArray();
|
||||||
assert(equal(vector[], expected[]));
|
assert(equal(array[], expected[]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,19 @@ import std.range;
|
|||||||
import std.traits;
|
import std.traits;
|
||||||
import tanya.memory;
|
import tanya.memory;
|
||||||
|
|
||||||
package(tanya) final class RefCountedStore(T)
|
private template Payload(T)
|
||||||
|
{
|
||||||
|
static if (is(T == class) || is(T == interface) || isArray!T)
|
||||||
|
{
|
||||||
|
alias Payload = T;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias Payload = T*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class RefCountedStore(T)
|
||||||
{
|
{
|
||||||
T payload;
|
T payload;
|
||||||
size_t counter = 1;
|
size_t counter = 1;
|
||||||
@ -34,7 +46,7 @@ package(tanya) final class RefCountedStore(T)
|
|||||||
mixin("return " ~ op ~ "counter;");
|
mixin("return " ~ op ~ "counter;");
|
||||||
}
|
}
|
||||||
|
|
||||||
int opCmp(size_t counter)
|
int opCmp(const size_t counter)
|
||||||
{
|
{
|
||||||
if (this.counter > counter)
|
if (this.counter > counter)
|
||||||
{
|
{
|
||||||
@ -50,7 +62,7 @@ package(tanya) final class RefCountedStore(T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int opEquals(size_t counter)
|
int opEquals(const size_t counter)
|
||||||
{
|
{
|
||||||
return this.counter == counter;
|
return this.counter == counter;
|
||||||
}
|
}
|
||||||
@ -81,15 +93,7 @@ private void unifiedDeleter(T)(RefCountedStore!T storage,
|
|||||||
*/
|
*/
|
||||||
struct RefCounted(T)
|
struct RefCounted(T)
|
||||||
{
|
{
|
||||||
static if (is(T == class) || is(T == interface) || isArray!T)
|
private alias Storage = RefCountedStore!(Payload!T);
|
||||||
{
|
|
||||||
private alias Payload = T;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
private alias Payload = T*;
|
|
||||||
}
|
|
||||||
private alias Storage = RefCountedStore!Payload;
|
|
||||||
|
|
||||||
private Storage storage;
|
private Storage storage;
|
||||||
private void function(Storage storage,
|
private void function(Storage storage,
|
||||||
@ -112,13 +116,18 @@ struct RefCounted(T)
|
|||||||
*
|
*
|
||||||
* Precondition: $(D_INLINECODE allocator !is null)
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
*/
|
*/
|
||||||
this()(auto ref Payload value,
|
this()(auto ref Payload!T value,
|
||||||
shared Allocator allocator = defaultAllocator)
|
shared Allocator allocator = defaultAllocator)
|
||||||
{
|
{
|
||||||
this(allocator);
|
this(allocator);
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
|
|
||||||
move(value, this.storage.payload);
|
move(value, this.storage.payload);
|
||||||
|
static if (__traits(isRef, value))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
@ -139,7 +148,7 @@ struct RefCounted(T)
|
|||||||
{
|
{
|
||||||
if (count != 0)
|
if (count != 0)
|
||||||
{
|
{
|
||||||
++storage;
|
++this.storage;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,7 +161,7 @@ struct RefCounted(T)
|
|||||||
{
|
{
|
||||||
if (this.storage !is null && !(this.storage.counter && --this.storage))
|
if (this.storage !is null && !(this.storage.counter && --this.storage))
|
||||||
{
|
{
|
||||||
deleter(storage, allocator);
|
deleter(this.storage, allocator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,34 +172,34 @@ struct RefCounted(T)
|
|||||||
* If it is the last reference of the previously owned object,
|
* If it is the last reference of the previously owned object,
|
||||||
* it will be destroyed.
|
* it will be destroyed.
|
||||||
*
|
*
|
||||||
* To reset the $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
* To reset $(D_PSYMBOL RefCounted) assign $(D_KEYWORD null).
|
||||||
*
|
*
|
||||||
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
* be used. If you need a different allocator, create a new
|
* be used. If you need a different allocator, create a new
|
||||||
* $(D_PSYMBOL RefCounted) and assign it.
|
* $(D_PSYMBOL RefCounted) and assign it.
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* rhs = $(D_KEYWORD this).
|
* rhs = New object.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD this).
|
||||||
*/
|
*/
|
||||||
ref typeof(this) opAssign()(auto ref Payload rhs)
|
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||||
{
|
{
|
||||||
if (this.storage is null)
|
if (this.storage is null)
|
||||||
{
|
{
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
}
|
}
|
||||||
else if (this.storage > 1)
|
else if (this.storage > 1)
|
||||||
{
|
{
|
||||||
--this.storage;
|
--this.storage;
|
||||||
this.storage = allocator.make!Storage();
|
this.storage = allocator.make!Storage();
|
||||||
this.deleter = &separateDeleter!Payload;
|
this.deleter = &separateDeleter!(Payload!T);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
|
||||||
assert(this.storage.counter != 0);
|
|
||||||
finalize(this.storage.payload);
|
finalize(this.storage.payload);
|
||||||
this.storage.payload = Payload.init;
|
this.storage.payload = Payload!T.init;
|
||||||
}
|
}
|
||||||
move(rhs, this.storage.payload);
|
move(rhs, this.storage.payload);
|
||||||
return this;
|
return this;
|
||||||
@ -206,15 +215,13 @@ struct RefCounted(T)
|
|||||||
else if (this.storage > 1)
|
else if (this.storage > 1)
|
||||||
{
|
{
|
||||||
--this.storage;
|
--this.storage;
|
||||||
this.storage = null;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Created with refCounted. Always destroyed togethter with the pointer.
|
deleter(this.storage, allocator);
|
||||||
assert(this.storage.counter != 0);
|
|
||||||
finalize(this.storage.payload);
|
|
||||||
this.storage.payload = Payload.init;
|
|
||||||
}
|
}
|
||||||
|
this.storage = null;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,8 +236,10 @@ struct RefCounted(T)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Reference to the owned object.
|
* Returns: Reference to the owned object.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE cound > 0).
|
||||||
*/
|
*/
|
||||||
inout(Payload) get() inout pure nothrow @safe @nogc
|
Payload!T get() pure nothrow @safe @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(count > 0, "Attempted to access an uninitialized reference.");
|
assert(count > 0, "Attempted to access an uninitialized reference.");
|
||||||
@ -240,7 +249,7 @@ struct RefCounted(T)
|
|||||||
return storage.payload;
|
return storage.payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
static if (isPointer!Payload)
|
version (D_Ddoc)
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
@ -251,6 +260,11 @@ struct RefCounted(T)
|
|||||||
*
|
*
|
||||||
* Returns: Reference to the pointed value.
|
* Returns: Reference to the pointed value.
|
||||||
*/
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*");
|
||||||
|
}
|
||||||
|
else static if (isPointer!(Payload!T))
|
||||||
|
{
|
||||||
ref T opUnary(string op)()
|
ref T opUnary(string op)()
|
||||||
if (op == "*")
|
if (op == "*")
|
||||||
{
|
{
|
||||||
@ -352,6 +366,44 @@ private unittest
|
|||||||
assert(rc.count == 1);
|
assert(rc.count == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
void func(RefCounted!int rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
rc = null;
|
||||||
|
assert(!rc.isInitialized);
|
||||||
|
assert(rc.count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(rc.count == 1);
|
||||||
|
func(rc);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
|
||||||
|
rc = null;
|
||||||
|
assert(!rc.isInitialized);
|
||||||
|
assert(rc.count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!int(5);
|
||||||
|
assert(*rc == 5);
|
||||||
|
|
||||||
|
void func(RefCounted!int rc)
|
||||||
|
{
|
||||||
|
assert(rc.count == 2);
|
||||||
|
rc = defaultAllocator.refCounted!int(4);
|
||||||
|
assert(*rc == 4);
|
||||||
|
assert(rc.count == 1);
|
||||||
|
}
|
||||||
|
func(rc);
|
||||||
|
assert(*rc == 5);
|
||||||
|
}
|
||||||
|
|
||||||
private unittest
|
private unittest
|
||||||
{
|
{
|
||||||
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
static assert(is(typeof(RefCounted!int.storage.payload) == int*));
|
||||||
@ -414,13 +466,13 @@ body
|
|||||||
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
auto ptr = (() @trusted => (cast(T*) mem[storageSize .. $].ptr))();
|
||||||
rc.storage.payload = emplace!T(ptr, args);
|
rc.storage.payload = emplace!T(ptr, args);
|
||||||
}
|
}
|
||||||
rc.deleter = &unifiedDeleter!(RefCounted!T.Payload);
|
rc.deleter = &unifiedDeleter!(Payload!T);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||||
* $(D_PSYMBOL RefCounted) using.
|
* $(D_PSYMBOL RefCounted).
|
||||||
*
|
*
|
||||||
* Params:
|
* Params:
|
||||||
* T = Array type.
|
* T = Array type.
|
||||||
@ -433,6 +485,7 @@ body
|
|||||||
* && size <= size_t.max / ElementType!T.sizeof)
|
* && size <= size_t.max / ElementType!T.sizeof)
|
||||||
*/
|
*/
|
||||||
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
|
RefCounted!T refCounted(T)(shared Allocator allocator, const size_t size)
|
||||||
|
@trusted
|
||||||
if (isArray!T)
|
if (isArray!T)
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@ -441,8 +494,7 @@ in
|
|||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
auto payload = cast(T) allocator.allocate(ElementType!T.sizeof * size);
|
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||||
initializeAll(payload);
|
|
||||||
return RefCounted!T(payload, allocator);
|
return RefCounted!T(payload, allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,3 +547,266 @@ private @nogc unittest
|
|||||||
auto rc = defaultAllocator.refCounted!(int[])(5);
|
auto rc = defaultAllocator.refCounted!(int[])(5);
|
||||||
assert(rc.length == 5);
|
assert(rc.length == 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private @nogc unittest
|
||||||
|
{
|
||||||
|
static bool destroyed = false;
|
||||||
|
|
||||||
|
struct F
|
||||||
|
{
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto rc = defaultAllocator.refCounted!F();
|
||||||
|
}
|
||||||
|
assert(destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Value type.
|
||||||
|
*/
|
||||||
|
struct Scoped(T)
|
||||||
|
{
|
||||||
|
private Payload!T payload;
|
||||||
|
|
||||||
|
invariant
|
||||||
|
{
|
||||||
|
assert(payload is null || allocator_ !is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes ownership over $(D_PARAM value), setting the counter to 1.
|
||||||
|
* $(D_PARAM value) may be a pointer, an object or a dynamic array.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* value = Value whose ownership is taken over.
|
||||||
|
* allocator = Allocator used to destroy the $(D_PARAM value) and to
|
||||||
|
* allocate/deallocate internal storage.
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
this()(auto ref Payload!T value,
|
||||||
|
shared Allocator allocator = defaultAllocator)
|
||||||
|
{
|
||||||
|
this(allocator);
|
||||||
|
|
||||||
|
move(value, this.payload);
|
||||||
|
static if (__traits(isRef, value))
|
||||||
|
{
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
this(shared Allocator allocator)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
this.allocator_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL Scoped) is noncopyable.
|
||||||
|
*/
|
||||||
|
@disable this(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the owned object.
|
||||||
|
*/
|
||||||
|
~this()
|
||||||
|
{
|
||||||
|
if (this.payload !is null)
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialized this $(D_PARAM Scoped) and takes ownership over
|
||||||
|
* $(D_PARAM rhs).
|
||||||
|
*
|
||||||
|
* To reset $(D_PSYMBOL Scoped) assign $(D_KEYWORD null).
|
||||||
|
*
|
||||||
|
* If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will
|
||||||
|
* be used. If you need a different allocator, create a new
|
||||||
|
* $(D_PSYMBOL Scoped) and assign it.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* rhs = New object.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD this).
|
||||||
|
*/
|
||||||
|
ref typeof(this) opAssign()(auto ref Payload!T rhs)
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
move(rhs, this.payload);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(null))
|
||||||
|
{
|
||||||
|
allocator.dispose(this.payload);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ditto.
|
||||||
|
ref typeof(this) opAssign(typeof(this) rhs)
|
||||||
|
{
|
||||||
|
swap(this.allocator_, rhs.allocator_);
|
||||||
|
swap(this.payload, rhs.payload);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Reference to the owned object.
|
||||||
|
*/
|
||||||
|
Payload!T get() pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
version (D_Ddoc)
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* op = Operation.
|
||||||
|
*
|
||||||
|
* Dereferences the pointer. It is defined only for pointers, not for
|
||||||
|
* reference types like classes, that can be accessed directly.
|
||||||
|
*
|
||||||
|
* Returns: Reference to the pointed value.
|
||||||
|
*/
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*");
|
||||||
|
}
|
||||||
|
else static if (isPointer!(Payload!T))
|
||||||
|
{
|
||||||
|
ref T opUnary(string op)()
|
||||||
|
if (op == "*")
|
||||||
|
{
|
||||||
|
return *payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin DefaultAllocator;
|
||||||
|
alias get this;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc unittest
|
||||||
|
{
|
||||||
|
auto p = defaultAllocator.make!int(5);
|
||||||
|
auto s = Scoped!int(p, defaultAllocator);
|
||||||
|
assert(p is null);
|
||||||
|
assert(*s == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
@nogc unittest
|
||||||
|
{
|
||||||
|
static bool destroyed = false;
|
||||||
|
|
||||||
|
struct F
|
||||||
|
{
|
||||||
|
~this() @nogc
|
||||||
|
{
|
||||||
|
destroyed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator);
|
||||||
|
}
|
||||||
|
assert(destroyed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new object of type $(D_PARAM T) and wraps it in a
|
||||||
|
* $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for
|
||||||
|
* the constructor of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Type of the constructed object.
|
||||||
|
* A = Types of the arguments to the constructor of $(D_PARAM T).
|
||||||
|
* allocator = Allocator.
|
||||||
|
* args = Constructor arguments of $(D_PARAM T).
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null)
|
||||||
|
*/
|
||||||
|
Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args)
|
||||||
|
if (!is(T == interface) && !isAbstractClass!T
|
||||||
|
&& !isAssociativeArray!T && !isArray!T)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
auto payload = allocator.make!(T, shared Allocator, A)(args);
|
||||||
|
return Scoped!T(payload, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new array with $(D_PARAM size) elements and wraps it in a
|
||||||
|
* $(D_PSYMBOL Scoped).
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* T = Array type.
|
||||||
|
* size = Array size.
|
||||||
|
* allocator = Allocator.
|
||||||
|
*
|
||||||
|
* Returns: Newly created $(D_PSYMBOL Scoped!T).
|
||||||
|
*
|
||||||
|
* Precondition: $(D_INLINECODE allocator !is null
|
||||||
|
* && size <= size_t.max / ElementType!T.sizeof)
|
||||||
|
*/
|
||||||
|
Scoped!T scoped(T)(shared Allocator allocator, const size_t size)
|
||||||
|
@trusted
|
||||||
|
if (isArray!T)
|
||||||
|
in
|
||||||
|
{
|
||||||
|
assert(allocator !is null);
|
||||||
|
assert(size <= size_t.max / ElementType!T.sizeof);
|
||||||
|
}
|
||||||
|
body
|
||||||
|
{
|
||||||
|
auto payload = allocator.resize!(ElementType!T)(null, size);
|
||||||
|
return Scoped!T(payload, allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
static assert(is(typeof(defaultAllocator.scoped!B(5))));
|
||||||
|
static assert(is(typeof(defaultAllocator.scoped!(int[])(5))));
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto s = defaultAllocator.scoped!int(5);
|
||||||
|
assert(*s == 5);
|
||||||
|
|
||||||
|
s = null;
|
||||||
|
assert(s is null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unittest
|
||||||
|
{
|
||||||
|
auto s = defaultAllocator.scoped!int(5);
|
||||||
|
assert(*s == 5);
|
||||||
|
|
||||||
|
s = defaultAllocator.scoped!int(4);
|
||||||
|
assert(*s == 4);
|
||||||
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Socket programming.
|
* Low-level socket programming.
|
||||||
*
|
*
|
||||||
* Copyright: Eugene Wissner 2016-2017.
|
* Copyright: Eugene Wissner 2016-2017.
|
||||||
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
* License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/,
|
||||||
@ -12,15 +12,16 @@
|
|||||||
*/
|
*/
|
||||||
module tanya.network.socket;
|
module tanya.network.socket;
|
||||||
|
|
||||||
import tanya.memory;
|
|
||||||
import core.stdc.errno;
|
import core.stdc.errno;
|
||||||
import core.time;
|
import core.time;
|
||||||
import std.algorithm.comparison;
|
import std.algorithm.comparison;
|
||||||
import std.algorithm.searching;
|
public import std.socket : SocketOptionLevel, SocketOption;
|
||||||
public import std.socket : socket_t, Linger, SocketOptionLevel, SocketOption,
|
|
||||||
SocketType, AddressFamily, AddressInfo;
|
|
||||||
import std.traits;
|
import std.traits;
|
||||||
import std.typecons;
|
import std.typecons;
|
||||||
|
import tanya.memory;
|
||||||
|
|
||||||
|
/// Value returned by socket operations on error.
|
||||||
|
enum int socketError = -1;
|
||||||
|
|
||||||
version (Posix)
|
version (Posix)
|
||||||
{
|
{
|
||||||
@ -32,7 +33,12 @@ version (Posix)
|
|||||||
import core.sys.posix.sys.time;
|
import core.sys.posix.sys.time;
|
||||||
import core.sys.posix.unistd;
|
import core.sys.posix.unistd;
|
||||||
|
|
||||||
private enum SOCKET_ERROR = -1;
|
enum SocketType : int
|
||||||
|
{
|
||||||
|
init = -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
private alias LingerField = int;
|
||||||
}
|
}
|
||||||
else version (Windows)
|
else version (Windows)
|
||||||
{
|
{
|
||||||
@ -43,16 +49,23 @@ else version (Windows)
|
|||||||
import core.sys.windows.windef;
|
import core.sys.windows.windef;
|
||||||
import core.sys.windows.winsock2;
|
import core.sys.windows.winsock2;
|
||||||
|
|
||||||
|
enum SocketType : size_t
|
||||||
|
{
|
||||||
|
init = ~0,
|
||||||
|
}
|
||||||
|
|
||||||
|
private alias LingerField = ushort;
|
||||||
|
|
||||||
enum : uint
|
enum : uint
|
||||||
{
|
{
|
||||||
IOC_UNIX = 0x00000000,
|
IOC_UNIX = 0x00000000,
|
||||||
IOC_WS2 = 0x08000000,
|
IOC_WS2 = 0x08000000,
|
||||||
IOC_PROTOCOL = 0x10000000,
|
IOC_PROTOCOL = 0x10000000,
|
||||||
IOC_VOID = 0x20000000, /// No parameters.
|
IOC_VOID = 0x20000000, // No parameters.
|
||||||
IOC_OUT = 0x40000000, /// Copy parameters back.
|
IOC_OUT = 0x40000000, // Copy parameters back.
|
||||||
IOC_IN = 0x80000000, /// Copy parameters into.
|
IOC_IN = 0x80000000, // Copy parameters into.
|
||||||
IOC_VENDOR = 0x18000000,
|
IOC_VENDOR = 0x18000000,
|
||||||
IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.
|
IOC_INOUT = (IOC_IN | IOC_OUT), // Copy parameter into and get back.
|
||||||
}
|
}
|
||||||
|
|
||||||
template _WSAIO(int x, int y)
|
template _WSAIO(int x, int y)
|
||||||
@ -183,19 +196,26 @@ else version (Windows)
|
|||||||
LPINT,
|
LPINT,
|
||||||
SOCKADDR**,
|
SOCKADDR**,
|
||||||
LPINT);
|
LPINT);
|
||||||
const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
|
const GUID WSAID_GETACCEPTEXSOCKADDRS = {
|
||||||
|
0xb5367df2, 0xcbac, 0x11cf,
|
||||||
|
[ 0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92 ],
|
||||||
|
};
|
||||||
|
|
||||||
struct WSABUF {
|
struct WSABUF
|
||||||
|
{
|
||||||
ULONG len;
|
ULONG len;
|
||||||
CHAR* buf;
|
CHAR* buf;
|
||||||
}
|
}
|
||||||
alias WSABUF* LPWSABUF;
|
alias WSABUF* LPWSABUF;
|
||||||
|
|
||||||
struct WSAOVERLAPPED {
|
struct WSAOVERLAPPED
|
||||||
|
{
|
||||||
ULONG_PTR Internal;
|
ULONG_PTR Internal;
|
||||||
ULONG_PTR InternalHigh;
|
ULONG_PTR InternalHigh;
|
||||||
union {
|
union
|
||||||
struct {
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
DWORD Offset;
|
DWORD Offset;
|
||||||
DWORD OffsetHigh;
|
DWORD OffsetHigh;
|
||||||
}
|
}
|
||||||
@ -219,36 +239,13 @@ else version (Windows)
|
|||||||
private WSABUF buffer;
|
private WSABUF buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Socket returned if a connection has been established.
|
|
||||||
*/
|
|
||||||
class OverlappedConnectedSocket : ConnectedSocket
|
class OverlappedConnectedSocket : ConnectedSocket
|
||||||
{
|
{
|
||||||
/**
|
this(SocketType handle, AddressFamily af) @nogc
|
||||||
* Create a socket.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* handle = Socket handle.
|
|
||||||
* af = Address family.
|
|
||||||
*/
|
|
||||||
this(socket_t handle, AddressFamily af) @nogc
|
|
||||||
{
|
{
|
||||||
super(handle, af);
|
super(handle, af);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins to asynchronously receive data from a connected socket.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* buffer = Storage location for the received data.
|
|
||||||
* flags = Flags.
|
|
||||||
* overlapped = Unique operation identifier.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
|
||||||
*/
|
|
||||||
bool beginReceive(ubyte[] buffer,
|
bool beginReceive(ubyte[] buffer,
|
||||||
SocketState overlapped,
|
SocketState overlapped,
|
||||||
Flags flags = Flags(Flag.none)) @nogc @trusted
|
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||||
@ -268,23 +265,13 @@ else version (Windows)
|
|||||||
&overlapped.overlapped,
|
&overlapped.overlapped,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
if (result == socketError && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to receive");
|
throw defaultAllocator.make!SocketException("Unable to receive");
|
||||||
}
|
}
|
||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ends a pending asynchronous read.
|
|
||||||
*
|
|
||||||
* Params
|
|
||||||
* overlapped = Unique operation identifier.
|
|
||||||
*
|
|
||||||
* Returns: Number of bytes received.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
|
||||||
*/
|
|
||||||
int endReceive(SocketState overlapped) @nogc @trusted
|
int endReceive(SocketState overlapped) @nogc @trusted
|
||||||
out (count)
|
out (count)
|
||||||
{
|
{
|
||||||
@ -309,19 +296,6 @@ else version (Windows)
|
|||||||
return lpNumber;
|
return lpNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends data asynchronously to a connected socket.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* buffer = Data to be sent.
|
|
||||||
* flags = Flags.
|
|
||||||
* overlapped = Unique operation identifier.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to send.
|
|
||||||
*/
|
|
||||||
bool beginSend(ubyte[] buffer,
|
bool beginSend(ubyte[] buffer,
|
||||||
SocketState overlapped,
|
SocketState overlapped,
|
||||||
Flags flags = Flags(Flag.none)) @nogc @trusted
|
Flags flags = Flags(Flag.none)) @nogc @trusted
|
||||||
@ -339,7 +313,7 @@ else version (Windows)
|
|||||||
&overlapped.overlapped,
|
&overlapped.overlapped,
|
||||||
NULL);
|
NULL);
|
||||||
|
|
||||||
if (result == SOCKET_ERROR && !wouldHaveBlocked)
|
if (result == socketError && !wouldHaveBlocked)
|
||||||
{
|
{
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
throw defaultAllocator.make!SocketException("Unable to send");
|
throw defaultAllocator.make!SocketException("Unable to send");
|
||||||
@ -347,16 +321,6 @@ else version (Windows)
|
|||||||
return result == 0;
|
return result == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Ends a pending asynchronous send.
|
|
||||||
*
|
|
||||||
* Params
|
|
||||||
* overlapped = Unique operation identifier.
|
|
||||||
*
|
|
||||||
* Returns: Number of bytes sent.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
|
||||||
*/
|
|
||||||
int endSend(SocketState overlapped) @nogc @trusted
|
int endSend(SocketState overlapped) @nogc @trusted
|
||||||
out (count)
|
out (count)
|
||||||
{
|
{
|
||||||
@ -380,17 +344,9 @@ else version (Windows)
|
|||||||
|
|
||||||
class OverlappedStreamSocket : StreamSocket
|
class OverlappedStreamSocket : StreamSocket
|
||||||
{
|
{
|
||||||
/// Accept extension function pointer.
|
// Accept extension function pointer.
|
||||||
package LPFN_ACCEPTEX acceptExtension;
|
package LPFN_ACCEPTEX acceptExtension;
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a socket.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* af = Address family.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) on errors.
|
|
||||||
*/
|
|
||||||
this(AddressFamily af) @nogc @trusted
|
this(AddressFamily af) @nogc @trusted
|
||||||
{
|
{
|
||||||
super(af);
|
super(af);
|
||||||
@ -412,28 +368,17 @@ else version (Windows)
|
|||||||
&dwBytes,
|
&dwBytes,
|
||||||
NULL,
|
NULL,
|
||||||
NULL);
|
NULL);
|
||||||
if (!result == SOCKET_ERROR)
|
if (!result == socketError)
|
||||||
{
|
{
|
||||||
throw make!SocketException(defaultAllocator,
|
throw make!SocketException(defaultAllocator,
|
||||||
"Unable to retrieve an accept extension function pointer");
|
"Unable to retrieve an accept extension function pointer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Begins an asynchronous operation to accept an incoming connection attempt.
|
|
||||||
*
|
|
||||||
* Params:
|
|
||||||
* overlapped = Unique operation identifier.
|
|
||||||
*
|
|
||||||
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
|
||||||
* $(D_KEYWORD false) otherwise.
|
|
||||||
*
|
|
||||||
* Throws: $(D_PSYMBOL SocketException) on accept errors.
|
|
||||||
*/
|
|
||||||
bool beginAccept(SocketState overlapped) @nogc @trusted
|
bool beginAccept(SocketState overlapped) @nogc @trusted
|
||||||
{
|
{
|
||||||
auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
|
auto socket = cast(SocketType) socket(addressFamily, 1, 0);
|
||||||
if (socket == socket_t.init)
|
if (socket == SocketType.init)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to create socket");
|
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||||
}
|
}
|
||||||
@ -445,7 +390,7 @@ else version (Windows)
|
|||||||
overlapped.handle = cast(HANDLE) socket;
|
overlapped.handle = cast(HANDLE) socket;
|
||||||
overlapped.event = OverlappedSocketEvent.accept;
|
overlapped.event = OverlappedSocketEvent.accept;
|
||||||
|
|
||||||
immutable len = (sockaddr_in.sizeof + 16) * 2;
|
const len = (sockaddr_in.sizeof + 16) * 2;
|
||||||
overlapped.buffer.len = len;
|
overlapped.buffer.len = len;
|
||||||
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
|
overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
|
||||||
|
|
||||||
@ -465,6 +410,139 @@ else version (Windows)
|
|||||||
return result == TRUE;
|
return result == TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OverlappedConnectedSocket endAccept(SocketState overlapped)
|
||||||
|
@nogc @trusted
|
||||||
|
{
|
||||||
|
scope (exit)
|
||||||
|
{
|
||||||
|
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
|
||||||
|
}
|
||||||
|
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
|
||||||
|
cast(SocketType) overlapped.handle,
|
||||||
|
addressFamily);
|
||||||
|
scope (failure)
|
||||||
|
{
|
||||||
|
defaultAllocator.dispose(socket);
|
||||||
|
}
|
||||||
|
socket.setOption(SocketOptionLevel.SOCKET,
|
||||||
|
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
||||||
|
cast(size_t) handle);
|
||||||
|
return socket;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else version (D_Ddoc)
|
||||||
|
{
|
||||||
|
/// Native socket representation type.
|
||||||
|
enum SocketType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket returned if a connection has been established.
|
||||||
|
*
|
||||||
|
* Note: Available only on Windows.
|
||||||
|
*/
|
||||||
|
class OverlappedConnectedSocket : ConnectedSocket
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a socket.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* handle = Socket handle.
|
||||||
|
* af = Address family.
|
||||||
|
*/
|
||||||
|
this(SocketType handle, AddressFamily af) @nogc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins to asynchronously receive data from a connected socket.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* buffer = Storage location for the received data.
|
||||||
|
* flags = Flags.
|
||||||
|
* overlapped = Unique operation identifier.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
|
*/
|
||||||
|
bool beginReceive(ubyte[] buffer,
|
||||||
|
SocketState overlapped,
|
||||||
|
Flags flags = Flags(Flag.none)) @nogc @trusted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends a pending asynchronous read.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* overlapped = Unique operation identifier.
|
||||||
|
*
|
||||||
|
* Returns: Number of bytes received.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE result >= 0).
|
||||||
|
*/
|
||||||
|
int endReceive(SocketState overlapped) @nogc @trusted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends data asynchronously to a connected socket.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* buffer = Data to be sent.
|
||||||
|
* flags = Flags.
|
||||||
|
* overlapped = Unique operation identifier.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) if unable to send.
|
||||||
|
*/
|
||||||
|
bool beginSend(ubyte[] buffer,
|
||||||
|
SocketState overlapped,
|
||||||
|
Flags flags = Flags(Flag.none)) @nogc @trusted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ends a pending asynchronous send.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* overlapped = Unique operation identifier.
|
||||||
|
*
|
||||||
|
* Returns: Number of bytes sent.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) if unable to receive.
|
||||||
|
*
|
||||||
|
* Postcondition: $(D_INLINECODE result >= 0).
|
||||||
|
*/
|
||||||
|
int endSend(SocketState overlapped) @nogc @trusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Windows stream socket overlapped I/O.
|
||||||
|
*/
|
||||||
|
class OverlappedStreamSocket : StreamSocket
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create a socket.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* af = Address family.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) on errors.
|
||||||
|
*/
|
||||||
|
this(AddressFamily af) @nogc @trusted;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begins an asynchronous operation to accept an incoming connection attempt.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* overlapped = Unique operation identifier.
|
||||||
|
*
|
||||||
|
* Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
|
||||||
|
* $(D_KEYWORD false) otherwise.
|
||||||
|
*
|
||||||
|
* Throws: $(D_PSYMBOL SocketException) on accept errors.
|
||||||
|
*/
|
||||||
|
bool beginAccept(SocketState overlapped) @nogc @trusted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously accepts an incoming connection attempt and creates a
|
* Asynchronously accepts an incoming connection attempt and creates a
|
||||||
* new socket to handle remote host communication.
|
* new socket to handle remote host communication.
|
||||||
@ -476,24 +554,116 @@ else version (Windows)
|
|||||||
*
|
*
|
||||||
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
* Throws: $(D_PSYMBOL SocketException) if unable to accept.
|
||||||
*/
|
*/
|
||||||
OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
|
OverlappedConnectedSocket endAccept(SocketState overlapped)
|
||||||
{
|
@nogc @trusted;
|
||||||
scope (exit)
|
|
||||||
{
|
|
||||||
defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
|
|
||||||
}
|
}
|
||||||
auto socket = make!OverlappedConnectedSocket(defaultAllocator,
|
}
|
||||||
cast(socket_t) overlapped.handle,
|
|
||||||
addressFamily);
|
/**
|
||||||
scope (failure)
|
* Socket option that specifies what should happen when the socket that
|
||||||
|
* promises reliable delivery still has untransmitted messages when
|
||||||
|
* it is closed.
|
||||||
|
*/
|
||||||
|
struct Linger
|
||||||
{
|
{
|
||||||
defaultAllocator.dispose(socket);
|
/// If nonzero, $(D_PSYMBOL close) and $(D_PSYMBOL shutdown) block until
|
||||||
|
/// the data are transmitted or the timeout period has expired.
|
||||||
|
LingerField l_onoff;
|
||||||
|
|
||||||
|
/// Time, in seconds to wait before any buffered data to be sent is
|
||||||
|
/// discarded.
|
||||||
|
LingerField l_linger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If $(D_PARAM timeout) is `0`, linger is disabled, otherwise enables the
|
||||||
|
* linger and sets the timeout.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* timeout = Timeout, in seconds.
|
||||||
|
*/
|
||||||
|
this(const ushort timeout)
|
||||||
|
{
|
||||||
|
time = timeout;
|
||||||
}
|
}
|
||||||
socket.setOption(SocketOptionLevel.SOCKET,
|
|
||||||
cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
|
///
|
||||||
cast(size_t) handle);
|
unittest
|
||||||
return socket;
|
{
|
||||||
|
{
|
||||||
|
auto linger = Linger(5);
|
||||||
|
assert(linger.enabled);
|
||||||
|
assert(linger.time == 5);
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
auto linger = Linger(0);
|
||||||
|
assert(!linger.enabled);
|
||||||
|
}
|
||||||
|
{ // Default constructor.
|
||||||
|
Linger linger;
|
||||||
|
assert(!linger.enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System dependent constructor.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* l_onoff = $(D_PSYMBOL l_onoff) value.
|
||||||
|
* l_linger = $(D_PSYMBOL l_linger) value.
|
||||||
|
*/
|
||||||
|
this(LingerField l_onoff, LingerField l_linger)
|
||||||
|
{
|
||||||
|
this.l_onoff = l_onoff;
|
||||||
|
this.l_linger = l_linger;
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto linger = Linger(1, 5);
|
||||||
|
assert(linger.l_onoff == 1);
|
||||||
|
assert(linger.l_linger == 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params:
|
||||||
|
* value = Whether to linger after the socket is closed.
|
||||||
|
*
|
||||||
|
* See_Also: $(D_PSYMBOL time).
|
||||||
|
*/
|
||||||
|
@property enabled(const bool value) pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
this.l_onoff = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Whether to linger after the socket is closed.
|
||||||
|
*/
|
||||||
|
@property bool enabled() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return this.l_onoff != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns: Timeout period, in seconds, to wait before closing the socket
|
||||||
|
* if the $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled).
|
||||||
|
*/
|
||||||
|
@property ushort time() const pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
return this.l_linger & ushort.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets timeout period, to wait before closing the socket if the
|
||||||
|
* $(D_PSYMBOL Linger) is $(D_PSYMBOL enabled), ignored otherwise.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* timeout = Timeout period, in seconds.
|
||||||
|
*/
|
||||||
|
@property void time(const ushort timeout) pure nothrow @safe @nogc
|
||||||
|
{
|
||||||
|
this.l_onoff = timeout > 0;
|
||||||
|
this.l_linger = timeout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +695,7 @@ else version (DragonFlyBSD)
|
|||||||
|
|
||||||
version (MacBSD)
|
version (MacBSD)
|
||||||
{
|
{
|
||||||
enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
|
enum ESOCKTNOSUPPORT = 44; // Socket type not suppoted.
|
||||||
}
|
}
|
||||||
|
|
||||||
private immutable
|
private immutable
|
||||||
@ -552,12 +722,32 @@ shared static this()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL AddressFamily) specifies a communication domain; this selects
|
||||||
|
* the protocol family which will be used for communication.
|
||||||
|
*/
|
||||||
|
enum AddressFamily : int
|
||||||
|
{
|
||||||
|
unspec = 0, /// Unspecified.
|
||||||
|
local = 1, /// Local to host (pipes and file-domain).
|
||||||
|
unix = local, /// POSIX name for PF_LOCAL.
|
||||||
|
inet = 2, /// IP protocol family.
|
||||||
|
ax25 = 3, /// Amateur Radio AX.25.
|
||||||
|
ipx = 4, /// Novell Internet Protocol.
|
||||||
|
appletalk = 5, /// Appletalk DDP.
|
||||||
|
netrom = 6, /// Amateur radio NetROM.
|
||||||
|
bridge = 7, /// Multiprotocol bridge.
|
||||||
|
atmpvc = 8, /// ATM PVCs.
|
||||||
|
x25 = 9, /// Reserved for X.25 project.
|
||||||
|
inet6 = 10, /// IP version 6.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error codes for $(D_PSYMBOL Socket).
|
* Error codes for $(D_PSYMBOL Socket).
|
||||||
*/
|
*/
|
||||||
enum SocketError : int
|
enum SocketError : int
|
||||||
{
|
{
|
||||||
/// Unknown error
|
/// Unknown error.
|
||||||
unknown = 0,
|
unknown = 0,
|
||||||
/// Firewall rules forbid connection.
|
/// Firewall rules forbid connection.
|
||||||
accessDenied = EPERM,
|
accessDenied = EPERM,
|
||||||
@ -587,12 +777,12 @@ enum SocketError : int
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
|
* $(D_PSYMBOL SocketException) should be thrown only if one of the socket functions
|
||||||
* returns -1 or $(D_PSYMBOL SOCKET_ERROR) and sets $(D_PSYMBOL errno),
|
* $(D_PSYMBOL socketError) and sets $(D_PSYMBOL errno), because
|
||||||
* because $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.
|
* $(D_PSYMBOL SocketException) relies on the $(D_PSYMBOL errno) value.
|
||||||
*/
|
*/
|
||||||
class SocketException : Exception
|
class SocketException : Exception
|
||||||
{
|
{
|
||||||
immutable SocketError error = SocketError.unknown;
|
const SocketError error = SocketError.unknown;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params:
|
* Params:
|
||||||
@ -680,16 +870,16 @@ abstract class Socket
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Socket handle.
|
/// Socket handle.
|
||||||
protected socket_t handle_;
|
protected SocketType handle_;
|
||||||
|
|
||||||
/// Address family.
|
/// Address family.
|
||||||
protected AddressFamily family;
|
protected AddressFamily family;
|
||||||
|
|
||||||
private @property void handle(socket_t handle) @nogc
|
private @property void handle(SocketType handle) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(handle != socket_t.init);
|
assert(handle != SocketType.init);
|
||||||
assert(handle_ == socket_t.init, "Socket handle cannot be changed");
|
assert(handle_ == SocketType.init, "Socket handle cannot be changed");
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
@ -703,7 +893,7 @@ abstract class Socket
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@property inout(socket_t) handle() inout const pure nothrow @safe @nogc
|
@property inout(SocketType) handle() inout const pure nothrow @safe @nogc
|
||||||
{
|
{
|
||||||
return handle_;
|
return handle_;
|
||||||
}
|
}
|
||||||
@ -715,10 +905,10 @@ abstract class Socket
|
|||||||
* handle = Socket.
|
* handle = Socket.
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(socket_t handle, AddressFamily af) @nogc
|
this(SocketType handle, AddressFamily af) @nogc
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
assert(handle != socket_t.init);
|
assert(handle != SocketType.init);
|
||||||
}
|
}
|
||||||
body
|
body
|
||||||
{
|
{
|
||||||
@ -759,7 +949,7 @@ abstract class Socket
|
|||||||
cast(int) level,
|
cast(int) level,
|
||||||
cast(int) option,
|
cast(int) option,
|
||||||
result.ptr,
|
result.ptr,
|
||||||
&length) == SOCKET_ERROR)
|
&length) == socketError)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to get socket option");
|
throw defaultAllocator.make!SocketException("Unable to get socket option");
|
||||||
}
|
}
|
||||||
@ -779,7 +969,7 @@ abstract class Socket
|
|||||||
SocketOption option,
|
SocketOption option,
|
||||||
out Linger result) const @trusted @nogc
|
out Linger result) const @trusted @nogc
|
||||||
{
|
{
|
||||||
return getOption(level, option, (&result.clinger)[0..1]);
|
return getOption(level, option, (&result)[0 .. 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
@ -826,7 +1016,7 @@ abstract class Socket
|
|||||||
cast(int)level,
|
cast(int)level,
|
||||||
cast(int)option,
|
cast(int)option,
|
||||||
value.ptr,
|
value.ptr,
|
||||||
cast(uint) value.length) == SOCKET_ERROR)
|
cast(uint) value.length) == socketError)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to set socket option");
|
throw defaultAllocator.make!SocketException("Unable to set socket option");
|
||||||
}
|
}
|
||||||
@ -843,7 +1033,7 @@ abstract class Socket
|
|||||||
void setOption(SocketOptionLevel level, SocketOption option, Linger value)
|
void setOption(SocketOptionLevel level, SocketOption option, Linger value)
|
||||||
const @trusted @nogc
|
const @trusted @nogc
|
||||||
{
|
{
|
||||||
setOption(level, option, (&value.clinger)[0..1]);
|
setOption(level, option, (&value)[0 .. 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ditto.
|
/// Ditto.
|
||||||
@ -878,7 +1068,7 @@ abstract class Socket
|
|||||||
}
|
}
|
||||||
else version (Windows)
|
else version (Windows)
|
||||||
{
|
{
|
||||||
return blocking_;
|
return this.blocking_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -892,12 +1082,12 @@ abstract class Socket
|
|||||||
{
|
{
|
||||||
int fl = fcntl(handle_, F_GETFL, 0);
|
int fl = fcntl(handle_, F_GETFL, 0);
|
||||||
|
|
||||||
if (fl != SOCKET_ERROR)
|
if (fl != socketError)
|
||||||
{
|
{
|
||||||
fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
|
fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
|
||||||
fl = fcntl(handle_, F_SETFL, fl);
|
fl = fcntl(handle_, F_SETFL, fl);
|
||||||
}
|
}
|
||||||
if (fl == SOCKET_ERROR)
|
if (fl == socketError)
|
||||||
{
|
{
|
||||||
throw make!SocketException(defaultAllocator,
|
throw make!SocketException(defaultAllocator,
|
||||||
"Unable to set socket blocking");
|
"Unable to set socket blocking");
|
||||||
@ -906,12 +1096,12 @@ abstract class Socket
|
|||||||
else version (Windows)
|
else version (Windows)
|
||||||
{
|
{
|
||||||
uint num = !yes;
|
uint num = !yes;
|
||||||
if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
|
if (ioctlsocket(handle_, FIONBIO, &num) == socketError)
|
||||||
{
|
{
|
||||||
throw make!SocketException(defaultAllocator,
|
throw make!SocketException(defaultAllocator,
|
||||||
"Unable to set socket blocking");
|
"Unable to set socket blocking");
|
||||||
}
|
}
|
||||||
blocking_ = yes;
|
this.blocking_ = yes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -963,7 +1153,7 @@ abstract class Socket
|
|||||||
{
|
{
|
||||||
.close(handle_);
|
.close(handle_);
|
||||||
}
|
}
|
||||||
handle_ = socket_t.init;
|
handle_ = SocketType.init;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -976,7 +1166,7 @@ abstract class Socket
|
|||||||
*/
|
*/
|
||||||
void listen(int backlog) const @trusted @nogc
|
void listen(int backlog) const @trusted @nogc
|
||||||
{
|
{
|
||||||
if (.listen(handle_, backlog) == SOCKET_ERROR)
|
if (.listen(handle_, backlog) == socketError)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to listen on socket");
|
throw defaultAllocator.make!SocketException("Unable to listen on socket");
|
||||||
}
|
}
|
||||||
@ -1029,8 +1219,8 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
*/
|
*/
|
||||||
this(AddressFamily af) @trusted @nogc
|
this(AddressFamily af) @trusted @nogc
|
||||||
{
|
{
|
||||||
auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
|
auto handle = cast(SocketType) socket(af, 1, 0);
|
||||||
if (handle == socket_t.init)
|
if (handle == SocketType.init)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to create socket");
|
throw defaultAllocator.make!SocketException("Unable to create socket");
|
||||||
}
|
}
|
||||||
@ -1047,7 +1237,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
*/
|
*/
|
||||||
void bind(Address address) const @trusted @nogc
|
void bind(Address address) const @trusted @nogc
|
||||||
{
|
{
|
||||||
if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
|
if (.bind(handle_, address.name, address.length) == socketError)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Unable to bind socket");
|
throw defaultAllocator.make!SocketException("Unable to bind socket");
|
||||||
}
|
}
|
||||||
@ -1066,7 +1256,7 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
*/
|
*/
|
||||||
ConnectedSocket accept() @trusted @nogc
|
ConnectedSocket accept() @trusted @nogc
|
||||||
{
|
{
|
||||||
socket_t sock;
|
SocketType sock;
|
||||||
|
|
||||||
version (linux)
|
version (linux)
|
||||||
{
|
{
|
||||||
@ -1075,14 +1265,14 @@ class StreamSocket : Socket, ConnectionOrientedSocket
|
|||||||
{
|
{
|
||||||
flags |= SOCK_NONBLOCK;
|
flags |= SOCK_NONBLOCK;
|
||||||
}
|
}
|
||||||
sock = cast(socket_t).accept4(handle_, null, null, flags);
|
sock = cast(SocketType).accept4(handle_, null, null, flags);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sock = cast(socket_t).accept(handle_, null, null);
|
sock = cast(SocketType).accept(handle_, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sock == socket_t.init)
|
if (sock == SocketType.init)
|
||||||
{
|
{
|
||||||
if (wouldHaveBlocked())
|
if (wouldHaveBlocked())
|
||||||
{
|
{
|
||||||
@ -1147,7 +1337,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
* handle = Socket.
|
* handle = Socket.
|
||||||
* af = Address family.
|
* af = Address family.
|
||||||
*/
|
*/
|
||||||
this(socket_t handle, AddressFamily af) @nogc
|
this(SocketType handle, AddressFamily af) @nogc
|
||||||
{
|
{
|
||||||
super(handle, af);
|
super(handle, af);
|
||||||
}
|
}
|
||||||
@ -1196,7 +1386,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
{
|
{
|
||||||
disconnected_ = true;
|
disconnected_ = true;
|
||||||
}
|
}
|
||||||
else if (ret == SOCKET_ERROR)
|
else if (ret == socketError)
|
||||||
{
|
{
|
||||||
if (wouldHaveBlocked())
|
if (wouldHaveBlocked())
|
||||||
{
|
{
|
||||||
@ -1233,7 +1423,7 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
|
|||||||
}
|
}
|
||||||
|
|
||||||
sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
|
sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
|
||||||
if (sent != SOCKET_ERROR)
|
if (sent != socketError)
|
||||||
{
|
{
|
||||||
return sent;
|
return sent;
|
||||||
}
|
}
|
||||||
@ -1273,14 +1463,11 @@ class InternetAddress : Address
|
|||||||
/// Internal internet address representation.
|
/// Internal internet address representation.
|
||||||
protected sockaddr_storage storage;
|
protected sockaddr_storage storage;
|
||||||
}
|
}
|
||||||
immutable ushort port_;
|
const ushort port_;
|
||||||
|
|
||||||
enum
|
enum ushort anyPort = 0;
|
||||||
{
|
|
||||||
anyPort = 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
this(in string host, ushort port = anyPort) @nogc
|
this(string host, const ushort port = anyPort) @nogc
|
||||||
{
|
{
|
||||||
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
|
if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
|
||||||
{
|
{
|
||||||
@ -1288,7 +1475,7 @@ class InternetAddress : Address
|
|||||||
"Address info lookup is not available on this system");
|
"Address info lookup is not available on this system");
|
||||||
}
|
}
|
||||||
addrinfo* ai_res;
|
addrinfo* ai_res;
|
||||||
port_ = port;
|
this.port_ = port;
|
||||||
|
|
||||||
// Make C-string from host.
|
// Make C-string from host.
|
||||||
auto node = cast(char[]) allocator.allocate(host.length + 1);
|
auto node = cast(char[]) allocator.allocate(host.length + 1);
|
||||||
@ -1304,16 +1491,17 @@ class InternetAddress : Address
|
|||||||
const(char)* servicePointer;
|
const(char)* servicePointer;
|
||||||
if (port)
|
if (port)
|
||||||
{
|
{
|
||||||
|
ushort originalPort = port;
|
||||||
ushort start;
|
ushort start;
|
||||||
for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
|
for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
|
||||||
{
|
{
|
||||||
ushort rest = port % 10;
|
ushort rest = originalPort % 10;
|
||||||
if (rest != 0)
|
if (rest != 0)
|
||||||
{
|
{
|
||||||
service[i] = cast(char) (rest + '0');
|
service[i] = cast(char) (rest + '0');
|
||||||
start = i;
|
start = i;
|
||||||
}
|
}
|
||||||
port /= 10;
|
originalPort /= 10;
|
||||||
}
|
}
|
||||||
servicePointer = service[start .. $].ptr;
|
servicePointer = service[start .. $].ptr;
|
||||||
}
|
}
|
||||||
@ -1333,12 +1521,23 @@ class InternetAddress : Address
|
|||||||
{
|
{
|
||||||
*dp = *sp;
|
*dp = *sp;
|
||||||
}
|
}
|
||||||
if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
|
if (ai_res.ai_family != AddressFamily.inet && ai_res.ai_family != AddressFamily.inet6)
|
||||||
{
|
{
|
||||||
throw defaultAllocator.make!SocketException("Wrong address family");
|
throw defaultAllocator.make!SocketException("Wrong address family");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto address = defaultAllocator.make!InternetAddress("127.0.0.1");
|
||||||
|
assert(address.port == InternetAddress.anyPort);
|
||||||
|
assert(address.name !is null);
|
||||||
|
assert(address.family == AddressFamily.inet);
|
||||||
|
|
||||||
|
defaultAllocator.dispose(address);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
|
* Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
|
||||||
*/
|
*/
|
||||||
@ -1355,9 +1554,9 @@ class InternetAddress : Address
|
|||||||
// FreeBSD wants to know the exact length of the address on bind.
|
// FreeBSD wants to know the exact length of the address on bind.
|
||||||
switch (family)
|
switch (family)
|
||||||
{
|
{
|
||||||
case AddressFamily.INET:
|
case AddressFamily.inet:
|
||||||
return sockaddr_in.sizeof;
|
return sockaddr_in.sizeof;
|
||||||
case AddressFamily.INET6:
|
case AddressFamily.inet6:
|
||||||
return sockaddr_in6.sizeof;
|
return sockaddr_in6.sizeof;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
@ -1376,6 +1575,15 @@ class InternetAddress : Address
|
|||||||
{
|
{
|
||||||
return port_;
|
return port_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto address = defaultAllocator.make!InternetAddress("127.0.0.1",
|
||||||
|
cast(ushort) 1234);
|
||||||
|
assert(address.port == 1234);
|
||||||
|
defaultAllocator.dispose(address);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
107
source/tanya/typecons.d
Normal file
107
source/tanya/typecons.d
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type constructors.
|
||||||
|
*
|
||||||
|
* This module contains templates that allow to build new types from the
|
||||||
|
* available ones.
|
||||||
|
*
|
||||||
|
* Copyright: Eugene Wissner 2017.
|
||||||
|
* 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.typecons;
|
||||||
|
|
||||||
|
import std.meta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* $(D_PSYMBOL Pair) can store two heterogeneous objects.
|
||||||
|
*
|
||||||
|
* The objects can by accessed by index as $(D_INLINECODE obj[0]) and
|
||||||
|
* $(D_INLINECODE obj[1]) or by optional names (e.g.
|
||||||
|
* $(D_INLINECODE obj.first)).
|
||||||
|
*
|
||||||
|
* $(D_PARAM Specs) contains a list of object types and names. First
|
||||||
|
* comes the object type, then an optional string containing the name.
|
||||||
|
* If you want the object be accessible only by its index (`0` or `1`),
|
||||||
|
* just skip the name.
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* Specs = Field types and names.
|
||||||
|
*/
|
||||||
|
template Pair(Specs...)
|
||||||
|
{
|
||||||
|
template parseSpecs(int fieldCount, Specs...)
|
||||||
|
{
|
||||||
|
static if (Specs.length == 0)
|
||||||
|
{
|
||||||
|
alias parseSpecs = AliasSeq!();
|
||||||
|
}
|
||||||
|
else static if (is(Specs[0]) && fieldCount < 2)
|
||||||
|
{
|
||||||
|
static if (is(typeof(Specs[1]) == string))
|
||||||
|
{
|
||||||
|
alias parseSpecs
|
||||||
|
= AliasSeq!(Specs[0],
|
||||||
|
parseSpecs!(fieldCount + 1, Specs[2 .. $]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias parseSpecs
|
||||||
|
= AliasSeq!(Specs[0],
|
||||||
|
parseSpecs!(fieldCount + 1, Specs[1 .. $]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
static assert(false, "Invalid argument: " ~ Specs[0].stringof);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pair
|
||||||
|
{
|
||||||
|
/// Field types.
|
||||||
|
alias Types = parseSpecs!(0, Specs);
|
||||||
|
|
||||||
|
static assert(Types.length == 2, "Invalid argument count.");
|
||||||
|
|
||||||
|
// Create field aliases.
|
||||||
|
static if (is(typeof(Specs[1]) == string))
|
||||||
|
{
|
||||||
|
mixin("alias " ~ Specs[1] ~ " = expand[0];");
|
||||||
|
}
|
||||||
|
static if (is(typeof(Specs[2]) == string))
|
||||||
|
{
|
||||||
|
mixin("alias " ~ Specs[2] ~ " = expand[1];");
|
||||||
|
}
|
||||||
|
else static if (is(typeof(Specs[3]) == string))
|
||||||
|
{
|
||||||
|
mixin("alias " ~ Specs[3] ~ " = expand[1];");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the values of the $(D_PSYMBOL Pair) as a list of values.
|
||||||
|
Types expand;
|
||||||
|
|
||||||
|
alias expand this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
static assert(is(Pair!(int, int)));
|
||||||
|
static assert(!is(Pair!(int, 5)));
|
||||||
|
|
||||||
|
static assert(is(Pair!(int, "first", int)));
|
||||||
|
static assert(is(Pair!(int, "first", int, "second")));
|
||||||
|
static assert(is(Pair!(int, "first", int)));
|
||||||
|
|
||||||
|
static assert(is(Pair!(int, int, "second")));
|
||||||
|
static assert(!is(Pair!("first", int, "second", int)));
|
||||||
|
static assert(!is(Pair!(int, int, int)));
|
||||||
|
|
||||||
|
static assert(!is(Pair!(int, "first")));
|
||||||
|
}
|
Reference in New Issue
Block a user